In recent years, I've worked with many organizations building large enterprise systems on Angular. Because of their scale and complexity, decomposing these systems into smaller, coherent modules becomes a necessity. While this sounds straightforward, in practice it's often difficult—every way of slicing brings its own set of trade-offs.
Domain-driven design (DDD), especially its strategic design discipline, has proven to be a powerful tool for identifying and assessing possible decompositions. Although this mindset is well established in backend development, it offers significant value on the frontend as well.
In this article, I'll examine how bounded contexts and context mapping translate to frontend architecture, why different domain slices may be needed there, and how these concepts influence event storming, team organization, and implementation. I'll also walk through turning a strategic design into working frontend code and highlight important considerations along the way.
What Are We Trying to Achieve?
Before diving into specific DDD principles, it's useful to consider the broader objectives behind decomposing large systems. At its core, the aim is to create a structure that remains maintainable, comprehensible, and adaptable as complexity grows. A vertical, business-oriented partitioning has proven effective—such partitions follow business capabilities instead of technical layers like components or services.

I use the word vertical broadly, as opposed to horizontal layering, which separates responsibilities by technical function.
Typical goals of such verticals are:
-
Low Coupling: Verticals should be developed, tested, and deployed as independently as possible. This minimizes technical and organizational dependencies. Changes in one area can be made locally without causing unintended consequences elsewhere.
-
High Cohesion: Features that belong together are addressed and delivered together. A change to a use case stays within a consistent context, without spanning multiple layers or teams. This improves understanding, reduces error potential, and makes the system easier to modify.
-
Conway's Law: Conway noted that systems tend to mirror the communication patterns of the people who build them. Deliberately aligning software structure with team structure (Inverse Conway Maneuver) helps avoid friction, clarifies ownership, and enables autonomy—rather than fighting the organization. Concretely, this means one team should own each vertical (or several), and a vertical should never be shared across teams.
-
Enabling autonomous teams: A well-drawn vertical allows one team to take full responsibility for a slice of the system—both functionally and technically. This creates independent delivery units with clear ownership and fast feedback loops.
-
Reduce cognitive load: Working on a cohesive vertical means dealing with a clearly scoped functional and technical area. This boosts focus, productivity, and quality, particularly in complex systems.
These principles hold no matter which decomposition approach you choose. As we'll see shortly, DDD is especially helpful in spotting verticals that satisfy these criteria.
Strategic Design
DDD centers on domain knowledge and close collaboration with domain experts. Strategic Design—one of the two founding disciplines of DDD—offers concepts and tools for structuring complex systems along domain-relevant boundaries. In this section, I'll outline the benefits of strategic design before zeroing in on subdomains and bounded contexts.
Subdomains
Strategic Design breaks the business under analysis into subdomains to keep complexity manageable and to channel investment where it yields the greatest competitive advantage. A subdomain is a business area—an airline, for example, might distinguish booking, check-in, luggage, or boarding:

In large software systems, not everything carries equal weight. Some capabilities give you a decisive edge; others are just necessary overhead. Strategic design surfaces these priorities by sorting each subdomain into one of three buckets:
-
Core Domain: Provides the key competitive advantage. This is where most effort goes.
-
Supporting Subdomain: Supports the core domain without being a differentiator itself.
-
Generic Subdomain: Handles tasks that standard solutions can cover and that can be bought off the shelf. Examples include authentication, newsletter distribution, or helpdesk ticketing.
From Subdomains to Bounded Contexts
Whereas subdomains reflect the relevant areas of the "real world," bounded contexts define how those areas are represented in software. In other words, subdomains belong to the problem space, and bounded contexts to the solution space. As Vladik Khononov puts it in his O'Reilly book Learning Domain-Driven Design:
“Sub-domains are discovered; bounded contexts are designed.”
Each bounded context thus has its own domain model and its own ubiquitous language. The domain model ends up as the implementation in code. The ubiquitous language is the set of terms that must be used consistently within that context—in conversations, documentation, and code.
This makes a bounded context a linguistic boundary that keeps terminology crisp, prevents conflicting rules, and lets each team evolve its model independently. A quick look at the word "flight" shows why this matters: in the booking context, a flight is a sellable offer with attributes like fare class, price, and seat availability; in the boarding context, it's an operational process with gate, seat, and security status:

