DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • AI Agents Expose a Design Gap in Microservices Resilience Architecture
  • Cloud Migration of Microservices: Strategy, Risks, and Best Practices
  • Monolith vs Microservices vs Modulith
  • A Comprehensive Analysis of Async Communication in Microservice Architecture

Trending

  • Building a High-Throughput Distributed Sequence Generator Using the Hi-Lo Algorithm
  • Why Your AI Agent's Logs Aren't Earning Trust
  • Zero-Downtime Deployments for Java Apps on Kubernetes
  • Beyond Manual Annotation: Engineering Self-Correcting Pseudo-Labeling Pipelines
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Micro Frontends to Microservices: Orchestrating a Truly End-to-End Architecture

Micro Frontends to Microservices: Orchestrating a Truly End-to-End Architecture

Aligning the front end and back end through domain-driven ownership, BFFs, and contract-first APIs to achieve a scalable, resilient architecture.

By 
Mohit Menghnani user avatar
Mohit Menghnani
DZone Core CORE ·
Jul. 08, 25 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
2.6K Views

Join the DZone community and get the full member experience.

Join For Free

Modular architecture is everywhere in modern app development, but it is rarely done systematically.

You may have created micro frontends or microservices, but the process of integrating them might feel alien or disjointed. This article aims to bridge that gap by seamlessly connecting React shells down to service meshes utilizing proven patterns. 

By the end of this article, I hope you will be able to design systems that achieve limitless scalability while avoiding dependency hell.

Why “Truly End-to-End” Matters

Picture this: Your front-end team deploys a micro frontend "User Profile." The backend termed its responsible piece as a "User Service." All seems aligned so far. But hold on — what happens when your front end needs to show a specific field? Let's call it user.preferredTheme. 

The front-end team requests that the backend team expose the field, but their sprint is already packed, and they can't prioritize it. Now, you are stuck — like countless others — in a state of misalignment, where modular systems break down. If the front-end and back-end boundaries don't align clearly, modularization turns into a nightmare with unclear ownership and cross-team dependencies (sometimes even across different orgs).

So, how do we avoid this nightmare where teams can move fast without blocking each other? 

Let's look at some of the foundational concepts of a truly end-to-end architecture:

  • Consistent Domain Boundaries: User, payments, catalog map from UI all the way through to the database.
  • Vertical Slice Ownership: Full vertical spans across slices, enabling teams to own end-to-end processes front and back end for their domain slice.
  • Isolated Change Impact: When changes are well isolated or contained, a problem in one system should not affect other unrelated parts

Sounds too good to be true? Let’s walk through how this could be achieved.

1. Maintaining Consistent Domain Boundaries From React Shell to Service Mesh

The Problem: Fragmented Boundaries Lead to Disordered Systems

Let's assume we have a cart micro-frontend that requires catalog information, user information, and payment details. All three details come from different sources, which map to individual teams with their own release timelines. 

A small change to the User API breaks the cart UI. Why? Because the cart is dependent on the User API. This is a classic example of Conway's Law. Your organization chart is being mirrored in the system's architecture. 

The Fix: Consistency Through Domain-Aligned Interfaces 

a. Not Just Contracts but a Shared Terminology

We should have clear terminology across domains. Before writing code, we should define what the user domain means. For example, users have profile data, authentication preferences, and notification settings.

b. BFF (Back End for Front End), Your Secret Weapon for Alignment

Micro frontends should interface with their domains' tailored backend services instead of calling all services directly. 

For instance, the UserProfile micro frontend interacts solely with the User-BFF service, which brings together data from User-Service and Notification-Service (now aggregated preferences).

JavaScript
 
// User-BFF endpoint (Node.js + Express)  
app.get('/user-profile/:id', async (req, res) => {  
  const user = await UserService.get(req.params.id);  
  const notifications = await NotificationService.get(user.id);  
  res.json({ ...user, notifications }); // Single contract for the frontend!  
}); 
Why This Works: The front end is shielded from backend divisions and changes. Only the BFF owner needs to be in the know for NotificationService changes.

c. Team Topology: Reflect Domain Boundaries

The teams should be structured around domains instead of the tech stacks

Group 'User Domain' as:
Frontend -- "UserProfile" Microfrontend
Backend -- "User-Service" and "User-BFF" services

If a team, “Team A,” owns Payments, then they own the entire process, starting from the React component to the payment database.

Tools and Techniques

1. Micro frontends per domain can be federated using Module Federation (Webpack).

JavaScript
 
// Module redevelopment takes place at user level where microfrontend would expose the UserProfile component.
new ModuleFederationPlugin({
name: "userApp",
exposes: { "./UserProfile": "./src/components/UserProfile" }
});


2. With a service mesh like Istio or Linkerd, traffic can be routed using domain labels such as user-domain, payment-domain.

3. With dependency heatmaps, we can detect cross-domain service calls, highlighting boundary violations.

2. API Contracts Patterns That Survive Org Churn

The Problem: Declining Documentation Standards or Documentation Rot

The integration with your payment service is working smoothly, and suddenly there is a re-org. The previous team starts introducing new changes, which are not communicated well, leading to subtle incompatibilities. Eventually, things start spiraling down, and before you know it, versioning becomes the go-to solution. At best, it is a band-aid, and we are accumulating complexity instead of addressing it.

The Fix: Contract-First Development 

Step 1: Design first, code later

Use standards like OpenAPI to define the endpoint and schema first. For example, the following is the schema for a GET on User Profiles

JavaScript
 
paths:  
  /user-profile/{id}:  
    get:  
      responses:  
        200:  
          content:  
            application/json:  
              schema:  
                type: object  
                properties:  
                  name: { type: 'string' }  
                  email: { type: 'string' } 


