When a system grows beyond a few hundred services, the boundaries that once made sense become liabilities. Teams spend more time negotiating interface contracts than delivering features. The Dynaxx macroarchitecture offers a different path: instead of carving modules by hand, let the system reveal its own natural partitions through unsupervised subnetwork discovery. This guide is for architects and tech leads who have already tried microservices, domain-driven design, or layered monoliths and are looking for a data-driven alternative that adapts as the system evolves.
Why Emergent Modularity Matters Now
Traditional modularization relies on upfront domain analysis. Teams draw bounded contexts, define aggregates, and hope the boundaries stay relevant. In practice, domains shift, new capabilities emerge, and the original module map becomes a source of friction. The cost of refactoring those boundaries later is high, often leading to distributed monoliths or sprawling service meshes that nobody fully understands.
Unsupervised subnetwork discovery flips the script. Instead of imposing a top-down module structure, we instrument the system to observe actual communication patterns—call graphs, data dependencies, temporal coupling—and let a clustering algorithm propose module boundaries. The result is a modularity that reflects how the system actually behaves, not how we imagined it would behave. Early adopters report that these emergent boundaries reduce cross-module changes by 30–50% compared to manually drawn boundaries, though results vary by domain and maturity of the instrumentation.
The Core Mechanism
At the heart of the approach is a simple feedback loop: instrument, cluster, evaluate, adjust. Instrumentation captures runtime metrics such as request traces, event streams, and shared data access. These signals are fed into a clustering algorithm—often a variant of spectral clustering or hierarchical agglomerative clustering—that groups components into subnetworks based on communication density. Each subnetwork becomes a candidate module. Teams then review the clusters, assign ownership, and set interface contracts. Over time, the clustering is repeated to catch drift, and the module map evolves organically.
Why Now?
Three trends make this practical today. First, observability tools have matured to the point where high-fidelity trace data is cheap to collect and store. Second, compute costs for clustering large graphs have dropped, making it feasible to run discovery on systems with thousands of nodes. Third, platform engineering teams are looking for ways to reduce cognitive load for developers; emergent modularity promises that developers can focus on their service without needing to understand the entire system map.
Three Approaches to Subnetwork Discovery
Not all unsupervised discovery is the same. The choice of algorithm, feature engineering, and validation strategy dramatically affects the quality of the resulting modules. We compare three common approaches: spectral clustering on call graphs, hierarchical clustering on temporal coupling, and graph neural network (GNN) based community detection.
Spectral Clustering on Call Graphs
This approach constructs a weighted graph where nodes are services or components and edge weights represent the volume of synchronous calls (RPCs, REST, gRPC) between them over a window of time. Spectral clustering then partitions the graph into subnetworks that minimize inter-cluster edges. It works well when the dominant interaction pattern is request-response and when call volumes are stable. However, it ignores asynchronous events and shared data stores, which can lead to modules that are tightly coupled through hidden channels.
Hierarchical Clustering on Temporal Coupling
Here, we measure how often two components change together in the same deployment or within a short time window. Temporal coupling is a strong signal of logical dependency, even if no direct call exists. Hierarchical clustering builds a dendrogram of change-co-occurrence patterns, and teams choose a cut level that yields manageable module sizes. This approach captures implicit dependencies that call graphs miss, but it requires a mature CI/CD pipeline with fine-grained deployment tracking. It can also be noisy if the team practices frequent, small deploys that bundle unrelated changes.
GNN-Based Community Detection
Graph neural networks learn a representation of each node that encodes both its local neighborhood and its role in the overall graph. Community detection algorithms then cluster these embeddings. GNNs can incorporate multiple signal types—calls, events, data access, ownership metadata—into a single model. They tend to produce more robust clusters in dynamic environments, but they require labeled training data for validation and significant compute resources during training. For most teams, this is a step too far unless they already have ML infrastructure in place.
How to Choose the Right Discovery Method
Selecting among these approaches depends on your system's characteristics, your observability maturity, and your tolerance for complexity. We recommend evaluating along four axes: signal richness, change stability, team size, and infrastructure overhead.
Signal Richness
If your system primarily communicates via synchronous APIs and you have distributed tracing in place, spectral clustering is a low-effort starting point. If you rely heavily on event-driven communication or shared databases, you need temporal coupling or a hybrid approach that includes event topics and data store access patterns. GNNs are overkill unless you have at least three distinct signal types and a team that can maintain the model.
Change Stability
Systems with frequent, large-scale refactoring or rapid feature growth benefit from hierarchical clustering because the dendrogram lets you adjust the granularity without re-running the entire discovery. Spectral clustering assumes a relatively stable graph structure; if the graph changes weekly, the clusters will shift too often for teams to build stable ownership.
Team Size and Coordination Overhead
Smaller teams (fewer than 5 per subnetwork) need fine-grained modules to avoid coordination bottlenecks. Larger teams can handle coarser modules. The clustering algorithm should allow you to specify a target module size range. Hierarchical clustering naturally supports this by choosing a cut level. Spectral clustering requires post-processing to merge or split clusters.
Infrastructure Overhead
Spectral clustering can run on a single machine for graphs up to tens of thousands of nodes. Hierarchical clustering scales similarly but requires a deployment tracking database. GNNs need GPU clusters and ML pipeline maintenance. Choose the simplest approach that meets your accuracy needs; over-engineering the discovery process is a common pitfall.
Trade-Offs in Practice: A Structured Comparison
To ground the discussion, we compare the three approaches across six dimensions that matter in production. The table below summarizes the key trade-offs.
| Dimension | Spectral (Call Graph) | Hierarchical (Temporal) | GNN (Multi-Signal) |
|---|---|---|---|
| Data requirements | Distributed traces | Deployment logs + git history | Traces, logs, events, metadata |
| Handles async? | No | Indirectly | Yes |
| Handles shared data? | No | Indirectly | Yes |
| Stability over time | Low | Medium | High |
| Compute cost | Low | Low | High |
| Team skill needed | Low | Medium | High |
No approach is universally superior. A common pattern is to start with spectral clustering as a quick baseline, then layer in temporal coupling as deployment tracking matures. GNNs are reserved for systems where the cost of misaligned modules is extreme, such as in financial trading platforms or safety-critical infrastructure.
Composite Scenario: E-Commerce Platform
Consider a mid-sized e-commerce platform with 200 microservices. The team tried domain-driven design but found that the 'order' domain actually spanned 40 services with no clear boundary. They deployed spectral clustering on call graphs and discovered three natural clusters: one around checkout (high call density), one around inventory (high data sharing), and one around recommendations (low call density but high temporal coupling with marketing campaigns). The inventory cluster was further split using temporal coupling because changes to stock levels and warehouse APIs always co-deployed. The result was six modules that reduced cross-module deployments by 40% in the first quarter.
Implementation Path: From Discovery to Living Modules
Adopting emergent modularity is not a one-time activity. It requires a repeatable pipeline that runs on a schedule and feeds into your development workflow. Below is a step-by-step path that teams have used successfully.
Step 1: Instrument for Graph Construction
Ensure every service emits trace spans with consistent service names and operation names. If you use a service mesh, export telemetry from the sidecar proxies. For data stores, capture access patterns via query logs or proxy agents. Aim for at least two weeks of data to smooth out daily and weekly cycles.
Step 2: Run Discovery on a Schedule
Set up a batch job that runs weekly or bi-weekly. Use a fixed window of the last 14 days. Output the cluster assignments as a machine-readable artifact (e.g., a JSON map of service to module). Version this artifact in your repository so that changes are tracked.
Step 3: Review and Assign Ownership
Each cluster should be reviewed by a small group of senior engineers. They may split or merge clusters based on team capacity and domain knowledge. The goal is not to accept the algorithm's output blindly, but to use it as a starting point for discussion. Document the rationale for any manual adjustments.
Step 4: Define Module Interfaces
For each module, identify the entry points that are consumed by other modules. These become the module's public API. Internal services within the module can communicate freely. Enforce that cross-module calls go through the defined interfaces, using linters or API gateways.
Step 5: Measure and Iterate
Track metrics like cross-module change frequency, module cohesion (ratio of internal to external calls), and team satisfaction. If a module consistently shows high external coupling, flag it for re-clustering in the next cycle. The system should be re-discovered at least quarterly, or after any major architectural change.
Risks of Getting It Wrong
Emergent modularity is not a silver bullet. Several failure modes can undermine the approach, and teams should be aware of them before committing.
Over-Fitting to Noisy Data
If your instrumentation is incomplete or noisy, the clusters will reflect artifacts rather than true dependencies. For example, a health-check endpoint that every service calls every second will create a star pattern that pulls all services into one cluster. Filter out infrastructure traffic and normalize call volumes by request count or duration.
Ignoring Human Factors
Clusters may be technically optimal but ignore team boundaries, time zones, or expertise. A module that spans three time zones with no overlap can cause coordination delays that outweigh the benefits of reduced coupling. Always map clusters to team structures and adjust if necessary.
Stagnation
Teams that run discovery once and never update it will eventually have the same problem as manually drawn boundaries: the module map drifts. Unsupervised discovery must be a living process. Set calendar reminders for re-discovery and make it part of the architectural review cycle.
False Precision
Clustering algorithms produce hard boundaries, but real systems have fuzzy edges. A service that sits at the boundary of two clusters may legitimately belong to both. Treat clusters as suggestions, not mandates. Allow services to be assigned to a module with a 'shared' designation if they are genuinely cross-cutting.
Frequently Asked Questions
How do we handle legacy systems with no traces? Start by instrumenting the most critical paths. Even partial data can yield useful clusters. Alternatively, use static analysis of code dependencies as a proxy, though it will miss runtime dynamics.
What if the clusters change drastically every week? This indicates that your system is either undergoing rapid change or your data window is too short. Extend the window to 30 days and smooth the results by averaging cluster assignments over multiple runs.
Can we combine this with domain-driven design? Yes. Use the discovered clusters as a reality check for your bounded contexts. If a DDD aggregate spans multiple clusters, it may be a sign that the aggregate is too broad or that the clustering missed a hidden dependency.
How many modules should we aim for? A common heuristic is 2–3 modules per team of 8–10 engineers. Smaller teams need finer granularity. Start with a target range of 5–15 modules for a system of 100–500 services.
Do we need a dedicated data science team? No. Spectral and hierarchical clustering are straightforward to implement with libraries like scikit-learn or NetworkX. GNNs require more specialized skills, but they are optional for most teams.
Next Moves for Your Team
If you are considering the Dynaxx macroarchitecture, start small. Pick a subsystem that is causing pain—high change coupling, frequent cross-team coordination—and instrument it for one week. Run spectral clustering on the call graph. Review the clusters with the team and see if they reveal any surprises. If they do, you have a candidate for deeper adoption. If they don't, your boundaries may already be well-aligned, and you can focus on other improvements.
Document your findings in a lightweight architecture decision record. Share the cluster maps with adjacent teams to get their perspective. Then, if the approach proves useful, expand to the full system gradually, adding temporal coupling as your deployment tracking matures. The goal is not to replace human judgment but to augment it with data-driven insight. Emergent modularity works best when it is treated as a conversation starter, not a final answer.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!