Topic
Backend Engineering
Application services, frameworks, and implementation patterns for maintainable backend systems.
Before Fixing Circular Dependencies, Fix the Domain Model
Resolve confusing ownership and concept boundaries before using interfaces or dependency inversion to treat an apparent circular reference.
Nullable or Zero? Choosing a Kotlin JPA Entity ID Strategy
Compare nullable and zero-valued numeric IDs for Kotlin JPA entities, including new-entity detection, ambiguity, and verification.
One Project, Many Deployables: Drawing Boundaries by Runtime Role
Separate public, admin, batch, and operations workloads into runnable applications without splitting the whole codebase into separate projects too early.
Name Readers, Finders, and Searchers by Behavior
Distinguish Reader, Finder, and Searcher classes by direct reads, added filtering, and composite searches rather than result count.
Compose My Page Data Without Inventing a My Domain
Build My page summaries through a dedicated composition layer while keeping user, order, and product responsibilities in their proper domains.
Why a My Page Is Not a Domain
A My Page is a client-facing view, not a business domain. Keep orders, products, and coupons with their real owners and combine them at the boundary.
Designing Domain Models Beyond the Shape of the UI
A tree-shaped API does not require a tree-shaped domain. Keep the client contract at the presentation boundary and compose it from internal concepts.
Your First Test Can Be Messy—Refactor It Later
Start with the test that proves the behavior you care about, mock enough to make it run, and improve test design through repeated use and review.
Keep API Request Models Out of the Core Domain
Convert external request objects into business-owned values at the presentation boundary so the API depends inward and the core never depends back.
Foreign Keys Are an Operational Tradeoff, Not a Rule
Choose foreign keys, indexes, and ORM mappings from integrity needs, incident response, deployment practice, and who operates the database.
Give Cross-Domain Behavior to the Domain That Owns It
Before placing a caller-owned interface across modules, identify which domain owns the action and let callers depend on that capability.
Who Owns the Enum? Dependency Design Across Domain Modules
Put a business enum with its domain, let storage depend inward, and use a small shared enum module only while the domain boundary is still emerging.
Provider Identity and Authentication Boundaries for Platform APIs
How a small platform API resolves provider keys into a domain identity, separates tenant data, and chooses an authentication boundary that fits its scale.
Domain Models First: A Conservative Approach to JPA Associations
A cautious way to map JPA entities: start with IDs, add associations only when lifecycles truly align, and model business concepts independently.
You Can Practice High-Traffic Engineering Without Real Traffic
Build and operate a small service, create load deliberately, and show how you found and fixed bottlenecks without pretending a load test equals production experience.
Reliable Database Tests Without a Shared Test Database
Choose mocks, an in-memory database, Testcontainers, or a real database by the failure you need to catch, while keeping ordinary tests isolated from shared infrastructure.
Do Not Create a DTO for Every Layer by Habit
Use DTOs where data crosses a boundary with a different contract. Mapping has a cost, but so does letting external request shapes define the inside of a service.
Put Circuit Breakers Next to the Failing I/O
Circuit breakers, timeouts, cache fallbacks, and remote-call policies belong near the implementation that performs the I/O, while domain code chooses the required behavior.
Timeouts Are Product Decisions, Not Just Client Settings
Design timeout, retry, and recovery policies from the user's waiting budget and the uncertainty of each failure instead of applying one retry rule everywhere.
Do Not Split Modules Before the Domain Has Matured
Domain maturity comes from understanding policy, behavior, and operating reality. Let those lessons reveal module boundaries instead of freezing guesses too early.
Upgrade Dependencies Before the Gap Becomes a Project
Frequent, prioritized dependency upgrades keep change small, expose compatibility problems early, and prevent a maintained service from quietly accumulating debt.
When an AI Agent Calls the Same Tool Twice
Retries are normal in distributed systems and AI workflows. Use database constraints, idempotency keys, and bounded retries to prevent duplicate side effects.
Gradle Dependency Scopes Are Architectural Boundaries
Use Gradle implementation, api, runtimeOnly, and compileOnly deliberately to express module access, prevent accidental coupling, and preserve design intent.
Isolate Admin Work from the Service Domain
Admin APIs have different queries, mutation needs, and release risks. Isolate them by module or repository instead of letting operational convenience reshape the core service.
Use Reader and Writer Components to Reveal Business Flow
Reader and writer components can hide storage details, narrow change, and let the business layer show policy, but only when the software's lifetime justifies them.
Carry One Trace ID across Distributed Services
Logs become operationally useful when one request can be followed across HTTP calls and asynchronous events without exposing sensitive data or flooding storage.
Reuse Below the Use-Case Layer
When use cases call one another for reuse, business changes spread and cycles follow. Compose above them or extract focused implementation components below them.
How to Split a Large Service Class by Responsibility and Layer
Use constructor dependencies and imports to diagnose an oversized service, then separate cohesive responsibilities before adding another architecture label.
Choose Normalization from Requirements and Object Relationships
Database normalization is not a score to maximize. Decide from change semantics, query cost, and the relationships the data is meant to preserve.