Step 2: CI verifies every PR with the contract or pre-defined schema
JavaScript
 
# CI pipeline
npx spectral lint user-profile-schema.yaml # Fails if contract broken


Proven Patterns for Resilient Contracts

a. Consumer-Driven Contracts (CDC)

With tools like Pact, the front-end and the back-end teams come to an agreement and define the expected behaviors beforehand

Example:

JavaScript
 
// UserProfile.spec.js (Pact test)
provider
.uponReceiving('a request for user profile')
.withRequest({ method: 'GET', path: '/user-profile/123' })
  .willRespondWith({ status: 200, body: { name: 'Alex' } });

The backend must run these tests against their changes, and it would fail if the response does not match the expectation.

b. Endpoints Facades

This is great, but how do we handle the legacy service? For those, we can introduce a facade layer:

New Frontend → Facade (clean contract) → Legacy Service 

The facade acts as a stable, clear gateway that protects new users from complex backend details, letting them interact with a straightforward system.

c. Feature Flags and Contract Tests

How do we change an API field?

  1. You can add the new field and continue to use the old field.
  2. Toggle the usage on the front end using feature flags.
  3. Validate contract tests for both fields.
  4. Once all consumers have migrated, you can safely remove the old field.

Case Study: The Unbreakable User-Profile API

I worked in a team where we standardized the practices for the User Profile API:

  1. Pact-based contract tests
  2. Open API schemas
  3. Rollouts were supported behind a feature flag

With changes over two years, which involved:

  • Three team re-orgs
  • A React front-end rewrite and migration
  • 4 backend service migrations 

There were no breaking changes made during this time, since every change required:

  • Pact verification
  • Schema linting
  • Feature-flagged rollouts

3. Real-World Problems Encountered and Fixed After Battletesting Them

Problem 1: Many Layers, Not Enough Ownership

Symptoms: Let's assume we need to introduce a "simple" change across five repositories in parallel. It's always a point of contention which team will own the change and how we split the work. 

Solution: Adopt single-threaded ownership split per vertical. If Team A owns the "search" vertical, they manage everything end-to-end:

  • SearchBar micro frontend
  • SearchService backend
  • SearchIndex database
  • Testing, Monitoring, etc.

Problem 2: Fragmented Observability

Symptoms: Let's assume we receive a 500 error, and the payment fails during checkout. The engineer looks at the front-end logs but cannot find the error. Also, there is no trace ID for request context in the logs, making it difficult to map it to the backend/DB logs.

Solution: Implement end-to-end tracing using trace IDs.

Example:

  1. On user interaction, generate a trace ID at the front end.

// React component  
const traceId = generateTraceId();
fetch(`/checkout`, { headers: { 'X-Trace-ID': traceId } });

  1. Pass this ID through every service (BFF, Upper bound smaller than microservices/DB).

  2. With OpenTelemetry, the entire journey can be visualized end-to-end.

[Checkout MFE] → [Checkout-BFF] → [PaymentsService] → [Database] 

Problem 3: Federation/Mesh Overzealous Configuration

Symptoms: During the build process of your micro frontend shell, you experience a five-minute delay, and your service mesh YAML files are chaotic.

Solution: Focus on practical solutions first.

  • Micro frontends:

    • For frequently used features, use build-time composition (like module federation)
    • For rarely used features, use run-top composition
  • Service mesh: 

    • The routing should be centralized only for shared items (auth, logging, etc.).

Bonus: Composition — Trade-Offs

  • Build Time (e.g., Module Federation)

    • Lower latency enhances system performance.
    • Slower builds
  • Run-Time (e.g., dynamic imports):

    • Reduced time dependency allowing static MFE deployments. Flexible loading
    • Increased latency during initial load.

Guiding Principle

We should use build-time composition for critical features like headers and carts, where performance matters. For rarely used or admin-only panels, we should use runtime composition using dynamic imports or dynamic loading.

In Conclusion: Discipline Wins

Unity may define the architecture's ambition, but it's the modular structure and the disciplined approach that determine whether the system evolves at scale or becomes more chaotic.

Frequently Asked Questions

How many micro frontends is too many?

If your app shell takes over three minutes to build, or teams frequently bump up versions or update shared dependencies, that means it’s been overly split. Start with three to five core domains like product, cart, and user.

Do I need a service mesh?

A service mesh is only required if there are more than 10 services, or advanced traffic routing such as canary deployments, circuit breakers, or retries. For smaller applications, an API gateway such as NGINX works great.

How do I sell domain-driven design to my leadership?

Estimate the time wasted managing intra-team silos, cross-team coordination and collaboration. Demonstrate how owning verticals streamlines workflows and reduces deployment bottlenecks by 30-50%.

Can I use GraphQL instead of BFF?

Absolutely! That’s one of the GraphQL benefits as a BFF layer. However, make sure that each domain has its own GraphQL schema per domain to avoid a monolithic API.

What’s the biggest mistake people make with contract testing?

Mistakes include assuming all scenarios where tests will run smoothly are valid without any issues (testing only happy paths). Edge cases, including but not limited to 400s and timeouts, need to be accounted for, or real issues may not be detected.

Architecture Requirements engineering microservices

Opinions expressed by DZone contributors are their own.

Related

  • AI Agents Expose a Design Gap in Microservices Resilience Architecture
  • Cloud Migration of Microservices: Strategy, Risks, and Best Practices
  • Monolith vs Microservices vs Modulith
  • A Comprehensive Analysis of Async Communication in Microservice Architecture

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook