0% found this document useful (0 votes)
9 views32 pages

Java Microservices Complete Learning Notes Alacriti-1

These learning notes provide a comprehensive path for mastering Core Java and Spring Boot, specifically tailored for microservices in the payment domain. The document outlines key concepts, interview expectations, and practical study methods to prepare for Senior Software Engineer roles. It covers a wide range of topics from Java fundamentals to system design, emphasizing the importance of clarity, correctness, and production mindset in interviews.

Uploaded by

Bhaswat Mandal
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views32 pages

Java Microservices Complete Learning Notes Alacriti-1

These learning notes provide a comprehensive path for mastering Core Java and Spring Boot, specifically tailored for microservices in the payment domain. The document outlines key concepts, interview expectations, and practical study methods to prepare for Senior Software Engineer roles. It covers a wide range of topics from Java fundamentals to system design, emphasizing the importance of clarity, correctness, and production mindset in interviews.

Uploaded by

Bhaswat Mandal
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Complete Learning Notes: Core Java + Spring Boot

Microservices (Payment Domain)


These notes are written to be read top-to-bottom as a learning path. If you read in order, you will build
concepts from fundamentals → JVM → concurrency → APIs → Spring Boot → microservices → consistency
→ Saga → security → testing → system design. AWS content is intentionally excluded, as requested.
Generated: 2026-02-07

Goal: Make you interview-ready for Senior Software Engineer roles requiring Core Java, REST API development,
microservices, distributed systems thinking, payment-domain flows, performance, reliability, and security.

How to study (practical):


• Read once end-to-end to build a mental map.
• On second pass, stop at each Checkpoint and answer aloud.
• For in-person interviews, practice explaining with a whiteboard: data flow, state transitions, failure
handling.

Interview reality: You will be evaluated on (1) correctness, (2) clarity, (3) trade-offs, and (4) production mindset
(failures, retries, idempotency, monitoring, data consistency). These notes keep that mindset throughout.

Area What interviewers expect you to articulate

Core Java OOP, static/final, immutability, exceptions, collections internals, generics, streams

JVM heap/stack/metaspace, GC intuition, leak patterns, performance tuning approach

Concurrency JMM, happens-before, volatile vs synchronized, locks, executors, deadlocks

REST APIs resource modeling, status codes, validation, pagination, versioning, idempotency

Spring Boot DI, configuration, validation, transactions, error handling, layered architecture

Microservices service boundaries, sync vs async, resiliency, observability, contracts

Data consistency ACID vs BASE, isolation levels, outbox, dedup, exactly-once illusions

Saga orchestration/choreography, compensations, unknown outcomes, reconciliation

Security authn/authz, JWT/OAuth2 basics, secure coding, payment data handling

Testing & design unit/integration/contract tests, system design for payments


Table of Contents (Learning Path)
1 1. Java Fundamentals You Must Get Right (types, references, static/final, immutability)
2 2. OOP Done Like a Senior (modeling, invariants, composition)
3 3. SOLID + Clean Code Applied to Services (testability, changeability)
4 4. Exceptions and Error Modeling (domain vs technical failures)
5 5. Collections: From Usage to Internals (HashMap, ConcurrentHashMap, complexity)
6 6. Generics + Streams for Real Work (PECS, type erasure, pitfalls)
7 7. JVM & Memory Management (heap/stack, GC, tuning mindset)
8 8. Concurrency Masterclass (JMM, volatile, synchronized, locks, executors)
9 9. REST API Design (validation, pagination, idempotency, versioning)
10 10. Spring Boot Production Basics (DI, config, validation, transactions, layering)

11 11. Microservices Architecture (boundaries, communication, contracts)

12 12. Resilience + Observability (timeouts, retries, circuit breaker, tracing)

13 13. Data Consistency in Distributed Systems (ACID/BASE, isolation, outbox)

14 14. Saga Orchestration for Payments (compensations, unknown outcomes, reconciliation)

15 15. Security for APIs + Payment Systems (JWT, encryption concepts, PCI mindset)

16 16. Testing Strategy (unit, integration, contract, performance)

17 17. System Design Walkthrough: Payment Service End-to-End

18 18. Final Interview Drills (prompts, checklists, quick answers)


1. Java Fundamentals You Must Get Right
This chapter builds the core language intuition that repeatedly shows up in interviews: how Java passes
values, what static/final really mean, how immutability helps correctness, and how equality contracts affect
collections.

1.1 How Java passes data: always pass-by-value

Java is pass-by-value for everything. For primitives, the value is the primitive. For objects, the value is the
reference (a pointer-like value). This is why you can mutate the same object inside a method, but you cannot
make the caller’s variable point to a new object.
class PassByValueDemo {
static void rebind(StringBuilder sb) {
sb = new StringBuilder("NEW"); // only local variable changes
}
static void mutate(StringBuilder sb) {
[Link]("!"); // mutates shared object on heap
}
public static void main(String[] args) {
StringBuilder a = new StringBuilder("Hi");
rebind(a);
mutate(a);
[Link](a); // Hi!
}
}

