Bounded Contexts and Context Mapping
How LZStock prevents the 'Distributed Monolith' anti-pattern by utilizing Strategic Domain-Driven Design to strictly define microservice boundaries and integration protocols.
- Eradicate the Distributed Monolith: Decomposed the global ecosystem into strict Bounded Contexts, ensuring each microservice fully owns its ubiquitous language, database, and domain model to guarantee true fault isolation.
- Strategic Integration Protocols: Orchestrated cross-boundary communication by pairing gRPC (Open Host Service) for synchronous API routing with NATS JetStream (Customer/Supplier) for resilient, asynchronous data ingestion.
- Bulletproof Anti-Corruption Layers (ACL): Shielded lightweight frontend-facing services from external SEC data chaos and internal heavy financial models by enforcing strict architectural translation boundaries.
The Objective
The most common cause of microservice failure is improper boundary definition. If services share the same database or rely on tightly coupled synchronous calls for every operation, you haven't built microservices; you've built a fragile "Distributed Monolith."
The objective of Strategic DDD is to decompose the global domain into explicit Bounded Contexts (BCs). Each BC represents a distinct micro-scope of responsibility with its own ubiquitous language, database, and domain model. Once the boundaries are set, we establish strict Context Mapping Patterns to govern how these isolated contexts integrate.
The Mental Model: Context Mapping
We have divided the LZStock ecosystem into several core Bounded Contexts. To integrate them without creating a tangled dependency web, we apply specific DDD context mapping patterns (OHS, ACL, C/S, CF) across gRPC and NATS JetStream.
Core Implementation: Integration Patterns
Each communication pathway in LZStock is a deliberate architectural decision designed to balance data consistency, decoupling, and performance.
Open Host Service (OHS) & Published Language (PL)
For synchronous, request-response communication, we utilize gRPC.
- Gateway Routing: The API Gateway (BC15) acts as the sole entry point. It calls Auth (BC13) and Dashboard (BC1) via gRPC. The upstream services act as Open Hosts, providing a strictly versioned Protocol Buffer definition (The Published Language) that the Gateway conforms to.
- Real-time Streaming: Market Monitoring (BC11) establishes a continuous gRPC Stream with the API Gateway (BC15), allowing the backend to push real-time market ticks directly to the Gateway, which then forwards them to clients via WebSockets.
Customer / Supplier (C/S) via Event-Driven Architecture
For cross-domain state changes and data ingestion, we avoid synchronous calls to prevent cascading network failures.
- The Pattern: The Data Pipeline (BC10) scrapes external data and publishes it via NATS JetStream (Asynchronous Event Push). The Company Data service (BC3) acts as the Customer, consuming these events at its own pace to update its core financial databases. This guarantees absolute system resiliency.
Anti-Corruption Layer (ACL) & Mappers
An Anti-Corruption Layer prevents foreign, unstructured, or overly complex data models from polluting a specific Bounded Context. We utilize this pattern in two critical areas:
- External Isolation (BC10): External SEC data is messy and volatile. The Data Pipeline (BC10) acts as an ACL, using HTTP crawlers to fetch the XML/JSON, cleansing it, and translating it into LZStock's internal domain events.
- Internal Heavy Model Mitigation (BC3 to BC1): The Company Data service (BC3) holds massive, complex models containing multi-language descriptions, historical financials, and SEC filings. If the Dashboard (BC1) consumed this directly, it would suffer from severe memory bloat. Instead, BC1 queries BC3 via gRPC (OHS), but implements a strict Internal ACL / Mapper. It extracts only the essential fields (Ticker, Name, Current Price) to construct a Lightweight Company View Model, perfectly tailored for fast UI rendering.
Edge Cases & Trade-offs
- Monorepo vs. Microservices (The Deployment Paradox): Our logical architecture is strictly Microservices, perfectly suited for horizontal scaling in our Kubernetes cluster. However, managing 6 different Git repositories for a small team creates severe versioning overhead.
- The Trade-off: We utilize a Monorepo managed by PM2 for local development. This provides the ultimate Developer Experience (DX) by allowing engineers to spin up the entire ecosystem with a single command, while our K8s CI/CD pipeline intelligently builds and deploys them as isolated microservice containers in production.
- Synchronous (gRPC) vs. Asynchronous (NATS) Coupling: It is tempting to use gRPC for everything because it "feels" like a standard function call. However, a gRPC call couples the availability of the upstream service to the downstream service. We strictly limit gRPC to Read-operations, Gateway routing, or continuous data streams. Any discrete operation that mutates state across Bounded Contexts (like data ingestion) must utilize NATS to maintain eventual consistency and temporal decoupling.
The Outcome
By explicitly mapping our Bounded Contexts and deliberately choosing integration protocols (gRPC Streams, NATS, Internal ACLs), LZStock prevents the "Big Ball of Mud" anti-pattern. The architecture ensures that frontend-facing services remain lightweight, while heavy data processing is asynchronously isolated, allowing independent squads to scale and iterate rapidly.