Cement Platform Split: Domain Modeling, Modular Monolith vs Microservices
This article explores architectural decomposition for a cement industrial platform, distinguishing business, code, and deployment boundaries, advocating modular monoliths over premature microservices, and detailing domain-driven design, bounded contexts, transaction boundaries, CQRS, and a practical migration strategy with evaluation criteria.
1. Distinguish Three Questions: Business, Code, and Deployment Boundaries
Many over-splitting issues stem from conflating three distinct concerns: business boundaries (what business facts, rules, and state a capability owns), code boundaries (which code can call which, data ownership, hidden implementation), and deployment boundaries (what needs independent release, scaling, or fault isolation). Only the last involves true distributed systems. The article emphasizes: five business modules do not mandate five microservices; deploying code in five processes does not guarantee clear business boundaries.
2. What Is a Modular Monolith?
A modular monolith organizes multiple modules with clear responsibilities within a single deployment unit. Example modules for an energy analysis app: metric definition & calculation, deviation rule management, anomaly event management, investigation & handling records, evidence query. These modules share a release but enforce strict boundaries: explicit interfaces, hidden internals, single data ownership, checkable dependency direction. A modular monolith is not "write a mess now, split later"; it is a deliberate structure preserving business boundaries while avoiding network, distributed transaction, and multi-service ops complexity.
3. Microservices: Added Capabilities and Responsibilities
Per James Lewis and Martin Fowler (2014), microservices require organization around business capabilities, independent deployment, and independent data storage. The third point is often overlooked: splitting means owning data responsibility. Benefits: independent deployment, differentiated scaling, fault isolation. Costs: timeouts, duplicate messages, data latency, version skew, multi-service coordination for one business result. A single in-process call becomes authentication, network, timeouts, retries, error handling, tracing, compatibility management. Fowler lists rapid provisioning, basic monitoring, rapid deployment as prerequisites. The decision criterion: does independent runtime value cover the new collaboration and ops cost?
4. Model from Business Actions, Not UI Menus
Splitting by UI (device page → device service, energy page → energy service) duplicates rules because the same anomaly appears in multiple views. Better: identify business actions — who publishes metric definitions, calculates results, judges deviations, confirms anomalies, records handling, decides closure. The rules and state behind these actions drive module boundaries.
5. Domain Modeling: Unify Terminology First
Domain modeling aligns business and engineering on key concepts. For cement mill power analysis, distinguish: meter readings, interval energy consumption, production volume, unit product power consumption, reference range, deviation assessment, anomaly event, handling task. Avoid polysemy: "anomaly" may mean missing data, high power consumption, or equipment fault — each has different criteria, handlers, closure conditions. A single generic "anomaly table" leads to endless special fields. Domain-driven design calls this ubiquitous language: once defined, code, docs, and conversation use the same term.
6. Bounded Contexts Limit Model Applicability
Bounded context (Eric Evans, DDD 2003) defines where a business language and model stay consistent. In energy analysis, "equipment" defines metering boundaries; in maintenance, it includes strategies, archives, repair history. They can share identity but not a giant all-field object. Context relationships — shared identifiers, call directions, event notifications — must be recorded explicitly (context map). Microsoft Azure's microservice boundary identification also uses domain model boundaries plus engineering factors like consistency. Crucially: bounded context is a model boundary, not a deployment mandate. Multiple contexts can run in one app; one context may need multiple runtime components. Do not mechanically map one context to one microservice.
7. Three Questions to Group Rules
When identifying modules, ask: (1) Do they maintain the same business state? (e.g., anomaly event confirmation, transfer, closure, reopen all around event lifecycle). (2) Must they satisfy business constraints together? (e.g., closing an event requires saving reason and confirmer simultaneously). (3) Do they change for the same reason? (e.g., reference condition grouping and deviation rules change together; notification channel changes do not affect judgment logic). Co-locating frequently co-changing rules reduces coupling. Sharing only a device ID is insufficient justification.
8. Avoid "One Object, One Service" Default
Device, tag, rule, event, task are business objects but do not each need a microservice. Beware splitting a single lifecycle: event creation service, confirmation service, closure service, history service. This scatters one lifecycle; a closure would need multiple service calls to keep state and history consistent. Better: let the event module own the full lifecycle, exposing different interfaces. Service boundaries should surround complete responsibilities, not database tables or CRUD actions.
9. First-Cut Modules for Power Anomaly Analysis
Assuming factory data ingestion exists, the article proposes six modules with responsibilities and exposed capabilities:
Object & Measurement Mapping : grinding system, equipment, measurement point relationships → query valid mappings & versions.
Metric Module : calculation口径, result status, revision versions → calculate & query power metrics.
Deviation Assessment Module : reference conditions, judgment rules, assessment results → evaluate if anomaly conditions met.
Event Module : merge, confirm, close, reopen → manage anomaly event lifecycle.
Handling Collaboration Module : responsibility assignment, investigation records, external task links → track investigation & handling.
Evidence Query Module : page-oriented composite queries → display related trends, metrics, records.
Object mapping may reuse existing platform services. Evidence query composes display data without gaining write access to other modules' data. This table is a responsibility design, not yet a deployment decision.
10. Enforce Module Boundaries in Code and Data Rules
Mere folder separation is insufficient. Four rules are needed: (1) Explicit public interfaces — other modules call only via agreed interfaces, not internal details. (2) Data modification ownership — event module modifies event state; metric module publishes metric revisions. No report module writing event tables, no event module updating metric results. (3) Checkable dependency direction — architecture fitness functions (automated checks) prevent cyclic dependencies and unauthorized cross-module access. (4) Tests for changes — modifying metric rules runs metric tests and verifies dependent contracts via consumer-driven contract testing.
11. Database Sharing vs. Modification Responsibility
A modular monolith can share a DB instance; independent services may also share DB infrastructure. The microservice guideline "database per service" opposes the shared-database anti-pattern where multiple modules freely read/write the same tables, coupling all callers to schema changes. The real danger: multiple modules arbitrarily reading/writing the same tables, anyone can modify schema, anyone can bypass business validation. Minimum practices: clear table ownership, controlled write interfaces, restricted cross-module dependencies, compatibility rules for schema changes. Cross-module joins are not universally forbidden but their cost must be acknowledged — if dozens of queries directly depend on a module's internal tables, extraction cost becomes high. Data ownership is logical responsibility; DB server count is a deployment choice — do not conflate them.
12. Transaction Boundaries Signal Split Difficulty
Closing an anomaly event requires atomic commit of event state, closure reason, confirmer, status change log — all within the event module. Splitting them across services introduces partial failure handling. Conversely, event confirmation triggering external EAM work order creation need not be instantaneous; allow intermediate state "pending work order submission" with async integration, explicit retry, and compensating transactions (explicit reverse operations) rather than global rollback. Before splitting, identify: which data must succeed together, which collaborations can be deferred? If many core operations demand cross-service immediate consistency, boundaries may be too fine.
13. Read-Side Composition Must Not Dictate Write Boundaries
An anomaly detail page may need device name, metric result, process trend, lab info, event status, handling records. This does not mean one service should own all. Use a page-oriented query composition layer or maintain read models for display — Command Query Responsibility Segregation (CQRS): queries can denormalize and assemble for efficiency, but commands (writes) must go to the module owning that business responsibility. Choice depends on data volume, freshness needs, call cost. Always clarify: data sources, allowed latency, fallback when a source is unavailable. Never centralize all write authority into a "comprehensive query service" for page convenience. Read side composes; write side remains responsibility-driven.
14. When to Keep a Modular Monolith
Prefer modular monolith when: single team maintains; business boundaries still shifting; modules usually release together; strong transactional coupling across modules; similar resource needs; limited factory deployment/ops capability; no clear independent scaling or isolation needs. This is not a "small project only" rule — a well-modularized app can serve critical business long-term. Trade-offs exist: unified release widens change impact, shared process risks resource contention, some issues cannot be isolated by code modules alone. Choose it because current benefit-cost fits, not because it is inherently superior.
15. When Independent Services Are Worth It
Seek concrete, verifiable reasons: (1) Independent scaling — e.g., historical recomputation needs heavy compute while daily event queries are light. (2) Independent release — e.g., model runtime updates frequently, shouldn't force business workbench redeploy. (3) Different deployment locations — e.g., collection must run on-premise at factory, group analytics in group cloud. (4) Fault isolation — e.g., bulk compute failure shouldn't drag down daily queries and event handling. (5) Security/dependency differences — e.g., dedicated access boundary or distinct runtime. (6) Truly independent team ownership — different teams own dev, release, monitoring, incident response, not just code writing. Each reason must be validated: is a separate process enough? Must business data be fully split? Can queues, resource quotas, or deployment tweaks solve it first? Isolating compute does not require splitting business domains.
16. Practical Approach: Modular Business App + Few Independent Components
For the first phase: factory-side collection & cache run independently; object mapping reuses platform capability; metric config, events, handling records in one modular business app; compute tasks move to independent worker processes as load demands; reuse existing notification, identity, model services. This keeps necessary deployment boundaries while avoiding many tiny services upfront. Independent compute workers need not be full microservices — they may share product version, team, and collaborate via task protocols. Further independence depends on stable interfaces, independent lifecycle, clear runtime ownership. Architecture is not binary "pure monolith vs. all microservices".
17. Detecting Distributed Monolith: Split but Cannot Change Independently
Distributed monolith bears distributed runtime cost without independent change ability. Symptoms: field change requires multi-service coordinated deploy; services directly access each other's DB tables; simple operation chains many synchronous calls, any failure blocks completion; all services must start for basic dev/test; no team can explain full business flow. Remediation: re-aggregate highly coupled responsibilities, reduce sync dependencies, stabilize interfaces, clarify data ownership. Merging several services back into a modular app is a valid architectural adjustment.
18. Event-Driven Reduces Sync Dependencies but Adds Complexity
Example: metric module publishes "power metric revised", deviation assessment module re-evaluates. Reduces direct calls but must handle: duplicate messages, consumption failures, ordering changes, version compatibility, backlog. Duplicate delivery is near-unavoidable; industrial platforms typically choose at-least-once, leaving idempotency to consumers (same message processed twice = same effect as once). Idempotency keys for cross-system dedup were covered in Part 3. Events should express facts that happened, not vague "update all related data". A metric revision event carries: metric result ID, object ID, statistical interval, result version, occurrence time. Consumer decides whether to process and records handling. For critical consistency, avoid "data committed but event not emitted" via transactional outbox: write pending events and business records in same DB transaction, separate dispatcher reads, sends, retries until success. Async collaboration's value is allowing explicit time gaps, not losing control over processing outcomes.
19. Reducing Migration Risk When Splitting
Do not rewrite all modules, migrate all data, swap all interfaces at once. Steps: (1) Stabilize responsibilities — confirm what target module owns and doesn't. (2) Funnel access — callers use module interfaces, reduce direct internal table/implementation dependencies. (3) Define contracts — explicit inputs, outputs, errors, timeouts, versions, permissions. (4) Validate new implementation — for compute/query, compare old vs new results with same inputs. (5) Controlled cutover — gradual traffic shift with monitoring (strangler fig pattern). (6) Explicit data write switch — always know who holds authoritative write; avoid long-term unconstrained dual-write. (7) Remove old paths — after stability, clean old dependencies to prevent dual implementations lingering. Rollback must consider business data produced by new system: can old system understand new data/state before deciding restoration.
20. Post-Split, Team Responsibilities Must Follow
An independent service needs someone accountable for: interface compatibility, runtime monitoring, capacity, release, incident handling, dependency upgrades. Conway's Law: system structure mirrors org communication structure. Splitting services without adjusting responsibility merely moves code complexity into organizational friction. If business team only develops and ops handles runtime without business understanding, service independence stalls at deployment level. Platform team provides common tools but cannot own each business service's correctness. Example: infra monitoring detects process death but not "power calculation used expired product mapping". Service owners must watch both technical and business health. Independent deployability is the starting point; independent accountability is the condition for long-term operation.
21. How to Judge If Splitting Actually Worked
Don't measure by service count. Compare before/after: modules touched per local requirement; reduction in coordinated releases; independent scaling reducing resource waste; fault blast radius shrinkage; faster troubleshooting; interface/message failure changes; local dev/test difficulty; factory delivery/upgrade ease; long-term maintenance cost acceptability. For quantitative reference, DORA's four delivery metrics (deployment frequency, lead time for changes, change failure rate, time to restore service) reflect split's impact on delivery and operations, though not business correctness. Goals may conflict: independent scaling improves resource efficiency but increases deployment complexity. Worthiness depends on project's primary constraints. Splitting is not a one-time proof of architectural advancement; it's an engineering decision requiring continuous validation.
22. Service Split Decision Card for Reviews
For each candidate independent deployment, fill a decision card:
Candidate Capability : e.g., historical metric recomputation.
Current Problem : recompute tasks compete with online queries for resources.
Business Boundary : which calculations owned, which records not.
Independent Deployment Benefit : can it scale, limit resources, isolate faults alone?
Data Ownership : what data read, what formal results written.
Interface & Consistency : task submission, result publishing, allowed latency.
Exception Handling : duplicate tasks, retry, timeout, cancellation.
Alternatives : independent worker process, resource quotas, scheduling optimization sufficient?
Runtime Responsibility : who releases, monitors, handles incidents.
Migration & Exit : cutover plan, validation, when to merge or adjust.
If the card only says "aligns with microservice trend", justification is insufficient. Save completed cards as Architecture Decision Records (ADR): context, options, decision, trade-offs — so future readers know not just "it was split" but "why it was split this way". Next article will cover architecture views, decision records, and review checklists.
23. Deliverables for This Phase
Produce five artifacts: (1) Domain & Glossary — clarify key concepts and scopes. (2) Module Responsibility & Dependency Diagram — who owns rules, state, data. (3) Data Ownership & Transaction Boundary Table — identify operations that must succeed together. (4) Deployment & Service Split Decision Records — justify independent runtime with costs. (5) Architecture Validation Checklist — covering dependency checks, interface compatibility, fault isolation, migration tests. These can be lightweight but must answer: why this split? how to prevent boundary bypass? what changes trigger re-evaluation?
Conclusion: Clarify Responsibilities First, Then Split Deployment
Cement industrial platform complexity won't vanish by adopting microservices; it may shift from code internals to network, data consistency, and ops. Safer design sequence: understand business first, then define model boundaries; establish module constraints first, then evaluate deployment isolation; find real benefits first, then bear distributed costs. For phases where business boundaries are still forming and team/ops resources are limited, modular monolith can be a long-term valid choice. For capabilities already demanding independent scaling, release, isolation, or deployment location, independent services deliver clear value. The ultimate goal is neither larger monolith nor more microservices, but: local changes completable locally, business rules with clear ownership, faults not spreading unbounded, system sustainably maintainable by teams.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