If both meanings were squeezed into one model, you'd end up with irrelevant fields and error-prone validations.
The integration between such contexts is often handled through domain events in the backend. Booking, for instance, publishes a TicketCancelled event. Boarding subscribes to it and removes the passenger from the list.
Bounded contexts are natural candidates for verticals. They're the first pick, especially in the core domain. For supporting subdomains, they also provide a solid basis for slicing—though for pragmatic reasons, you don't always have to adhere strictly. With purchased solutions for generic subdomains, you usually have limited influence over how they're partitioned.
But as we'll see below, other criteria for slicing exist beyond the preferred bounded context.
Relationship Between Bounded Contexts and Subdomains
Ideally, each subdomain is mapped to a separate bounded context, yielding a direct correspondence in the built system. Yet for technical or organizational reasons, it can be sensible to break a subdomain into multiple bounded contexts. This may happen if the subdomain is too large or complex for a single context, or if it contains distinct areas with their own languages.
However, purchased standard products like ERP systems—or "legacy" applications that never embraced DDD—often span several subdomains. In such cases, there's no clear vocabulary, and terminology gets muddled. This leads to high coupling and low cohesion—the exact opposite of what we're after. The anti-corruption layer, discussed below, helps shield our contexts from such systems.
Context Mapping
Context mapping makes the relationships between bounded contexts explicit. This helps teams see who depends on whom to succeed and to realistically estimate the effort for changes and integrations. Strategic Design describes several patterns for implementing these relationships. Two of them are illustrated in the following example:

The Booking context requires data from Accounting. Suppose Accounting is a purchased standard product without a clean ubiquitous language. To prevent Booking's model from being diluted, an Anti-Corruption Layer translates selected pieces of Accounting's model into Booking's terms. The ACL also centralizes handling of breaking changes coming from Accounting.
For the other subdomains, Booking exposes an Open/Host Service. This publishes selected information (ideally via events) and hides details of Booking's model from other contexts. Changes to Booking's model can thus stay contained within the context.
Another pattern in this category is the Shared Kernel. It's a model fragment shared by two or more bounded contexts. The upside is that duplication and mapping overhead disappear, since all teams use the same implementation. The downside is that every change requires coordination and couples the release cycles of the teams involved—when conflicts arise, the shared kernel quickly becomes a bottleneck.
Because of these drawbacks, shared kernels are discouraged whenever possible. If used at all, they should be kept as small as possible. Vlad Khononov stresses in his work on DDD that the shared kernel undermines the essence of the bounded context, so it demands solid justification. He mentions cases where the same team owns multiple contexts or where the shared kernel serves as an intermediate step when refactoring a legacy system that wasn't designed with DDD in mind.
The opposite of sharing is the Separate Ways pattern, where two contexts deliberately implement similar aspects independently. This reduces coordination overhead and makes sense when the implementations are already drifting apart over time.
There are additional patterns in this space that go beyond the scope of this article. A thorough overview can be found in this still-current and well-written article on InfoQ as well as in the DDD crew's repository, which also offers a tidy cheat sheet.
Sharing Technical Code is Beyond the Focus of DDD!
When discussing the Shared Kernel, it's worth noting that context mapping patterns concern domain-specific code. Sharing technical code—such as design systems—lies outside DDD's scope and is less critical, since we're generally dealing with more stable APIs.
Whether and how much to share is typically an architectural decision that weighs competing concerns, like coupling versus duplication and consistency. Such choices must be made deliberately on a case-by-case basis, balancing costs and benefits.
For this reason, using a monorepo doesn't contradict DDD—especially since modern tooling lets us declare which parts of the system may depend on which others. This makes it possible to express conscious decisions about sharing domain-specific code (in line with DDD principles) and technical code (based on other architectural considerations).
Identifying Good Context Boundaries
With the fundamentals of Strategic Design for domain slicing established, and knowing that Bounded Contexts make strong candidates for verticals, the next step is figuring out how to spot them. Fortunately, DDD provides several heuristics that can point toward distinct Bounded Contexts:
-
Same term, different meaning: When a word like passenger, booking, or flight carries different interpretations in different parts of the domain, this signals separate contexts. It's a sign that distinct ubiquitous languages are colliding.
-
Divergent models for the same concept: The same concept needs to be represented considerably differently in various areas, such as having different attributes.
-
Distinct responsibilities: Different responsibilities tend to generate different terms and models.
-
Pivotal Events: In his book on EventStorming, Alberto Brandolini suggests watching for critical moments in a process. These pivotal events represent decisive turning points, and such transitions often mark the edges of bounded contexts.
Examining vocabulary, semantics, and responsibilities is the more established route to identifying bounded contexts. Interestingly, pivotal events align with these concepts while also bringing additional useful viewpoints into the picture. The following section explores them more closely.
A Closer Look at Pivotal Events
A defining trait of pivotal events is a shift in perspective. This change can lead to different rules or a different vocabulary. It can also involve a meaningful change in state or responsibility, and may result in a temporal decoupling.
When booking flights, all of these criteria are present: The flight transitions to a booked state (state change), and responsibility moves to the airline. The airline now views the flight from a new angle. For instance, the various pricing options no longer matter for the later check-in and boarding steps, and different rules kick in if a cancellation occurs. For the passenger, the journey picks up weeks later at the airport (temporal decoupling).
A useful metaphor for grasping pivotal events is scene changes in a film: A significant part of the story is complete, and what follows will likely build on it. Responsibility can pass to other actors, and the new scene can continue the narrative at a different time.
Michael Plöd, a well-known German DDD expert, has explored further heuristics for pinpointing pivotal events. He has collected his insights in his article Finding the Turning Points.
It's worth remembering that the points outlined here for recognizing bounded contexts are heuristics, not hard rules. As the next section shows, it's entirely possible for different heuristics to come into conflict. This isn't necessarily a problem—it also indicates that there is often no single perfect answer.
When Heuristics Clash
The following figure illustrates some of the heuristics discussed earlier:

Here we can see different responsibilities, pivotal events (squares with arrows), and the key terms represented as icons.
Now let's attempt to define bounded contexts by applying the heuristics we've covered:
-
Terminology: Luggage in Check-in Luggage and Pickup Luggage refers to the same thing. Luggage might also appear in Booking, but with a different connotation: in that case, it's about purchasing the option to check a certain number of bags.
-
Pivotal Events: Among the events shown, there's a notable shift in perspective (passenger is now checked in, passenger is now boarded). This also introduces new rules, such as the need to unload luggage if a checked-in passenger doesn't show up for boarding.
-
Responsibilities: The airline agents handle the entire journey from check-in to boarding. This could suggest a unified view of the process.
Interestingly, the boundaries of the bounded context containing Check-in Luggage shift depending on which heuristic you follow. To be transparent, this example was deliberately constructed so the three heuristics contradict each other. My aim is to make the point that this shouldn't be cause for concern. In such situations, a deliberate decision is necessary. The goals set at the outset naturally come into play, along with an evaluation of the different consequences.
This scenario is quite typical in software architecture: there's usually no single right answer, but rather several options, each with its own trade-offs.
However, there's some good news: these decisions are rarely made in isolation. Instead, they are weighed together with the viewpoints and experiences of various experts. This aligns with the role of a modern architect, who doesn't see themselves as a decision-maker but as someone who ensures conscious choices are made after considering the alternatives.
An initial decision isn't permanent either. You gain initial experience with it and might choose to refactor later.
Event Storming, which we'll cover next, is an extremely popular workshop format that brings together the knowledge, perspectives, and experiences of different experts.
Before moving on to Event Storming, I'd like to stress that the approach discussed here moves the focus away from a purely technical or data-driven (static) perspective and toward the actual processes and interactions within the domain. Those who only examine static data—concepts and attributes—can easily miss the fact that the same information can have different meanings and requirements in different contexts. Looking at the process, on the other hand, reveals dynamics, role changes, and shifts in purpose—and thus those transitions where splitting into Bounded Contexts makes the most sense.
Further Reading: Angular Architecture Workshop (Remote, Interactive, Advanced)
Become an expert for enterprise-scale and maintainable Angular applications with our Angular Architecture workshop!

English Version | German Version
Event Storming
Collaboration with domain experts and the consistent development of deep domain knowledge are essential principles of DDD. Only those who genuinely understand the business can design sustainable systems and identify solutions that remain robust over the long term.
Event Storming, developed by Alberto Brandolini, is a popular interactive workshop format in the DDD environment. It gathers all participants—from domain experts to developers to UX designers—and lets them combine their knowledge. Using colored sticky notes, the domain is visualized step by step and laid out chronologically.
The focus is on domain events, represented by orange sticky notes. Each domain event describes the completion of a subsection—something that is finished and influences what happens next (e.g., flight booked or passenger checked in). Different perspectives can be discussed immediately on the spot. By placing these events together, a visual model emerges quickly that everyone can understand.
In the course of a so-called Big Picture Event Storming, the overall view is worked out. It is therefore also ideal for discussing context boundaries. The example used here could be presented as follows during such a workshop:

For better readability, I created this example on a computer. In practice, however, working together on-site is recommended, particularly because it fosters communication.
Parts that aren't relevant here are simply skipped using ellipses: further events that the participants discovered and additional process steps during the flight.
The following information can be derived from this illustration:
- Events: Orange Slip
- Pivotal Events: Orange notes with yellow, vertical subdivisions
- Swimlanes: Yellow horizontal subdivisions that show optional or parallel processes.
- Milestones: Blue notes at the top that divide the process into a few sections.
The milestones here are chosen to be coarse-grained for the sake of clarity. Further information that is typically placed alongside the domain events in the Big Picture:
- Actors (personas, users, etc.)
- External systems
- Hotspots (sensitive areas with open questions)
- Opportunities and potential
The individual subprocesses can be further elaborated in a subsequent event storming process. A good overview of the additional details discussed in such formats can be found in the DDD Crew Glossary.
For context definition, Brandolini lists several heuristics that partly overlap with those mentioned above. These include focusing on different usages of terms (language), pivotal events, and responsibilities (actors, personas, etc.). As is common in the DDD environment, he recommends not only focusing on nouns when considering term usage, but also paying attention to verbs, which often better reveal the respective purpose.
In addition to the heuristics already discussed, he introduces further ones. Interestingly, some of them are based on the experts' behavior during collaborative modeling:
-
Pay attention to the swimlanes: If separate lanes for independent processes emerge in the EventStorming timeline, this indicates independent Bounded Contexts.
-
Pay attention to the people in the room: Domain experts instinctively gather in the areas of their domain, correct notes, etc. Where people cluster or intervene frequently, there is usually a model boundary.
-
Pay attention to body language: Head shaking, eye rolling, or other nonverbal signals indicate hidden conflicts or hierarchy friction. Divergent needs are an indicator of separate models.
Using all the heuristics discussed so far, one could define the following context boundaries in the example used here:

Event Storming for the Angular Frontend?
A key principle of Event Storming is bringing together different perspectives. This applies to the front-end and UX aspects as well. Therefore, front-end developers, e.g. using Angular or React, and UX experts should also be involved. This can be achieved in two ways:
- Dedicated Event Stormings from the perspective of UX and/or frontend with focus on the user journey
- Integration of frontend and UX aspects into classic Event Stormings
To integrate UX aspects into traditional event stormings, e.g., at the process level, Brandolini suggests adding (UI) wireframes at the relevant points in the process. The manually sketched or prepared wireframes can be placed in the middle of the modeled process using adhesive tape.
To improve UX, Brandolini recommends capturing the mood and feelings of users in event storming in his presentation Transactions Redefined. Someone who has booked the wrong flight, for example, may be unsettled. However, if this person immediately sees that they can cancel the flight free of charge within a certain time frame, this has a positive impact on the user experience.
By explicitly incorporating the front-end perspective, it's easier to determine whether different slices are necessary in the front-end. This has several consequences, which I'll outline in the next section.
Alternative Frontend Decomposition Strategies
Across the projects I have observed, frontend decomposition frequently mirrors the backend structure quite closely:

This produces vertical slices that align well with the objectives outlined earlier:
- Minimal interdependencies
- Strong internal consistency
- Team structure that mirrors the technical landscape
- Independent, self-sufficient teams
- Lower mental overhead
Well-defined vertical slices also enable teams to deliver complete business value within a specific area from start to finish.
Yet, I have also encountered situations where teams intentionally adopt a different decomposition strategy for their frontend. One notable case involved a heavily regulated sector with complicated computations that required frequent updates to comply with changes in legislation. In contrast, the Angular interface handling the individual workflows was comparatively straightforward.
Under these circumstances, the ubiquitous language that exists within the backend must be adapted or simplified for the frontend — or at least for each distinct frontend slice. Techniques commonly used in context mapping offer valuable approaches for this translation.
Alberto Brandolini highlights this point in his insightful blog post Customer Journey as a Bounded Context. His example about booking event tickets is quite convincing: the user-facing view remains simple, yet several backend domains collaborate — ticketing, seat allocation, purchasing, and payment processing (which might follow a generic subdomain pattern). He observes that users often employ their own vocabulary and issues a caution:
"[ ... ] when DDD folks finally [ ... ] master the language complexity [ ... ], they can make the final mistake of exposing this complexity to the customer.
Vaughn Vernon also touches on this scenario in his well-regarded publication Implementing Domain-Driven Design, known for its strong pedagogical approach: if the frontend genuinely has a clearly articulated vocabulary, it constitutes its own simple ("cheap bargain") bounded context, typically devoid of business rules. He is careful, however, to differentiate this from a composite UI — the latter merely stitches together fragments from separate domains without producing a new, distinct language.
Setting aside the (for me, at most, secondary) debate about whether this qualifies as a separate Bounded Context, the discussion underscores how DDD thinking proves remarkably useful in frontend work: placing emphasis on vocabulary and meaning, utilizing collaborative techniques like EventStorming, and applying the heuristics we have covered to identify good candidates for slicing.
Nevertheless, adopting frontend slices that differ from the backend incurs certain drawbacks: the previously mentioned goals are partially sacrificed. If a dedicated team forms around the differently structured frontend, that team loses some autonomy and forfeits true end-to-end accountability.
I have witnessed instances where frontend developers stayed within teams aligned to backend structures while also contributing to a virtual group focused on a particular user journey. This approach does not resolve every issue, but it softens many of them. The example likewise reinforces that software architecture fundamentally revolves around weighing trade-offs and compromises.
From a technical standpoint, the question of where to perform the language translation arises naturally. One elegant answer is the Backend for Frontend (BFF) pattern:

