Matching Architecture to the Subdomain

Not Every Bounded Context Deserves the Same Design

Software Design

Not every part of a system carries the same kind of complexity. Some enforce the rules that define the core of the business. The rest either support it or are solved problems that any competitor could buy off the shelf. Applying the same architecture to all of them produces systems that are over-engineered where simplicity would serve better, and under-engineered where complexity demands more.

The architecture should match the problem it is solving. The subdomain classification from the earlier articles already tells you how complex the problem is. Using it as a design input rather than just a label is the difference between structure that helps and structure that gets in the way.

The previous articles mapped the strategic picture: bounded contexts define the boundaries, context mapping names the relationships, and integration patterns determine how they communicate, using an online learning platform as the running example. On that platform, the learning context governs completion and certification, enrollment manages who can join a course and when, and the notification context sends emails. None of them calls for the same architecture. This article works through what each subdomain type demands.

The Subdomain Type as a Design Input

The subdomain classification captures two things at once: how complex the rules are likely to be, and how much they are expected to change. Together those two factors determine how much architectural isolation is worth the investment.

Generic subdomains are solved problems where the goal is to avoid building what already exists. When custom code is unavoidable, keeping it thin is the right default. There is no domain model to protect.

Supporting subdomains have real but contained rules. The rules are stable and self-contained enough that a domain object can own them alongside its persistence. A layered architecture keeps them visible and testable without the overhead of a fully isolated domain model.

Core subdomains have complex, evolving rules that the business depends on getting right. As the business changes, these rules change with it. That points toward ports and adapters, also known as hexagonal architecture: an architecture that keeps the domain logic free from infrastructure so the rules can evolve without touching the database or the web framework.

The Solved Problem

The first question for a generic subdomain is whether an off-the-shelf solution already covers the need. On the learning platform, notifications are the clearest example. Before writing a custom notification system, consider what a provider like SendGrid already handles: email delivery, retry logic, bounce handling, and tracking. The integration work is real, but the domain modeling has already been done by someone else.

Even with a provider handling the delivery, the notification handler still needs to be written. It receives a course completion event, composes the message, and calls SendGrid. This is what Martin Fowler calls a Transaction Script1: one procedure for one operation, with the provider called directly and no domain model in between.

A notification handler receiving an event and calling a provider directly
A notification handler receiving an event and calling a provider directly

The developer experience reflects this. You write the handler and there is nothing to unit test in isolation, not because coverage is missing, but because there are no domain decisions to verify. The branching is about infrastructure: was the event data complete, did the provider return an error. The test that matters is whether the integration with the provider actually works.

Yours, Not Your Edge

Supporting subdomains have real business rules that warrant explicit modeling, but the rules are contained and stable rather than complex and interconnected. On the learning platform, enrollment and billing are both supporting subdomains.

Enrollment has rules worth making explicit. Students can only enroll in published courses. Cohort-based courses have enrollment windows. Some courses have prerequisite requirements. Each of these rules belongs to a single object and can be evaluated against the data it owns.

A layered architecture fits rules of this kind. An enrollment request arrives at a route, which delegates to an optional application service that coordinates an object owning both the rules and its persistence. This is sometimes called Active Record when the object saves itself, or the data access object (DAO) pattern when a dedicated object handles persistence on its behalf.

Enrollment Route, Enrollment Service, and Enrollment DAO with direct database access
Enrollment Route, Enrollment Service, and Enrollment DAO with direct database access

In both cases, the business rules live on the object rather than scattered across service methods. You can instantiate the object in a test and verify the enrollment rule directly, without an HTTP request or a running application. The database is still in the picture, but it is not in the way.

The Competitive Advantage

The core subdomain is where the full investment pays off. The rules are complex, they span multiple parts of the model, and they change as the business evolves.

On the learning platform, a student becomes eligible for certification only after completing all required lessons, passing any required assessments, and satisfying any prerequisites on the course. That rule has multiple conditions and touches multiple objects. When it lives in a service method, it gets duplicated, split across helpers, or quietly bypassed by a different code path. When it lives on a domain object that owns the state it needs to evaluate the rule, it is findable, testable, and evolvable.

The same need applies to curriculum design. A published course cannot be arbitrarily edited. A course cannot be published without at least one lesson. These rules need a home in the model, not in a service layer that might or might not remember to enforce them.

This is where ports and adapters architecture earns its place. The domain logic sits at the center, insulated from the web framework, the database, and any external service. Interfaces define what the domain needs from persistence; adapters provide the implementation.

The domain model at the center, insulated from persistence and external services by interfaces and adapters
The domain model at the center, insulated from persistence and external services by interfaces and adapters