Checkpoint: Explain in one sentence why rebind() does not change the caller’s reference but mutate() changes
the caller’s object state.

1.2 static: class-level state and behavior

static means the member belongs to the class, not to a particular object instance. There is one copy per
classloader.
• Static fields are shared across all instances: useful for constants and shared caches (but be careful with
memory leaks).
• Static methods are not polymorphic: they are resolved by the reference type at compile time (method
hiding).
• Static initialization runs when class is first loaded (lazy, unless forced).

class Parent { static void hello(){ [Link]("Parent"); } }


class Child extends Parent { static void hello(){ [Link]("Child"); } } // hides

public class StaticDispatch {


public static void main(String[] args) {
Parent p = new Child();
[Link](); // Parent (NOT Child) because static uses reference type
}
}

Interview line: “Static methods are resolved at compile-time; instance methods are dynamically dispatched.”

1.3 final: reassignment vs immutability

final has different meanings depending on where you use it.


• final variable: cannot be reassigned after initialization.
• final reference: reference cannot point to another object, but the object may still be mutable.
• final method: cannot be overridden.
• final class: cannot be extended.

final class Holder {


final [Link]<String> list = new [Link]<>();
void add(String s){ [Link](s); } // allowed; final reference doesn't prevent mutation
}

To make an object truly immutable, you must design it so that state cannot change, and you do not expose
internal mutable references.

1.4 Immutability: the simplest concurrency tool

Immutable objects are inherently thread-safe: no thread can observe partial mutations because there are no
mutations.
import [Link];

final class Money {


private final String currency;
private final long cents;

Money(String currency, long cents) {


[Link] = [Link](currency);
[Link] = cents;
}
String currency(){ return currency; }
long cents(){ return cents; }

Money add(Money other){


if(![Link]([Link])) throw new IllegalArgumentException("Currency mismatch");
return new Money(currency, cents + [Link]);
}
}

• Use immutable types for: identifiers, money amounts, timestamps, domain events, request/response
DTOs.
• Common mistake: returning internal List/Map directly. Return unmodifiable copies.
• Use [Link] (Instant, LocalDate) instead of [Link].

1.5 equals(), hashCode(), compareTo(): correctness contracts

Collections such as HashMap/HashSet depend on correct equality and hashing. If you break these, you get
“impossible” bugs (lost entries, duplicates).
• equals must be: reflexive, symmetric, transitive, consistent; handle null.
• hashCode: if [Link](b) then [Link] == [Link].
• Never use mutable fields in hashCode if object is used as HashMap key.

final class UserKey {


private final long userId;
UserKey(long userId){ [Link] = userId; }

@Override public boolean equals(Object o){


return (o instanceof UserKey uk) && [Link] == userId;
}
@Override public int hashCode(){ return [Link](userId); }
}

Checkpoint: Why is it dangerous to base hashCode on email if email can change?


2. OOP Done Like a Senior
Interviews rarely fail you because you forgot definitions. They fail you because you cannot model a domain
cleanly and enforce invariants. Here we learn OOP as design, not vocabulary.

2.1 Encapsulation as invariant protection

Encapsulation is not “private fields + getters”. It is: hide state and expose behavior that keeps state valid.
class Account {
private long balanceCents;

public Account(long initial) {


if (initial < 0) throw new IllegalArgumentException("negative");
[Link] = initial;
}

public void debit(long amount) {


if (amount <= 0) throw new IllegalArgumentException("amount");
if (balanceCents < amount) throw new IllegalStateException("insufficient funds");
balanceCents -= amount;
}

public void credit(long amount) {


if (amount <= 0) throw new IllegalArgumentException("amount");
balanceCents += amount;
}

public long balanceCents() { return balanceCents; }


}

Notice: we did not give a setter for balance; we force all updates through debit/credit so rules are enforced.

2.2 Abstraction and interfaces: contracts between modules

Abstraction is about depending on what you need, not on details. In services, you want core domain logic
to depend on interfaces, not on framework classes.
interface PaymentGateway {
GatewayResponse authorize(Payment payment);
GatewayResponse capture(Payment payment);
}

Senior hint: In interviews, when you introduce an interface, also say what it enables: testability, swapping
vendors, failure simulation, and clean separation.

2.3 Inheritance vs composition (and why composition usually wins)

Inheritance creates tight coupling and fragile base classes. Composition models variability safely.
interface FeePolicy { long fee(long amountCents); }

class FlatFee implements FeePolicy {


private final long fee;
FlatFee(long fee){ [Link] = fee; }
public long fee(long amount){ return fee; }
}

