Zum Hauptinhalt springen

English + German

English stays on the page. Click the button to show the German text under each question.

Architecture and Production

Senior interviewers stop asking “what is a bean” and start asking what happens when it fails. Speak in APIs, data, consistency, and operations — not in framework trivia.

Deutsch

Architektur und Produktion

Senior-Interviewer hören auf mit „was ist ein Bean“ und fragen was passiert, wenn es fehlschlägt. Sprich in APIs, Daten, Konsistenz und Betrieb — nicht in Framework-Trivia.

1. How do you structure a Spring Boot backend?

Deutsch

Wie strukturierst du ein Spring-Boot-Backend?

Level: Mid · Listen for: use-case boundary; DTO vs entity; transaction at the service

Niveau: Mid · Darauf hören sie: Use-Case-Grenze; DTO vs. Entity; Transaktion am Service

Model answer

I keep a use case as the unit: controller (HTTP) → application service (transaction, invariants) → domain + repositories. DTOs cross the HTTP boundary. Entities do not.

Package by feature when the app is more than a tutorial. Shared kernels stay small. I avoid a generic Utils dumping ground and a circular service package of 80 classes.

Hexagonal/ports-and-adapters is a good fit when I have more than one adapter (HTTP + messaging + CLI). I do not start there for a CRUD module with one database.

Follow-ups

  • Where does mapping live?
  • Modulith vs microservices for a 6-person team?

Trap: copying Netflix’s microservice chart for a single product.

Memory sentence: One use case, one transaction, DTOs at the edge.

Musterantwort

Der Use Case ist die Einheit: Controller (HTTP) → Application Service (Transaktion, Invarianten) → Domain + Repositories. DTOs überschreiten die HTTP-Grenze. Entities nicht.

Packages schneide ich by feature, sobald die App mehr als ein Tutorial ist. Shared Kernels bleiben klein. Ich vermeide eine generische Utils-Halde und ein zyklisches service-Package mit 80 Klassen.

Hexagonal/Ports-and-Adapters passt, wenn ich mehr als einen Adapter habe (HTTP + Messaging + CLI). Für ein CRUD-Modul mit einer Datenbank fange ich damit nicht an.

Nachfragen

  • Wo lebt das Mapping?
  • Modulith vs. Microservices für ein 6-Personen-Team?

Falle: das Netflix-Microservice-Chart für ein einzelnes Produkt kopieren.

Merksatz: Ein Use Case, eine Transaktion, DTOs an der Kante.

2. When do you split a monolith?

Deutsch

Wann teilst du einen Monolithen auf?

Level: Senior · Listen for: team/bounded context, not “for scale”

Niveau: Senior · Darauf hören sie: Team/Bounded Context, nicht „wegen Scale“

Model answer

I split when a bounded context and a team need an independent release cycle, or when a part of the system has a truly different scale/storage (search, billing). I do not split because a tutorial said “microservices”.

A modular monolith (packages or Gradle modules, no circular deps, clear APIs) gets most of the design benefit with one deploy. Distributed transactions, ops, and local testing get worse the day you split.

If I split, the first cut is along data ownership, not along layers (user-service that every other service calls for every field is a distributed monolith).

Follow-ups

  • How do you share a database “temporarily”?
  • What is the first service you would extract from an online shop?

Trap: “we will split later” while entities join across future boundaries.

Memory sentence: Split on bounded contexts and data ownership, not on class count.

Musterantwort

Ich teile auf, wenn ein Bounded Context und ein Team einen eigenen Release-Zyklus brauchen, oder wenn ein Teil des Systems wirklich anderes Scale/Storage hat (Search, Billing). Ich teile nicht auf, weil ein Tutorial „Microservices“ gesagt hat.

Ein modularer Monolith (Packages oder Gradle-Module, keine zyklischen Deps, klare APIs) holt den Großteil des Design-Gewinns mit einem Deploy. Distributed Transactions, Ops und lokales Testen werden schlechter an dem Tag, an dem du aufteilst.