This BFF sits physically within the backend layer, yet it conceptually belongs to the frontend and should ideally be owned by the frontend team. Beyond its role in translating between bounded contexts, a BFF provides additional benefits concerning caching, security practices, and observability. An alternative I have encountered involves handling orchestration directly within the frontend. That route sacrifices several of those gains and adds complexity to the client-side code. On the other hand, it removes the operational overhead of running and maintaining a separate BFF service.
Although the standard advice in these scenarios leans toward a BFF, the final choice must reflect the circumstances of the particular project. Once more, the essence lies in balancing benefits against drawbacks.
This discussion naturally leads us into the next part, which focuses on the concrete technical realization of strategic design principles within frontend applications.
The Language of the Interface
In various presentations, Alberto Brandolini has introduced the phrase surface language to distinguish the vocabulary used in the user interface from the ubiquitous languages found in backend systems. These vocabularies can diverge quite significantly in certain settings. Consider an e-commerce platform selling tickets for flights or trains: customers generally have little interest in the intricate logic behind fare calculation. Overloading them with such internal details would be counterproductive and could even deter them from completing a purchase. Another illustration is software that must adapt to regional requirements, such as a payroll system that needs to comply with the legal frameworks of multiple countries.
Other scenarios allow the two vocabularies to overlap considerably, and the frontend sometimes adopts the backend language directly. I have observed this in numerous internal business tools used predominantly by domain specialists.
An interesting insight for me is that a distinct surface language does not automatically demand different slice boundaries, and vice versa. Situations exist where you can cut all the way from the backend to the frontend, yet still require different languages — for instance, to localize for diverse markets or to abstract complexity for end users.
Conversely, even when frontend slices are organized differently, each slice can predominantly rely on the vocabulary of a single (primary) backend slice. For example, a catalog application for automotive replacement parts might closely mirror the language of the spare parts context in the backend. The frontend slice need not be concerned with the fact that vendor addresses, which are relevant for deliveries, originate from logic belonging to yet another backend domain.
Translating Strategic Design into Angular Code
Architecture decisions don't happen in isolation; eventually, they have to make their way into actual source code. That holds true for strategic design as well. While the nitty-gritty details of implementation go beyond DDD's core focus, it's highly beneficial that the choices we've made — such as context boundaries and the deliberate communication rules between contexts — be mirrored as clearly as possible in the codebase: Team members should immediately recognize which context they're working within, what they can rely on from neighboring contexts, and precisely where the dividing lines fall.
Based on my experience, there are several ways to represent Bounded Contexts within Angular and frontend projects:
- Folders as a context
- Libraries as a context
- Applications as a context
The initial two approaches yield a monolithic application (often referred to simply as a monolith), whereas the final one leads toward a micro frontend setup.
As a rule, I lean toward a solution that’s minimally complex while still being effective. There's no reason to tackle issues we don't actually have. If plain folder structures meet your needs, consider that a fortunate situation!
Enforcing Boundaries in Modular Monoliths
In monolithic applications, keeping context boundaries intact requires ongoing effort, since it’s all too simple to cross them with imports from another feature area. Tools like Nx and Sheriff step in here, offering linting rules to uphold Bounded Contexts. Nx enforces these rules at the library level within a monorepo, while Sheriff targets the folder level.
Both tools aren't limited to one framework. Nx, for example, provides ready-made support for Angular and React, and Sheriff is compatible with any stack that runs on TypeScript.
Nx and Sheriff both allow you to assign tags to discrete units like libraries or folders. These tags can represent Bounded Contexts, but they may also encapsulate finer-grained elements like feature modules or layers within those contexts. From these tags, you can formulate import restrictions, for instance:
- The booking feature is restricted to importing from Booking and Shared only
- The boarding feature is restricted to importing from Boarding and Shared only
This arrangement prevents Booking from reaching into Boarding, and vice versa. Additional rules are typically needed since granting every part of Booking access to the entire Shared section would be too permissive.
While the Shared area might house shared kernels in the DDD sense (though they don't necessarily have to live there), I want to stress the earlier caution: this pattern warrants careful deliberation, and exploring other options is wise.
More often, that shared space is devoted to generic technical code. Obviously, we want to guard against it turning into a bloated, catch-all module. That's why this area is frequently partitioned further into distinct sub-modules. Moreover, decisions about exactly what gets exposed through a public API need careful thought. These considerations extend beyond DDD, yet they remain essential when breaking your strategic design into concrete code.
Attempting to import from an unauthorized context triggers a linter warning. The illustration below demonstrates the outcome when someone in the ticketing context tries to import from the check-in context:

For rapid feedback, this validation runs right in the IDE. It's also wise to promote these linting checks into the build process, ensuring non-compliant code is rejected automatically.
This tooling also paves the way for implementing the context mapping patterns we covered earlier:
- Open/Host Service: Linting constraints dictate that one domain may interact only with a clearly-defined "API" — perhaps a service or a barrel file — exposed by another domain.
- Anti-Corruption Layer: The rules limit access to a given domain exclusively through its designated ACL.
I've written about using Sheriff in this blog post, and my thoughts on Nx are available here. The Nx team has also shared a recent article on this subject.
Through these rules, the conscious decisions from your strategic design — plus any technical-driven ones — can be stated explicitly within the code, for instance, in an Angular application. This approach enforces the intended modularization, which is why these monolithic setups are often dubbed "Moduliths," shorthand for modular monoliths.
The term Modulith serves to set this architecture apart from the Micro Frontend pattern discussed next.
Micro Frontends at a Glance
Micro Frontend Architectures involve creating (more or less) independent frontends — be they Angular or React applications — that individual teams can develop and deploy on their own schedule. These frontends operate without tight coupling, essentially following a shared-nothing principle, though pragmatic reasons might lead to some elements being shared.
Micro Frontends are very effective at hitting the foundational criteria — low coupling, high cohesion, autonomous teams, and alignment with Conway's Law. The Bounded Contexts we've identified also make excellent candidates for Micro Frontends (paralleling how they map to Micro Services on the backend). Not surprisingly, DDD has experienced a notable resurgence with the rise of these architectures.
To create a seamless experience for the user, the individual Micro Frontends must be assembled at runtime. The simplest integration method is via hyperlinks. For a tighter, more embedded integration, a common pattern is to load the Micro Frontends within a host shell application:

Indeed, Micro Frontends come with their own set of hurdles, particularly when working with single-page applications that run in the browser. Key difficulties include:
- Heavier bundles: Each micro frontend gets built independently, and eventually, all that code must be loaded into the shell.
- Version conflicts: Different Micro Frontends might rely on different technologies or versions, leading to collisions.
- Styling consistency: With autonomy comes the challenge of achieving uniform branding and styles.
Perhaps the biggest constraint is that mainstream SPA frameworks didn't originate with Micro Frontends in mind. Angular's compilation process, for instance, works best when all code is compiled and optimized together to reduce bundle sizes.
The silver lining here is that each of these issues can be addressed. We've assisted many organizations — from major banks and insurance firms to automotive and industrial enterprises — in navigating this landscape. However, this typically calls for a small, focused platform team to furnish guidelines and helper libraries.
Given this, it's prudent to weigh whether the added complexity is justified. Generally, we'd suggest this path when multiple independent teams contribute to a larger product and a monorepo isn't practical due to technical or organizational barriers. The principles here are also valuable when designing plugin systems, such as for SaaS platforms that need to be customized per tenant.
In a post co-authored with the Angular team, I detail these points and introduce Native Federation, a technology for integrating Micro Frontends into a shell.
For a deeper dive into the technical specifics — including guidance on mixing frameworks and versions — check out this article series.
When Micro Frontends are aligned with Bounded Contexts, achieving low coupling becomes straightforward: the applications are inherently isolated. When functionality needs to be shared, say via an open/host service, you can enable inter-frontend communication through various methods, such as:
- A lightweight service bus in the browser
- Plain objects on the global namespace
- Sharing contracts and tokens via npm packages or dependency injection
Moreover, Native Federation permits code sharing among individual Micro Frontends.
Wrapping Up
Large-scale frontend systems thrive when their structure is guided by business needs. Rather than organizing around technical layers, the objective is to assemble functional units marked by high coherence, minimal coupling, and clear ownership. DDD concepts, especially those from Strategic Design — subdomains, Bounded Contexts, and context mapping — are instrumental in pinpointing and weighing the candidates for these partitions.
Event Storming proves exceptionally useful here: it's a collaborative workshop method that brings business processes into focus and reveals contextual boundaries, with an eye toward UX and the frontend. It clarifies whether the frontend needs its own distinct slicing or can shadow the backend's structure. Once these choices are made, they can be codified and policed using tools such as Nx or Sheriff. Micro Frontends, in turn, provide a path to technical decoupling that boosts team autonomy. The outcome is a frontend architecture that is both domain-centric and sustainable over the long haul.