class PercentFee implements FeePolicy {


private final int bp; // basis points
PercentFee(int bp){ [Link] = bp; }
public long fee(long amount){ return (amount * bp) / 10_000; }
}

class PricingService {
private final FeePolicy policy;
PricingService(FeePolicy policy){ [Link] = policy; }

long total(long amount){ return amount + [Link](amount); }


}

2.4 Polymorphism as the engine of extensibility

Polymorphism lets you add new behavior without rewriting existing code (Open/Closed). In payment
domain: different payment methods, different fraud checks, different routing rules.
• Prefer polymorphism when behavior branches would otherwise become huge if/else ladders.
• Keep implementations small and cohesive; avoid “God strategy”.
• Make selection logic explicit (router/factory).
3. SOLID + Clean Code Applied to Services
SOLID is a set of heuristics to keep code change-friendly. The interviewer wants to see that you can build
systems that survive constant requirement changes.

3.1 SRP: separate orchestration, domain rules, and infrastructure

In microservices, a common anti-pattern is mixing HTTP logic + DB + payment gateway calls inside one
controller method. Split layers: Controller → Application service → Domain → Repositories/Gateways.
// Controller: HTTP boundary only
@RestController
class PaymentController {
private final PaymentAppService app;
PaymentController(PaymentAppService app){ [Link] = app; }

@PostMapping("/payments")
PaymentResult create(@RequestHeader("Idempotency-Key") String key,
@Valid @RequestBody CreatePaymentRequest req) {
return [Link](key, req);
}
}

// Application service: orchestrates use-cases


class PaymentAppService {
private final PaymentRepository repo;
private final PaymentGateway gateway;

PaymentAppService(PaymentRepository repo, PaymentGateway gateway){


[Link] = repo; [Link] = gateway;
}

PaymentResult createPayment(String idemKey, CreatePaymentRequest req){


// idempotency check omitted here; covered later
Payment p = [Link]([Link](), [Link](), [Link]());
[Link](p);
GatewayResponse r = [Link](p);
[Link](r);
[Link](p);
return [Link](p);
}
}

Checkpoint: Explain what belongs in Controller vs Application Service vs Domain.

3.2 OCP + Strategy (fee, routing, validation)

Open/Closed: you add a new payment method without changing the orchestration code.

3.3 DIP: depend on interfaces, not concrete classes


Dependency inversion enables unit tests that are fast and deterministic: you can inject a fake gateway/repo
and simulate failures.
class FakeGateway implements PaymentGateway {
public GatewayResponse authorize(Payment p){ return [Link]("AUTH-123"); }
public GatewayResponse capture(Payment p){ return [Link]("CAP-999"); }
}

3.4 LSP + ISP: design contracts that don’t surprise callers

• LSP: If a method accepts PaymentGateway, any implementation must obey the contract (timeouts,
idempotency assumptions, error types).
• ISP: Prefer small interfaces (PaymentAuthorizer, PaymentCapturer) if clients do not need full gateway
surface area.
4. Exceptions and Error Modeling
In payment microservices, error handling is not a side topic. It defines correctness and user trust.

4.1 Exceptions vs error responses

Inside Java code, throw exceptions to signal failure. At HTTP boundaries, convert failures into stable error
responses with consistent codes.
• Separate domain errors (invalid state, insufficient funds) from technical errors (DB down, gateway
timeout).
• Domain errors usually map to 4xx/409; technical errors map to 5xx (or 503).
• Never leak stack traces to clients.

class DomainException extends RuntimeException {


private final String code;
DomainException(String code, String msg){ super(msg); [Link] = code; }
String code(){ return code; }
}

record ErrorResponse(String code, String message, String traceId) {}