Wenn ich aufteile, geht der erste Schnitt entlang Data Ownership, nicht entlang der Schichten (user-service, den jeder andere Service für jedes Feld aufruft, ist ein verteilter Monolith).

Nachfragen

  • Wie nutzt ihr eine Datenbank „vorübergehend“ gemeinsam?
  • Welchen Service würdest du als Erstes aus einem Online-Shop extrahieren?

Falle: „wir teilen später auf“, während Entities über künftige Grenzen joinen.

Merksatz: Aufteilen nach Bounded Contexts und Data Ownership, nicht nach Klassenzahl.

3. REST vs messaging for service-to-service calls?

Deutsch

REST vs. Messaging für Calls zwischen Services?

Level: Senior · Listen for: coupling in time; failure modes

Niveau: Senior · Darauf hören sie: Kopplung in der Zeit; Failure Modes

Model answer

REST/HTTP is for request/response when the caller needs the answer now (read a price, submit a form). It couples availability: if B is down, A fails.

Messaging (Kafka, Rabbit, SQS) is for facts that already happened (“OrderPlaced”). The consumer can lag, retry, and scale independently. You accept eventual consistency.

I do not replace every HTTP call with Kafka. I do not do “REST and hope” for a process that must not lose money. Payments and stock usually want an explicit workflow (outbox, saga, or a single service that owns the invariant).

Follow-ups

  • Sync HTTP with retries vs idempotent consumers?
  • When is gRPC the better RPC?

Trap: a queue for a user-facing GET.

Memory sentence: HTTP for answers now; events for facts that already happened.

Musterantwort

REST/HTTP ist für Request/Response, wenn der Caller die Antwort jetzt braucht (einen Preis lesen, ein Formular absenden). Es koppelt Verfügbarkeit: ist B down, fällt A aus.

Messaging (Kafka, Rabbit, SQS) ist für Fakten, die schon passiert sind („OrderPlaced“). Der Consumer darf hinterherhinken, retrien und unabhängig skalieren. Du akzeptierst eventual consistency.

Ich ersetze nicht jeden HTTP-Call durch Kafka. Ich mache kein „REST und hoffen“ für einen Prozess, der kein Geld verlieren darf. Payments und Stock wollen meist einen expliziten Workflow (outbox, saga, oder ein einzelner Service, der die Invariante besitzt).

Nachfragen

  • Sync-HTTP mit Retries vs. idempotente Consumer?
  • Wann ist gRPC das bessere RPC?

Falle: eine Queue für ein GET, auf das der User wartet.

Merksatz: HTTP für Antworten jetzt; Events für Fakten, die schon passiert sind.

4. How do you keep a write-and-publish consistent? (outbox)

Deutsch

Wie hältst du Schreiben und Publishen konsistent? (outbox)

Level: Senior · Listen for: dual write problem; outbox + poller / CDC

Niveau: Senior · Darauf hören sie: Dual-Write-Problem; outbox + Poller / CDC

Model answer

If you INSERT an order and then kafka.send, either can fail: committed order without event, or event without row.

The outbox pattern: in the same database transaction, write the business row and an outbox row. A publisher (poller or CDC like Debezium) reads outbox rows and publishes. Consumers are idempotent.

I do not start a distributed XA transaction between Kafka and Postgres as the default design.

Follow-ups

  • Idempotency key on the consumer?
  • Ordering per aggregate id?

Trap: @TransactionalEventListener as if it published to other services. That is in-process.

Memory sentence: One DB transaction for state + outbox; the broker is downstream.

Musterantwort

Wenn du eine Order per INSERT schreibst und danach kafka.send aufrufst, kann beides fehlschlagen: Order ist committed und das Event fehlt — oder das Event ist da und die Row fehlt.

Das outbox-Pattern: in derselben Datenbank-Transaktion die Business-Row und eine outbox-Row schreiben. Ein Publisher (Poller oder CDC wie Debezium) liest outbox-Rows und publisht. Consumer sind idempotent.

Ich starte keine verteilte XA-Transaktion zwischen Kafka und Postgres als Default.

Nachfragen

  • Idempotency Key am Consumer?
  • Reihenfolge pro Aggregate-Id?

