Enterprise Architecture Patterns Tutorial 0/65 lessons ~6 min read Lesson 12

    Bounded Contexts

    Bounded contexts delimit a domain model's applicability — inside the boundary, terms and rules are consistent; across boundaries, integrate via translation, not shared entities.

    Course progress0%
    Focus
    18 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Bounded contexts delimit a domain model's applicability — inside the boundary, terms and rules are consistent; across boundaries, integrate via translation, not shared entities. Google Ads spans billing, targeting, and serving — each context owns its model and ubiquitous language.

    Real production story

    Google Ads engineers shared a "Campaign" entity across serving, billing, and reporting teams. Serving needed microsecond budget checks; billing needed immutable invoice lines; reporting needed historical snapshots. One ORM model served none well — serving skipped validations, billing got mutable state bugs. Bounded context split produced CampaignServing, BillingContract, and ReportingSnapshot with ACL mappers. p99 serve path simplified; billing disputes dropped; reporting backfills became event-driven without touching serve hot path.

    Business problem

    Google Ads is multiple businesses in one product surface. Without bounded contexts, one entity name hides incompatible invariants and couples release trains.

    • Performance isolation: Serving context cannot wait for billing schema migrations.
    • Correctness: Financial invariants differ from ad delivery heuristics — one model compromises both.
    • Team autonomy: Hundreds of engineers need deploy independence within clear seams.

    Architecture overview

    Bounded context packages entities, value objects, aggregates, services, and repositories under one consistent model. Boundaries are social and technical: team ownership, API contracts, separate persistence.

    • Boundary signals: Terminology forks, different lifecycles, different SLAs on same noun.
    • Integration: Domain events, public APIs, ACL — never shared mutable tables.
    • Size: Context too large = mini monolith; too small = integration tax — fit team cognitive load.
    • Testing: Contract tests at boundary; internal model tests stay private.

    Architecture motivation

    Bounded context is the unit of model consistency. Staff architects enforce: one aggregate root ownership, one ubiquitous language, explicit integration — not shared database tables.

    • Force: Same noun, different rules — "Campaign" is not one thing.
    • Constraint: Legacy shared DB — strangler with ACL and dual-write reconciliation.
    • Outcome: Engineers reason locally; cross-context changes are explicit integration projects.

    Internal architecture

    Google Ads bounded contexts — separate models, explicit integration:

    • No foreign keys across context databases — only correlation IDs in events.
    • ACL in reporting ingests legacy CSV without polluting serving model.
    text
    ┌──────────────────┐
    │ Serving Context │ hot path · microsecond budgets
    │ CampaignServe AR │ eventual budget from Billing events
    └────────┬─────────┘
    │ CampaignBudgetUpdated (event)
    ┌──────────────────┐
    │ Billing Context │ strong invariants · invoice lines
    │ BillingContract │
    └────────┬─────────┘
    │ InvoiceLineCreated (event)
    ┌──────────────────┐
    │ Reporting Context│ append-only snapshots
    │ CampaignSnapshot │
    └──────────────────┘

    Data flow

    Within context: command → aggregate → persist → publish event. Across contexts: consumer translates event to local model via ACL — never import foreign entities.

    • Serving write: Impression logged; budget check against local projection.
    • Billing sync: Consumes delivery events; emits budget updates serving consumes.
    • Reporting: Async snapshot from both; never blocks serve path.

    System design diagram

    Two diagrams show the Bounded Contexts topology and the primary request/event path used in production at scale.

    Bounded Contexts — system view
    Ads UI
    Edge
    Serving context
    Core
    Billing context
    Data
    Reporting
    Async
    High-level topology for Bounded Contexts.
    Bounded Contexts — request / event flow
    Command
    Ingress
    Aggregate
    Store
    Domain event
    Store
    ACL consumer
    Emit
    Follow this path when reviewing production designs.

    Production code example

    Anti-corruption layer mapper — Java between billing and serving contexts:

    • ACL is the only file allowed to know both sides' DTO shapes — enforce with ArchUnit.
    • Map to local value objects, not foreign entities.
    java
    public final class BillingToServingAcl {
    public ServingBudgetProjection map(CampaignBudgetUpdated event) {
    return ServingBudgetProjection.builder()
    .campaignId(CampaignId.of(event.campaignId()))
    .remainingMicros(event.remainingBudgetMicros())
    .currency(CurrencyCode.of(event.currency()))
    .asOf(Instant.ofEpochMilli(event.occurredAt()))
    .build();
    }
    public CampaignServeCommand map(ServeAdRequest request, ServingBudgetProjection budget) {
    if (budget.remainingMicros() < request.estimatedCostMicros()) {
    throw new InsufficientBudgetException(request.campaignId());
    }
    return CampaignServeCommand.create(
    request.campaignId(),
    request.adGroupId(),
    request.auctionId()
    );
    }
    }
    // Serving context never imports billing.domain.Campaign

    Enterprise case study

    Google Ads Campaign split: Unified Campaign entity blocked serving performance and billing correctness.

    • Before: Shared Postgres schema; serve p99 regressions on billing deploy days.
    • Decision: Three bounded contexts, separate stores, event choreography, ACL for legacy reporting.
    • After: Serve and billing deploy independently; billing dispute rate down 40%; reporting lag explicit in SLA.

    Trade-offs

    • Duplication vs coupling: Duplicate Campaign ID and subset of fields — avoid shared entity JAR.
    • Eventual consistency: Serving budget projection may lag billing — define max staleness SLA.
    • Migration cost: Splitting context from shared DB takes quarters — plan reconciliation.
    • Context granularity: Split when change rates diverge; merge when integration cost exceeds benefit.

    Security considerations

    Context boundary enforces least privilege: Serving service account cannot write billing ledger.

    • IAM per context: Separate DB roles, KMS keys, and audit logs.
    • PII scope: Billing holds payment instruments; serving holds pseudonymous IDs only.
    • Event payload: Scrub PII at publish boundary — consumers trust minimum data.

    Scalability analysis

    Bounded contexts enable scale: Serving scales horizontally; billing scales on batch windows; reporting scales on pipeline workers — independent.

    • Data partition: Each context DB shards by customer ID independently.
    • Team scale: New team = new context only if boundary is clear — avoid context sprawl.
    • Catalog: Internal developer portal lists contexts, events, and owners.

    Failure scenarios

    Boundary violations cause incidents: Direct SQL join across contexts; shared cache key namespace collision; deploying billing migration breaks serving.

    • Leaky boundary: Serving reads billing table — circuit break when billing slow takes down ads.
    • Schema coupling: Shared migration — separate flyway per context database.
    • Event schema break: Version events; consumers support n and n-1.

    Staff engineer insights

    • Bounded context is where your ubiquitous language stops being ambiguous — if two teams argue about one class, you need two contexts.
    • Shared database is the anti-pattern that kills bounded contexts — the schema is the hidden coupling layer.
    • Context size should match what one team can hold in working memory — not what fits on one poster.

    Interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    3 questions
    1AdvancedQuestionHow is bounded context different from a microservice?+

    Answer

    Bounded context is a logical model boundary; microservice is a deployment boundary. Ideally they align, but one context can start as a module in a modular monolith. Multiple contexts in one service is OK temporarily; one context split across many services without clear aggregate ownership is a distributed monolith.

    Follow-up

    When would you put two contexts in one deployable?
    2AdvancedQuestionYou have a shared database blocking context split. Migration plan?+

    Answer

    Identify aggregate ownership per table; introduce ACL in app layer first; dual-write to new context DB with reconciliation job; switch reads behind flag; deprecate cross-context FKs; delete shared write path. Order by risk: read-only reporting first, money path last.

    Follow-up

    How do you handle transactions during dual-write?
    3AdvancedQuestionHow big should a bounded context be?+

    Answer

    One team owns it comfortably; one ubiquitous language without internal contradictions; cohesive change rate; typically 3–7 aggregates, not 50. Split when terminology forks or deploy coupling hurts; merge when integration overhead dominates.

    Follow-up

    Signals that context is too small?

    Architecture review questions

    • Does each context have one team owner and separate persistence?
    • Are cross-context references only via IDs in events or APIs?
    • Is ACL present where models translate — not direct entity import?
    • Are ubiquitous language glossaries per context documented?
    • Do contract tests validate published event/API schemas?
    • Is shared database usage explicitly zero or on deprecation timeline?

    Summary

    Bounded contexts at Google scale isolate incompatible models behind explicit boundaries — serving, billing, and reporting each own their Campaign variant, integrated by events and ACLs, not shared entities.

    Ready to mark this lesson complete?Track your journey across the entire course.