@RestControllerAdvice
class Errors {
@ExceptionHandler([Link])
ResponseEntity<ErrorResponse> domain(DomainException ex, HttpServletRequest req){
return [Link](409).body(new ErrorResponse([Link](), [Link](), [Link]("X-C
}
}

Checkpoint: If the gateway times out, do you return “FAILED”? Why is “UNKNOWN/PENDING” often better?
5. Collections: From Usage to Internals
Interviews love collections because it reveals whether you understand performance and correctness beyond
writing code that “works”.

5.1 When to choose which collection

• ArrayList: best for indexed access and append-heavy workloads.


• LinkedList: rarely best; can be useful for deque-like operations but ArrayDeque often wins.
• HashMap/HashSet: fast lookup; requires correct equals/hashCode.
• TreeMap/TreeSet: ordered operations, range queries.
• ConcurrentHashMap: high concurrency reads/writes; forbids null keys/values.

5.2 HashMap internals (the explanation interviewers want)

High-level flow for get(key): compute hash → find bucket index → scan chain or tree → equals() match →
return value.
• Capacity is power-of-two; index computed via (n-1) & hash (fast).
• Collisions store entries in bucket chain; may treeify to red-black tree after threshold.
• Resize re-bins entries; can be expensive; use initial capacity when you can estimate size.
• Hash flooding can degrade performance; modern JDK mitigates using tree bins.

// Capacity power-of-two enables fast bucket index computation:


int idx = ([Link] - 1) & hash;

5.3 ConcurrentHashMap: what makes it different

• Designed for concurrency: avoids global lock for all operations.


• Reads are mostly lock-free; updates use fine-grained mechanisms (CAS/locks).
• No null keys/values to avoid ambiguity under concurrent access.
• Iterators are weakly-consistent: no ConcurrentModificationException, but may not reflect all updates.

var chm = new [Link]<String, String>();


[Link]("k", k -> "computed");
6. Generics + Streams for Real Work

6.1 Generics: invariance and PECS

Generics are invariant: List is not List. Use wildcards. PECS: Producer Extends, Consumer Super.
static void readNumbers([Link]<? extends Number> nums) { // Producer
for (Number n : nums) [Link](n);
}
static void addIntegers([Link]<? super Integer> dst) { // Consumer
[Link](1); [Link](2);
}

6.2 Type erasure: what disappears at runtime

• Generic type info is erased at runtime: you cannot do new T(), instanceof List.
• Prefer explicit DTOs over Map for APIs (safer, clearer).

6.3 Streams: readability vs complexity

Streams are great for transformations but can reduce clarity if overused. Do not hide business rules behind
long stream pipelines.
var counts =
[Link]("A","B","A","C","A").stream()
.collect([Link](x -> x,
[Link]()));

Checkpoint: When is a for-loop better than a stream? Answer with 2 reasons.


7. JVM & Memory Management
You do not need to be a JVM engineer, but you must have correct mental models for memory, GC, and
diagnosing performance issues.

7.1 Memory areas (stack, heap, metaspace)

• Stack: per-thread call frames, local variables, references; fast allocation/deallocation.


• Heap: shared objects and arrays; GC-managed.
• Metaspace: class metadata; native memory; classloader leaks can grow it.

7.2 GC: the generational hypothesis

Most objects die young. So collectors optimize for young-generation collection.


• Young gen: frequent minor GCs, cheap if survivors are few.
• Old gen: less frequent but costlier collections.
• Stop-the-world pauses impact latency (p95/p99).

7.3 Real memory leaks in Java

Java prevents dangling pointers, but you can still leak by keeping references reachable.
• Static caches that grow without eviction.
• Listener lists not cleaned.
• ThreadLocal usage in thread pools without remove().
• Large maps/lists holding old data due to not clearing references.

class LeakyCache {
private static final [Link]<String, byte[]> CACHE = new [Link]<>();
static void add(String k) { [Link](k, new byte[10_000_000]); } // unbounded growth
}

7.4 Performance tuning mindset (what to say in interviews)

• Start with symptoms: latency percentiles, throughput, error rate, CPU, memory, GC pause time.
• Form hypotheses: allocation rate too high? DB pool saturated? thread pool exhausted?
• Use tools: thread dump, heap dump, JFR/Flight Recorder, metrics dashboards.
• Fix root causes: reduce allocations in hot paths, bound caches, add timeouts, optimize DB queries.
8. Concurrency Masterclass
Concurrency is a top filter for senior roles. You must explain not just APIs, but the Java Memory Model:
visibility, ordering, and atomicity.

8.1 Thread lifecycle and interruption

• Interruption is cooperative: you signal a thread to stop; it checks the interrupt flag.
• Best practice: when catching InterruptedException, restore flag and stop work.

try {
[Link](1000);
} catch (InterruptedException e) {
[Link]().interrupt(); // restore
return;
}

8.2 JMM and happens-before (core concept)

Without synchronization, one thread’s writes may not be visible to another. JMM defines happens-before
rules that guarantee visibility and ordering.
• volatile write happens-before subsequent volatile read of same variable.
• synchronized unlock happens-before subsequent lock on same monitor.
• [Link] happens-before actions in started thread; join happens-before after join returns.

class SafePublishExample {
private volatile boolean ready = false;
private int data;

void producer() { data = 42; ready = true; }


int consumer() { while(!ready){} return data; } // sees 42
}

8.3 volatile vs synchronized vs atomic classes

• volatile gives visibility/order but not atomic compound operations like ++.
• synchronized gives mutual exclusion + visibility, but can reduce concurrency if overused.
• AtomicInteger uses CAS for atomic updates; good for counters and simple state.

class Counter {
private final [Link] c = new [Link]
int inc(){ return [Link](); }
}
8.4 Executors: correct concurrency in services

In production services, creating raw threads is uncommon. Use executors to bound concurrency and
manage resources.
var pool = [Link](16);
try {
var f = [Link](() -> "ok");
String r = [Link](300, [Link]);
} finally {
[Link]();
}

8.5 Deadlocks: how to prevent

• Establish lock ordering and follow it everywhere.


• Use tryLock with timeout to avoid infinite waits.
• Avoid holding locks while calling remote services or blocking IO.

final Object A = new Object();


final Object B = new Object();

// Rule: always lock A then B


synchronized (A) {
synchronized (B) {
// critical section
}
}

Checkpoint: Explain why deadlocks are more likely when you call external services inside synchronized blocks.
9. REST API Design (Interview Grade)
REST design is not about annotations. It is about resource modeling, correctness, reliability, and client
safety (idempotency).

9.1 Resources, methods, and status codes

• Model nouns: /payments, /refunds, /transactions.


• Use methods correctly: GET safe, POST create, PUT replace, PATCH partial update, DELETE remove.
• Use proper codes: 201 created, 400 bad request, 401/403 auth, 404 not found, 409 conflict, 422
validation (optional), 500/503 server/unavailable.

9.2 Validation at boundaries

Reject invalid requests early using bean validation. Keep domain invariants strict even if validation exists at
controller level.

9.3 Pagination and filtering

• Offset pagination can skip/duplicate under concurrent inserts.


• Cursor pagination using (createdAt, id) provides stable navigation.
• Document sort order and limits.

9.4 Idempotency: must-have for payment APIs

If the client retries a POST due to timeout, your system must not create duplicates or double-charge. Use
Idempotency-Key.
class IdempotencyStore {
[Link]<CachedResponse> find(String key) { return [Link](); }
void save(String key, String requestHash, CachedResponse resp) { /* persist */ }
}

• Store key + request hash + response; if same key different hash → 409 conflict.
• Return the same response for repeated calls with same key.
• Apply idempotency for command endpoints: create payment, capture, refund.
10. Spring Boot Production Basics
Spring Boot helps you build services quickly, but interviews test whether you understand architecture:
layering, DI, configuration, transactions, and error handling.

10.1 Dependency Injection: constructor injection

• Constructor injection enables immutability and testability.


• Avoid field injection (harder to test, hidden dependencies).
• Keep beans small and cohesive.

10.2 Configuration management


@ConfigurationProperties(prefix="payments")
class PaymentProps {
private int timeoutMs;
public int getTimeoutMs(){ return timeoutMs; }
public void setTimeoutMs(int timeoutMs){ [Link] = timeoutMs; }
}

10.3 Transactions are local; microservices need sagas

@Transactional gives atomicity inside one database. It does not make remote calls atomic.
@Service
class PaymentRepoService {
private final PaymentRepository repo;
PaymentRepoService(PaymentRepository repo){ [Link] = repo; }

@Transactional
public PaymentEntity create(PaymentEntity e){ return [Link](e); }
}

Checkpoint: Explain why @Transactional does not solve distributed consistency between Payment and Order
services.
11. Microservices Architecture

11.1 Service boundaries

Define services around business capabilities. Payment domain naturally separates: Payments, Orders,
Ledger, Reconciliation, Fraud.
• Avoid shared databases across services; integrate via APIs/events.
• Each service owns its data and publishes events when state changes.
• Keep APIs backward compatible; treat them as contracts.

11.2 Sync vs async communication

• Sync (HTTP): easier but can cause cascading failures if dependencies slow down.
• Async (events): decouples; supports retries; introduces eventual consistency.
• Hybrid: commands sync, events async.

11.3 Contracts and compatibility

• Additive changes are safest (add new optional fields).


• Never change meaning of an existing field silently.
• Use consumer-driven contract tests to prevent breaking changes.
12. Resilience + Observability

12.1 Timeouts, retries, backoff + jitter

Without timeouts, your service can hang. Without bounded retries, your service can amplify failures (retry
storms).
• Set connect + read timeouts on every outbound call.
• Retry only transient failures (timeouts, 503).
• Use exponential backoff + jitter; cap retries.
• Use circuit breaker to fail fast when dependency is unhealthy.

class Backoff {
static long ms(int attempt){
long base = (long) [Link](2, attempt) * 100L;
long jitter = (long)([Link]() * 50L);
return [Link](base + jitter, 2000L);
}
}

12.2 Bulkheads and isolation

• Isolate dependencies into separate thread pools or semaphores.


• Bound queues; when full, reject fast rather than kill the service.
• Protect request threads from blocking.

12.3 Observability: logs, metrics, traces

• Structured logs with paymentId/orderId/correlationId.


• Metrics: latency percentiles, error rate, retries, timeouts, queue depth, DB pool usage.
• Tracing: propagate trace headers; measure end-to-end latency.
13. Data Consistency in Distributed Systems

13.1 ACID recap and why it matters in payments

• Atomicity: payment + ledger entry commit together (in one service DB).
• Consistency: invariants hold (no double refund).
• Isolation: concurrent updates don’t corrupt balances.
• Durability: once committed, data persists.

13.2 Isolation levels with real anomalies

• Read Committed: prevents dirty reads, but non-repeatable reads possible.


• Repeatable Read: stable reads in transaction; phantoms depend on DB.
• Serializable: strongest; may reduce throughput.

13.3 ACID vs BASE

In microservices, you usually keep local ACID but accept global BASE (eventual consistency). You
compensate instead of doing distributed 2PC.

13.4 Transactional Outbox pattern

To reliably publish events when DB transaction commits, store events in an outbox table in the same
transaction and publish asynchronously.
-- In the same DB transaction:
INSERT INTO payments(...);
INSERT INTO outbox(event_id, event_type, payload_json, created_at) VALUES (...);

-- A relay publishes outbox rows to MQ and marks them as sent.


14. Saga Orchestration for Payments

14.1 Choreography vs orchestration

• Choreography: services react to events; no central controller; can become hard to reason about.
• Orchestration: a coordinator commands steps and triggers compensations; clearer for complex flows like
payments.

14.2 Why payments need Saga thinking

• Gateways time out; callbacks duplicate; outcomes can be unknown.


• Order reservation, payment authorization, and confirmation must be coordinated.
• Compensation must be best-effort and idempotent.

14.3 Orchestrator skeleton with compensations


class PaymentSagaOrchestrator {

public SagaResult run(CreatePaymentCommand cmd) {


SagaContext ctx = new SagaContext([Link](), [Link]());
try {
reserveOrder(ctx); // command to Order service
authorize(ctx); // call gateway
confirmOrder(ctx); // command to Order service
return [Link](ctx);
} catch (Exception ex) {
compensate(ctx); // reverse best-effort
return [Link](ctx, ex);
}
}

private void reserveOrder(SagaContext ctx) {}


private void authorize(SagaContext ctx) {}
private void confirmOrder(SagaContext ctx) {}

private void compensate(SagaContext ctx) {


try { cancelOrder(ctx); } catch(Exception ignored) {}
try { voidAuthorization(ctx); } catch(Exception ignored) {}
try { releaseReservation(ctx); } catch(Exception ignored) {}
}

private void cancelOrder(SagaContext ctx) {}


private void voidAuthorization(SagaContext ctx) {}
private void releaseReservation(SagaContext ctx) {}
}
14.4 Unknown outcomes and reconciliation

If you timed out calling the gateway, you do not know whether it authorized or not. Treat the state as
PENDING, store gateway correlation data, and reconcile later by querying gateway or processing settlement
files.

Checkpoint: Explain why “fail fast” is not always correct for payments; sometimes “pending + reconcile” is safer.
15. Security for APIs + Payment Systems

15.1 Authentication vs authorization

• AuthN: who you are (JWT/OAuth2).


• AuthZ: what you can do (roles/scopes/policies).
• Service-to-service authentication matters too.

15.2 JWT basics (what to say in interviews)

• JWT is base64 [Link]; signature prevents tampering.


• Always validate: signature, issuer, audience, expiry, not-before, clock skew.
• Never store secrets in JWT; assume claims are readable.

15.3 Payment security mindset (PCI-style thinking)

• Minimize sensitive card data exposure; prefer tokenization.


• Never log PAN/CVV; redact identifiers where needed.
• Encrypt in transit (TLS) and at rest; rotate keys; least privilege.
• Audit access and state changes; keep immutable trails.
16. Testing Strategy

16.1 Unit tests: fast and deterministic

• Test domain invariants and state transitions.


• Mock external gateways and DB repositories.
• Use test data builders for readability.

16.2 Integration tests

• Test REST endpoints with real serialization and validation.


• Test repository with real DB (Testcontainers if available).
• Verify transactional behavior and constraints.

16.3 Contract tests

• Protect API compatibility across microservices.


• Run contracts in CI before deploy.

16.4 Performance tests

• Measure p95/p99 latency; track GC pauses; monitor thread pools and DB pools.
• Test failure modes: slow gateway, gateway down, DB latency spikes.
17. System Design Walkthrough: Payment Service
End-to-End

17.1 Start with requirements

• Correctness: no double charges, no double refunds.


• Reliability: handle timeouts, retries, unknown outcomes.
• Auditability: immutable event history and traceability.
• Security: protect sensitive data; secure APIs.

17.2 State machine design


enum PaymentState { CREATED, AUTHORIZED, CAPTURED, SETTLED, DECLINED, CANCELED, REFUNDED, CHARGEBACK }

class Payment {
private PaymentState state = [Link];

void authorizeApproved() {
if (state != [Link]) throw new IllegalStateException("bad transition");
state = [Link];
}
void capture() {
if (state != [Link]) throw new IllegalStateException("must authorize first");
state = [Link];
}
void refund() {
if (state != [Link] && state != [Link]) throw new IllegalStateException("n
state = [Link];
}
}

17.3 Data model and constraints

• payments: payment_id (PK), order_id, amount, currency, state, gateway_ref, idempotency_key,


created_at, updated_at
• unique(client_id, idempotency_key): prevents duplicates on retries.
• payment_events (append-only): every state change with timestamp and payload for audit.

17.4 End-to-end flow (explain on whiteboard)

1 Client calls POST /payments with Idempotency-Key.


2 Service validates request; checks idempotency store; returns cached response if exists.
3 Creates payment intent (CREATED) and stores.
4 Calls gateway authorize with timeout; if approved → state AUTHORIZED; if declined → DECLINED; if
timeout → PENDING/UNKNOWN.
5 Publishes domain event via outbox (PaymentAuthorized, PaymentDeclined, PaymentPending).
6 Saga orchestrator coordinates with Order service (reserve/confirm) and triggers compensations on
failure.
7 Reconciliation job processes pending payments, duplicates, settlement reports, chargebacks.
18. Final Interview Drills
Use these prompts to practice speaking clearly in-person. Your goal is to explain with structure: definition
→ example → trade-off → production considerations.

18.1 80 drill prompts

• 1. Explain HashMap internals including collisions and resize. (give a concrete example and 2 trade-offs).
• 2. Explain ConcurrentHashMap and why it forbids nulls. (give a concrete example and 2 trade-offs).
• 3. Explain volatile vs synchronized with a bug example. (give a concrete example and 2 trade-offs).
• 4. Explain happens-before and why visibility matters. (give a concrete example and 2 trade-offs).
• 5. Design an idempotent POST endpoint for payments. (give a concrete example and 2 trade-offs).
• 6. Explain ACID isolation levels using refund/double-spend examples. (give a concrete example and 2
trade-offs).
• 7. Explain Outbox pattern and why it exists. (give a concrete example and 2 trade-offs).
• 8. Design saga orchestration with compensations. (give a concrete example and 2 trade-offs).
• 9. Explain unknown outcomes with payment gateways and reconciliation. (give a concrete example and 2
trade-offs).
• 10. Explain GC pauses and how they affect latency. (give a concrete example and 2 trade-offs).
• 11. Explain Java memory leak patterns and how to diagnose them. (give a concrete example and 2
trade-offs).
• 12. Explain why constructor injection improves testability. (give a concrete example and 2 trade-offs).
• 13. Explain @Transactional limitations in microservices. (give a concrete example and 2 trade-offs).
• 14. Explain retries with backoff+jitter and retry storms. (give a concrete example and 2 trade-offs).
• 15. Explain circuit breaker behavior and tuning. (give a concrete example and 2 trade-offs).
• 16. Explain API error modeling and consistent error codes. (give a concrete example and 2 trade-offs).
• 17. Explain secure API design for payments. (give a concrete example and 2 trade-offs).
• 18. Explain the payment lifecycle: auth, capture, settlement, reconciliation, chargebacks. (give a concrete
example and 2 trade-offs).
• 19. Explain HashMap internals including collisions and resize. (give a concrete example and 2
trade-offs).
• 20. Explain ConcurrentHashMap and why it forbids nulls. (give a concrete example and 2 trade-offs).
• 21. Explain volatile vs synchronized with a bug example. (give a concrete example and 2 trade-offs).
• 22. Explain happens-before and why visibility matters. (give a concrete example and 2 trade-offs).
• 23. Design an idempotent POST endpoint for payments. (give a concrete example and 2 trade-offs).
• 24. Explain ACID isolation levels using refund/double-spend examples. (give a concrete example and 2
trade-offs).
• 25. Explain Outbox pattern and why it exists. (give a concrete example and 2 trade-offs).
• 26. Design saga orchestration with compensations. (give a concrete example and 2 trade-offs).
• 27. Explain unknown outcomes with payment gateways and reconciliation. (give a concrete example and
2 trade-offs).
• 28. Explain GC pauses and how they affect latency. (give a concrete example and 2 trade-offs).
• 29. Explain Java memory leak patterns and how to diagnose them. (give a concrete example and 2
trade-offs).
• 30. Explain why constructor injection improves testability. (give a concrete example and 2 trade-offs).
• 31. Explain @Transactional limitations in microservices. (give a concrete example and 2 trade-offs).
• 32. Explain retries with backoff+jitter and retry storms. (give a concrete example and 2 trade-offs).
• 33. Explain circuit breaker behavior and tuning. (give a concrete example and 2 trade-offs).
• 34. Explain API error modeling and consistent error codes. (give a concrete example and 2 trade-offs).
• 35. Explain secure API design for payments. (give a concrete example and 2 trade-offs).
• 36. Explain the payment lifecycle: auth, capture, settlement, reconciliation, chargebacks. (give a concrete
example and 2 trade-offs).
• 37. Explain HashMap internals including collisions and resize. (give a concrete example and 2
trade-offs).
• 38. Explain ConcurrentHashMap and why it forbids nulls. (give a concrete example and 2 trade-offs).
• 39. Explain volatile vs synchronized with a bug example. (give a concrete example and 2 trade-offs).
• 40. Explain happens-before and why visibility matters. (give a concrete example and 2 trade-offs).
• 41. Design an idempotent POST endpoint for payments. (give a concrete example and 2 trade-offs).
• 42. Explain ACID isolation levels using refund/double-spend examples. (give a concrete example and 2
trade-offs).
• 43. Explain Outbox pattern and why it exists. (give a concrete example and 2 trade-offs).
• 44. Design saga orchestration with compensations. (give a concrete example and 2 trade-offs).
• 45. Explain unknown outcomes with payment gateways and reconciliation. (give a concrete example and
2 trade-offs).
• 46. Explain GC pauses and how they affect latency. (give a concrete example and 2 trade-offs).
• 47. Explain Java memory leak patterns and how to diagnose them. (give a concrete example and 2
trade-offs).
• 48. Explain why constructor injection improves testability. (give a concrete example and 2 trade-offs).
• 49. Explain @Transactional limitations in microservices. (give a concrete example and 2 trade-offs).
• 50. Explain retries with backoff+jitter and retry storms. (give a concrete example and 2 trade-offs).
• 51. Explain circuit breaker behavior and tuning. (give a concrete example and 2 trade-offs).
• 52. Explain API error modeling and consistent error codes. (give a concrete example and 2 trade-offs).
• 53. Explain secure API design for payments. (give a concrete example and 2 trade-offs).
• 54. Explain the payment lifecycle: auth, capture, settlement, reconciliation, chargebacks. (give a concrete
example and 2 trade-offs).
• 55. Explain HashMap internals including collisions and resize. (give a concrete example and 2
trade-offs).
• 56. Explain ConcurrentHashMap and why it forbids nulls. (give a concrete example and 2 trade-offs).
• 57. Explain volatile vs synchronized with a bug example. (give a concrete example and 2 trade-offs).
• 58. Explain happens-before and why visibility matters. (give a concrete example and 2 trade-offs).
• 59. Design an idempotent POST endpoint for payments. (give a concrete example and 2 trade-offs).
• 60. Explain ACID isolation levels using refund/double-spend examples. (give a concrete example and 2
trade-offs).
• 61. Explain Outbox pattern and why it exists. (give a concrete example and 2 trade-offs).
• 62. Design saga orchestration with compensations. (give a concrete example and 2 trade-offs).
• 63. Explain unknown outcomes with payment gateways and reconciliation. (give a concrete example and
2 trade-offs).
• 64. Explain GC pauses and how they affect latency. (give a concrete example and 2 trade-offs).
• 65. Explain Java memory leak patterns and how to diagnose them. (give a concrete example and 2
trade-offs).
• 66. Explain why constructor injection improves testability. (give a concrete example and 2 trade-offs).
• 67. Explain @Transactional limitations in microservices. (give a concrete example and 2 trade-offs).
• 68. Explain retries with backoff+jitter and retry storms. (give a concrete example and 2 trade-offs).
• 69. Explain circuit breaker behavior and tuning. (give a concrete example and 2 trade-offs).
• 70. Explain API error modeling and consistent error codes. (give a concrete example and 2 trade-offs).
• 71. Explain secure API design for payments. (give a concrete example and 2 trade-offs).
• 72. Explain the payment lifecycle: auth, capture, settlement, reconciliation, chargebacks. (give a concrete
example and 2 trade-offs).
• 73. Explain HashMap internals including collisions and resize. (give a concrete example and 2
trade-offs).
• 74. Explain ConcurrentHashMap and why it forbids nulls. (give a concrete example and 2 trade-offs).
• 75. Explain volatile vs synchronized with a bug example. (give a concrete example and 2 trade-offs).
• 76. Explain happens-before and why visibility matters. (give a concrete example and 2 trade-offs).
• 77. Design an idempotent POST endpoint for payments. (give a concrete example and 2 trade-offs).
• 78. Explain ACID isolation levels using refund/double-spend examples. (give a concrete example and 2
trade-offs).
• 79. Explain Outbox pattern and why it exists. (give a concrete example and 2 trade-offs).
• 80. Design saga orchestration with compensations. (give a concrete example and 2 trade-offs).

You might also like