Falle: @TransactionalEventListener, als würde es an andere Services publishen. Das ist in-process.

Merksatz: Eine DB-Transaktion für State + outbox; der Broker ist downstream.

5. Caching: where, and what can go wrong?

Deutsch

Caching: wo, und was kann schiefgehen?

Level: Mid · Listen for: cache-aside; TTL; stampede; key design; consistency

Niveau: Mid · Darauf hören sie: cache-aside; TTL; Stampede; Key-Design; Konsistenz

Model answer

I cache read-heavy, tolerance-for-stale data: product catalog, config, authorization metadata. I do not cache bank balances without a story.

Cache-aside (@Cacheable): miss → DB → put. Writes evict (@CacheEvict) or update. Keys must include everything that changes the result (tenant, locale, user).

Problems: stampede (many misses at TTL expiry — use locking/singleflight or Caffeine/Redis features), stamp of stale data after writes, caching user-specific data under a global key, caching JPA entities (lazy proxies, identity).

I cache DTOs or values, not Hibernate entities.

Follow-ups

  • Redis vs local Caffeine?
  • How do you cache a 404? (negative caching, short TTL)

Trap: @Cacheable on a method with a mutable entity return type.

Memory sentence: Cache values, evict on write, and name the stale window.

Musterantwort

Ich cache read-heavy Daten mit Toleranz für Stale: Produktkatalog, Config, Authorization-Metadaten. Kontostände cache ich nicht ohne eine Begründung.

Cache-aside (@Cacheable): Miss → DB → Put. Writes evicten (@CacheEvict) oder updaten. Keys müssen alles enthalten, was das Ergebnis ändert (Tenant, Locale, User).

Probleme: Stampede (viele Misses beim TTL-Ablauf — Locking/Singleflight oder Caffeine-/Redis-Features), Stamp von stale Data nach Writes (alte Werte bleiben liegen), user-spezifische Daten unter einem globalen Key cachen, JPA-Entities cachen (Lazy Proxies, Identity).

Ich cache DTOs oder Values, keine Hibernate-Entities.

Nachfragen

  • Redis vs. lokales Caffeine?
  • Wie cachest du eine 404? (Negative Caching, kurze TTL)

Falle: @Cacheable auf einer Methode mit mutable Entity als Rückgabetyp.

Merksatz: Values cachen, bei Write evicten, und das Stale-Fenster benennen.

6. How do you design pagination and filtering as an API?

Deutsch

Wie designst du Pagination und Filterung als API?

Level: Mid · Listen for: stable cursors for large data; max limits

Niveau: Mid · Darauf hören sie: stabile Cursors bei großen Daten; Max-Limits

Model answer

Public APIs get:

  • a max page size
  • a default sort that is deterministic
  • filters that map to indexed columns
  • for large/real-time feeds, cursor/keyset pagination (?after=id) instead of page=5000

I document 400 for unknown sort fields instead of mapping them to SQL (ORDER BY ${field} is an injection and a full-table sort).

Follow-ups

  • Total count cost?
  • Filtering on JSONB columns?

Trap: GET /search with a JSON body.

Memory sentence: Bound size, stable order, indexes behind every filter.

Musterantwort

Öffentliche APIs bekommen:

  • eine maximale page size
  • einen Default-Sort, der deterministisch ist
  • Filter, die auf indexierte Spalten mappen
  • bei großen/Echtzeit-Feeds Cursor-/Keyset-Pagination (?after=id) statt page=5000

Für unbekannte Sort-Felder dokumentiere ich 400, statt sie nach SQL zu mappen (ORDER BY ${field} ist Injection und ein Full-Table-Sort).

Nachfragen

  • Kosten eines Total Count?
  • Filtern auf JSONB-Spalten?

Falle: GET /search mit JSON-Body.

Merksatz: Größe begrenzen, stabile Ordnung, hinter jedem Filter ein Index.

7. What indexes would you add? How do you explain EXPLAIN?

Deutsch

Welche Indexes würdest du setzen? Wie erklärst du EXPLAIN?

Level: Mid · Listen for: selectivity; covering vs lookup; write cost