When a new completion criterion needs to be added, you find the domain object, add the rule, and write a unit test. No database, no HTTP call, no infrastructure setup. That freedom to change is what the isolation is protecting.

Tests That Reveal the Architecture

The architecture shape determines where meaningful tests can be written.

For a generic subdomain, there is little business logic to exercise in isolation. You write a unit test for the notification handler, mock the SendGrid call, and the test passes. But you still have no idea whether the email renders correctly or whether SendGrid accepts the actual payload. The tests that answer those questions require the real provider. When unit tests have almost nothing meaningful to verify, integration and end-to-end tests carry the weight. This distribution, heavier at the top than the base, is often called an inverted testing pyramid.

For a supporting subdomain, there is genuine logic worth testing directly. An enrollment rule that rejects a student who has not met the prerequisites can be verified by instantiating the object and calling the method. But the interactions between layers matter too, so integration tests have an equal place. Unit tests and integration tests carry equal weight, with end-to-end coverage kept minimal. This balance, with the bulk in the middle, is often called a testing diamond.

For the core subdomain, the domain objects carry the rules. A certification eligibility check, a prerequisite evaluation, a course rule: all of these can be exercised without a database or an HTTP call. The unit test base is where the confidence comes from, and the ports and adapters architecture makes that base both practical and thorough. With integration tests covering the adapters and end-to-end coverage kept minimal, this distribution is the classic testing pyramid.

Three subdomain types, three testing shapes
Three subdomain types, three testing shapes

Using the wrong shape for the subdomain makes the mismatch visible quickly. A classic pyramid for a generic subdomain means writing mocked unit tests for code that just calls an external provider. The tests pass confidently while the real integration failures slip through untested.

An inverted testing pyramid for the core subdomain means verifying domain rules through the full stack. Test setup becomes complex, the suite runs slowly, and covering every code path requires a combinatorial explosion of end-to-end scenarios. In practice, developers stop writing tests for edge cases because each one is too expensive to set up. Coverage of the rules that matter most quietly erodes.

Reading the Signals

A subdomain’s classification can shift in either direction, and the architecture needs to follow.

The classification is not permanent. The business changes, and the subdomain needs to keep up. Features that started as commodity integrations can grow into genuine domain problems. Anticipated complexity can fail to materialize, leaving the architecture ahead of where the domain actually is.

On the learning platform, the billing context might start as a thin wrapper around a payment provider. Over time the platform adds pricing tiers, discount policies, and invoice customization. What was generic has become a supporting subdomain with real business logic. You try to write a test for a new pricing rule and find yourself starting up the full application or making HTTP calls to verify a single condition. That is the architecture telling you it has outgrown its current shape.

That kind of friction is worth naming. When tests become the obstacle rather than the safety net, the architecture is signaling that it no longer fits the domain it is supposed to serve.

The shift can go the other way. On the learning platform, the certification context might be designed for complex eligibility rules that never fully materialize. You make a simple change and find yourself touching an interface, an adapter, an application service, and a domain object. None of them contain logic that justifies the journey. The structure is protecting a complexity that no longer exists.

Simplifying is the harder direction. Adding structure to under-invested code is uncomfortable but straightforward. Stripping out interfaces, adapters, and application services from code that no longer warrants them means accepting that the anticipated complexity never arrived and that the architecture built for it is now costing more than it protects. Keeping it because it was already built is the sunk cost fallacy in architectural form.

When the signals appear, revisit the classification before touching the architecture. The architecture reflects an assumption about how complex the rules are and how much they will change. When that assumption no longer holds, the classification should shift first, and the architecture should follow it.

What Comes Next

The strategic side of the series is now complete. Bounded contexts define the boundaries. The context map names the relationships. Integration patterns determine how contexts communicate. The architecture of each context reflects the nature of the subdomain it implements.

Choosing ports and adapters for the core subdomain isolates the domain model from infrastructure. What that model contains is the next question. A course on the learning platform has a title and a set of lessons. Is the title an object in its own right, or just a string? Is a lesson interchangeable with any other lesson, or does its identity matter? The next article covers entities and value objects: the first building blocks of a domain model.


  1. Martin Fowler, Patterns of Enterprise Application Architecture. Addison-Wesley, 2002. ↩︎

This article is part of the Domain-Driven Design series

  1. 1. What Is Domain-Driven Design?
  2. 2. Subdomains and Bounded Contexts in Domain-Driven Design
  3. 3. Context Mapping in Domain-Driven Design
  4. 4. Integrating Bounded Contexts
  5. 5. Matching Architecture to the Subdomain
domain-driven-design