Sharing Bounded Context for Startups
A DDD approach that ensures team scalability without oversplitting domain boundaries too early.

Introduction
Splitting the Bounded Context at the startup stage may seem like overkill. However, if the initial domain boundary is not clear, terminology conflicts and responsibility confusion will repeat as the team increases. This article introduces a DDD boundary setting method that can be applied even to small organizations.

Problem definition
If you quickly add features without context, the following problem will repeat itself.
- The same word has different meanings for each team, increasing communication costs.
- The data model is expanded to a general-purpose table, increasing the scope of impact when changes are made.
- Distribution risk increases as permissions, payment, and content policies are mixed in one service.
In the beginning, you can start with language and separation of responsibilities rather than complete separation. Create a context map first and gradually adjust the code boundaries.
Key concepts
| perspective | Design criteria | Verification points |
|---|---|---|
| language | Ubiquitous language definition by team | Frequency of term conflict |
| model | Separate entities by context | Model change ramifications |
| Integration | ACL (anti-corruption layer) | Boundary Invasion PR Rate |
| organization | Aligning team responsibilities and code boundaries | Decision-making speed |
In startups, it is more realistic to distinguish between core domains and support domains rather than dividing the boundaries too much. What's important are naming conventions and interface contracts.
Code Example 1: Bounded Context Contract
// Billing Context
export type BillingInvoice = {
invoiceId: string;
accountId: string;
totalAmount: number;
status: "issued" | "paid" | "void";
};
// Content Context
export type ContentPost = {
postId: string;
authorId: string;
title: string;
visibility: "public" | "private";
};
Code example 2: ACL adapter
export class BillingAcl {
constructor(private readonly billingClient: BillingApiClient) {}
async getActivePlan(accountId: string) {
const invoice = await this.billingClient.fetchLatestInvoice(accountId);
return {
accountId,
isPaid: invoice.status === "paid",
plan: invoice.totalAmount > 0 ? "pro" : "free",
};
}
}
Architecture flow
Clean Architecture Layer Structure
When looking at the DDD boundary from a Clean Architecture perspective, the structure of managing integrated policies between contexts at the application layer and isolating the infrastructure using adapters is stable.
| Layer | responsibility | How to apply |
|---|---|---|
| Entities | Context Internal Model | BillingInvoice, ContentPost |
| Use Cases | Cross-context policy | PublishIfPlanAllows |
| Interface Adapters | ACL, Event Translator | BillingAcl |
| Frameworks | DB, message broker | Postgres, Kafka |
Infrastructure diagram
Tradeoffs
- Clear boundaries make collaboration easier, but require initial design time.
- Having an ACL can prevent contamination, but it increases the integration code.
- Context separation is advantageous for expansion, but separation too early can result in over-design.
Cleanup
The goal of Bounded Context in startups is not a perfect microservice. Simply separating terminology and responsibilities and reducing boundary intrusions can significantly reduce future expansion costs.
Image source
- Cover: source link
- License: CC BY-SA 3.0 / Author: Eric Gaba (Sting - fr:Sting)
- Note: After downloading the free license image from Wikimedia Commons, it was optimized to JPG at 1600px.