Niveau: Mid · Darauf hören sie: Selectivity; Covering vs. Lookup; Write-Kosten

Model answer

I index columns used in WHERE/JOIN/ORDER that actually filter. Equality columns first in a composite index, then range. I do not index every column.

EXPLAIN (ANALYZE, BUFFERS) shows seq scan vs index scan, row estimates, and sort. I look for seq scans on large tables, nested loops with huge inner counts (N+1 at the SQL level), and sorts that spill to disk.

Every index slows writes and uses space. Unused indexes should die.

Follow-ups

  • Why did the DB ignore your index? (type mismatch, function on column, low selectivity)
  • Partial indexes?

Trap: adding 12 indexes because the API has 12 filters, without measuring.

Memory sentence: Index for the queries you run; prove it with EXPLAIN ANALYZE.

Musterantwort

Ich indexiere Spalten in WHERE/JOIN/ORDER, die wirklich filtern. Equality-Spalten zuerst im Composite Index, dann Range. Ich indexiere nicht jede Spalte.

EXPLAIN (ANALYZE, BUFFERS) zeigt Seq Scan vs. Index Scan, Row Estimates und Sort. Ich suche Seq Scans auf großen Tabellen, Nested Loops mit riesigen Inner Counts (N+1 auf SQL-Ebene) und Sorts, die auf Disk spillen.

Jeder Index bremst Writes und kostet Platz. Ungenutzte Indexes sollen sterben.

Nachfragen

  • Warum hat die DB deinen Index ignoriert? (Type Mismatch, Funktion auf der Spalte, niedrige Selectivity)
  • Partial Indexes?

Falle: 12 Indexes setzen, weil die API 12 Filter hat — ohne zu messen.

Merksatz: Indexiere für die Queries, die du fährst; beweise es mit EXPLAIN ANALYZE.

8. Resilience: timeouts, retries, circuit breakers?

Deutsch

Resilience: Timeouts, Retries, Circuit Breakers?

Level: Senior · Listen for: retry only what is safe; bulkhead; fail fast

Niveau: Senior · Darauf hören sie: nur sicheres retrien; bulkhead; fail fast

Model answer

Every remote call has a timeout. Without one, threads pile up and the whole app dies.

Retries are for idempotent calls (GET, PUT with idempotency key). Retrying POST without a key doubles charges. Exponential backoff + jitter.

A circuit breaker stops calling a dead dependency so you can fail fast and keep other features up. A bulkhead limits the pool of threads/connections per dependency so one integration cannot eat Tomcat.

I use Resilience4j (or equivalent), not infinite catch + retry in a loop.

Follow-ups

  • Retry-storm on recovery?
  • Hedged requests?

Trap: retry-everything interceptor on RestClient.

Memory sentence: Time out, retry only idempotent work, isolate each dependency.

Musterantwort

Jeder Remote Call hat einen Timeout. Ohne den stauen sich Threads, und die ganze App stirbt.

Retries sind für idempotente Calls (GET, PUT mit Idempotency Key). POST ohne Key zu retrien verdoppelt Abbuchungen. Exponential Backoff + Jitter.

Ein circuit breaker hört auf, eine tote Dependency aufzurufen — fail fast, andere Features bleiben oben. Ein bulkhead begrenzt den Pool aus Threads/Connections pro Dependency, damit eine Integration Tomcat nicht auffrisst.

Ich nutze Resilience4j (oder ein Äquivalent), nicht endlos catch + Retry in einer Schleife.

Nachfragen

  • Retry-Storm, wenn die Dependency wieder kommt?
  • Hedged Requests?

Falle: ein Interceptor auf RestClient, der alles retried.

Merksatz: Timeout setzen, nur idempotente Arbeit retrien, jede Dependency isolieren.

9. Observability: logs, metrics, traces?

Deutsch

Observability: Logs, Metrics, Traces?

Level: Mid · Listen for: correlation id; RED/USE; not “we have ELK”

Niveau: Mid · Darauf hören sie: Correlation Id; RED/USE; nicht „wir haben ELK“

Model answer

Three signals:

  • Logs — events with a correlation / trace id, structured JSON, no PII/secrets
  • Metrics — RED (rate, errors, duration) per endpoint; pool usage (Hikari, Tomcat); JVM
  • Traces — one request across services; spans for HTTP and SQL

Actuator + Micrometer is the Boot native path. I would rather have 10 good metrics and traces than 400 dashboards nobody uses.

On-call: start from a trace of a slow request, then SQL, then code — not from a guess.

Follow-ups

  • Cardinality explosion on metrics labels (userId as a tag)?
  • Sampling traces in prod?

Trap: logging full request bodies in prod.

Memory sentence: Correlate logs, metrics, and traces; measure the request, not the machine only.

Study: Actuator

Musterantwort

Drei Signale:

  • Logs — Events mit Correlation / Trace Id, strukturiertes JSON, keine PII/Secrets
  • Metrics — RED (Rate, Errors, Duration) pro Endpoint; Pool-Auslastung (Hikari, Tomcat); JVM
  • Traces — ein Request über Services hinweg; Spans für HTTP und SQL

Actuator + Micrometer ist der Weg, den Boot mitbringt. Ich hätte lieber 10 gute Metrics und Traces als 400 Dashboards, die niemand nutzt.

Im On-Call starte ich beim Trace eines langsamen Requests, dann SQL, dann Code — nicht beim Raten.

Nachfragen

  • Cardinality Explosion bei Metrics-Labels (userId als Tag)?
  • Traces in Prod samplen?

Falle: volle Request Bodies in Prod loggen.

Merksatz: Logs, Metrics und Traces korrelieren; den Request messen, nicht nur die Maschine.

Lesen: Actuator

10. Health probes in Kubernetes?

Deutsch

Health Probes in Kubernetes?

Level: Mid · Listen for: liveness vs readiness vs startup

Niveau: Mid · Darauf hören sie: liveness vs. readiness vs. startup

Model answer
  • Liveness: process is deadlocked or stuck — Kubernetes restarts it. Do not include the database. A DB blip should not kill every pod in a loop.
  • Readiness: this instance can take traffic — include DB/broker checks you truly need. Fail readiness to drop from the load balancer.
  • Startup: slow Boot apps get a longer window so liveness does not kill them while they are still loading.

Actuator health groups map to these endpoints. I keep liveness cheap.

Follow-ups

  • What if the DB is down globally — should readiness fail? (yes; liveness no)
  • External HTTP dependency in readiness?

Trap: one /health that restarts pods whenever Postgres hiccups.

Memory sentence: Liveness is “am I alive?”; readiness is “should I get traffic?”

Study: Actuator

Musterantwort

  • Liveness: Prozess ist deadlocked oder hängt — Kubernetes startet ihn neu. Die Datenbank nicht reinnehmen. Ein kurzer DB-Aussetzer soll nicht jeden Pod in einer Schleife killen.
  • Readiness: diese Instanz kann Traffic annehmen — DB-/Broker-Checks dazu, die du wirklich brauchst. Readiness failen, um aus dem Load Balancer zu fliegen.
  • Startup: langsame Boot-Apps bekommen ein längeres Fenster, damit Liveness sie nicht killt, während sie noch laden.

Actuator Health Groups mappen auf diese Endpoints. Liveness bleibt billig.

Nachfragen

  • Was, wenn die DB global down ist — soll Readiness failen? (ja; Liveness nein)
  • Externe HTTP-Dependency in Readiness?

Falle: ein /health, das Pods neu startet, sobald Postgres kurz aussetzt.

Merksatz: Liveness ist „lebe ich?“; Readiness ist „soll ich Traffic bekommen?“

Lesen: Actuator

11. Secrets and config in production?

Deutsch

Secrets und Config in Produktion?

Level: Mid · Listen for: env / secret store; not git; 12-factor

Niveau: Mid · Darauf hören sie: Env / Secret Store; nicht Git; 12-Factor

Model answer

Config is environment: non-secret in application.yml, secrets in the platform (Kubernetes Secrets, Vault, cloud secret manager) injected as env or files. Boot relaxed binding maps them.

I never commit credentials, and I never log Environment dumps in prod. Rotate keys. Different credentials per environment.

spring.profiles.active=prod selects behavior; it is not the secret store.

Follow-ups

  • Config server vs K8s native config?
  • Encryption at rest vs in transit?

Trap: a application-prod.yml in the repo with a “temporary” password.

Memory sentence: Code in git; secrets in the platform.

Musterantwort

Config gehört in die Environment: nicht-geheime Werte in application.yml, Secrets in der Plattform (Kubernetes Secrets, Vault, Cloud Secret Manager), injiziert als Env oder Dateien. Spring Boot mappt sie per Relaxed Binding.

Ich committe nie Credentials und logge nie Environment-Dumps in Prod. Keys rotieren. Pro Environment andere Credentials.

spring.profiles.active=prod wählt Verhalten; es ist nicht der Secret Store.

Nachfragen

  • Config Server vs. K8s-native Config?
  • Encryption at rest vs. in transit?

Falle: eine application-prod.yml im Repo mit einem „temporären“ Passwort.

Merksatz: Code in Git; Secrets in der Plattform.

12. How would you design an Order service? (10-minute sketch)

Deutsch

Wie würdest du einen Order-Service designen? (10-Minuten-Skizze)

Level: Senior · Listen for: aggregate, idempotency, stock, payment, failure

Niveau: Senior · Darauf hören sie: Aggregate, Idempotency, Stock, Payment, Failure

Model answer

I talk through this sketch, not a class diagram:

  1. API: POST /orders with an Idempotency-Key. 201 + Location.
  2. Aggregate: Order (status: PENDING → PAID → FULFILLED | CANCELLED) owned by this service.
  3. Transaction: create order lines, reserve stock in this DB (or via a stock service with a reservation API). Short transaction. No HTTP to the payment provider inside it.
  4. Payment: after commit, call provider (or emit OrderPlaced). On webhook PaymentSucceeded, a separate idempotent handler marks PAID.
  5. Failure: payment fails → cancel reservation. Provider timeout → reconcile job. Duplicate POST → same order id.
  6. Read model: GET /orders/{id} from the DB; list endpoint paginated.
  7. Observability: metrics on status transitions; trace id on webhook.

I mention what I am not doing: two-phase commit across payment and stock, returning entities, @Async as the reliability layer.

Follow-ups

  • Exactly-once vs at-least-once webhooks?
  • How do you cancel after PAID?

Trap: a 20-box microservice drawing before the HTTP contract.

Memory sentence: Idempotent API, short transactions, async money, explicit states.

Musterantwort

Ich gehe diese Skizze durch, kein Klassendiagramm:

  1. API: POST /orders mit Idempotency-Key. 201 + Location.
  2. Aggregate: Order (Status: PENDING → PAID → FULFILLED | CANCELLED), gehört diesem Service.
  3. Transaktion: Order Lines anlegen, Stock in dieser DB reservieren (oder über einen Stock-Service mit Reservation-API). Kurze Transaktion. Kein HTTP zum Payment Provider darin.
  4. Payment: nach dem Commit den Provider aufrufen (oder OrderPlaced emittieren). Beim Webhook PaymentSucceeded markiert ein separater idempotenter Handler PAID.
  5. Failure: Payment schlägt fehl → Reservation stornieren. Provider-Timeout → Reconcile-Job. Doppeltes POST → dieselbe Order-Id.
  6. Read Model: GET /orders/{id} aus der DB; List-Endpoint paginiert.
  7. Observability: Metrics auf Statusübergängen; Trace Id am Webhook.

Ich sage, was ich nicht mache: Two-Phase Commit über Payment und Stock, Entities zurückgeben, @Async als Reliability-Schicht.

Nachfragen

  • Exactly-once vs. at-least-once Webhooks?
  • Wie stornierst du nach PAID?

Falle: eine 20-Kästchen-Microservice-Zeichnung vor dem HTTP-Vertrag.

Merksatz: Idempotente API, kurze Transaktionen, async Geld, explizite States.

13. CAP / consistency in a Spring service?

Deutsch

CAP / Konsistenz in einem Spring-Service?

Level: Senior · Listen for: you usually choose a database’s model; don’t recite CAP as a slogan

Niveau: Senior · Darauf hören sie: du wählst meist das Modell der Datenbank; CAP nicht als Slogan aufsagen

Model answer

A single Postgres service is strongly consistent for one row (with the isolation you chose). CAP shows up when you have replicas, multi-region, or multiple data stores.

If I add a cache or a search index, I have accepted lag. If I publish events, consumers are eventually consistent. I say that in the API: list endpoints may lag; GET by id after 201 should read the primary.

I do not “pick AP or CP” for a CRUD app on one database. I do name where I allowed lag.

Follow-ups

  • Read-your-writes after a write to the primary and a read from a replica?
  • PACELC?

Trap: “we are CP because we use Spring.”

Memory sentence: One database can be strict; every extra store is a consistency decision.

Musterantwort

Ein einzelner Postgres-Service ist strongly consistent für eine Row (mit der Isolation, die du gewählt hast). CAP taucht auf, wenn du Replicas, Multi-Region oder mehrere Data Stores hast.

Wenn ich einen Cache oder einen Search Index dazunehme, habe ich Lag akzeptiert. Wenn ich Events publishe, sind Consumer eventually consistent. Das sage ich in der API: List-Endpoints dürfen hinterherhinken; GET per Id nach 201 soll den Primary lesen.

Ich wähle nicht AP oder CP für eine CRUD-App auf einer Datenbank. Ich benenne, wo ich Lag zugelassen habe.

Nachfragen

  • Read-your-writes nach einem Write auf den Primary und einem Read von einer Replica?
  • PACELC?

Falle: „wir sind CP, weil wir Spring nutzen.“

Merksatz: Eine Datenbank kann streng sein; jeder zusätzliche Store ist eine Konsistenz-Entscheidung.

14. What do you look at in a PR for a Spring API?

Deutsch

Worauf schaust du in einem PR für eine Spring-API?

Level: Senior · Listen for: a review checklist, not style nits only

Niveau: Senior · Darauf hören sie: eine Review-Checkliste, nicht nur Style-Nits

Model answer

I read in this order:

  1. HTTP contract — statuses, DTO, validation, authz
  2. Transaction boundary — one use case, no remote I/O inside
  3. SQL — fetch plan, indexes, N+1, pagination
  4. Security — default deny, no entity over-posting, no leaked fields
  5. Tests — a unit test for the rule, a slice for HTTP/security
  6. Ops — metrics, timeouts, feature flags, migrations expand/contract

Nits (import order) last. If the transaction wraps an HTTP call, that is the review comment, not the missing JavaDoc.

Follow-ups

  • How do you review generated code / AI diffs?
  • When do you ask for an EXPLAIN in the PR?

Trap: approving because “the tests pass” when all tests are @SpringBootTest happy paths.

Memory sentence: Review the boundary, the transaction, and the SQL; style is last.

Musterantwort

Ich lese in dieser Reihenfolge:

  1. HTTP-Vertrag — Statuscodes, DTO, Validierung, Authz
  2. Transaktionsgrenze — ein Use Case, kein Remote-I/O drin
  3. SQL — Fetch Plan, Indexes, N+1, Pagination
  4. Security — Default Deny, kein Entity Over-Posting, keine geleakten Felder
  5. Tests — ein Unit Test für die Regel, ein Slice für HTTP/Security
  6. Ops — Metrics, Timeouts, Feature Flags, Migrations Expand/Contract

Nits (Import-Reihenfolge) zuletzt. Wenn die Transaktion einen HTTP-Call umschließt, ist das der Review-Kommentar — nicht das fehlende JavaDoc.

Nachfragen

  • Wie reviewst du generierten Code / AI-Diffs?
  • Wann verlangst du ein EXPLAIN im PR?

Falle: approven, weil „die Tests grün sind“, obwohl alle Tests @SpringBootTest-Happy-Paths sind.

Merksatz: Review die Grenze, die Transaktion und das SQL; Style kommt zuletzt.