Skip to content

Commit 2656d8e

Browse files
Add Backends For Frontends pattern (#300)
Implements the BFF pattern with two client-specific gateways (MobileBff, DesktopBff) aggregating shared downstream services (AuthService, CartService, OrderService, SupplierService) into client-tailored response shapes. Closes #300
1 parent b55fc2b commit 2656d8e

33 files changed

Lines changed: 1730 additions & 0 deletions

backends-for-frontends/README.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
title: "Backends For Frontends Pattern in Java: Tailoring APIs to Client Needs"
3+
shortTitle: Backends For Frontends
4+
description: "Learn the Backends For Frontends (BFF) design pattern in Java. Understand how to give each client type its own dedicated backend service, with real-world examples, code, and diagrams."
5+
category: Architectural
6+
language: en
7+
tag:
8+
- API design
9+
- Architecture
10+
- Client-server
11+
- Decoupling
12+
- Microservices
13+
---
14+
15+
## Also known as
16+
17+
* Backend For Frontend
18+
* BFF Pattern
19+
20+
## Intent of Backends For Frontends Pattern
21+
22+
Provide each client-side application (mobile, desktop, chatbot, and so on) with its own dedicated
23+
backend service, so every client gets an API shaped exactly for its own needs instead of sharing
24+
one general-purpose backend with every other client.
25+
26+
## Detailed Explanation of Backends For Frontends Pattern with Real-World Examples
27+
28+
Real-world example
29+
30+
> Imagine a retail company whose mobile app, desktop back-office tool, and support chatbot all
31+
> need customer, cart, order and supplier data -- but a phone screen wants a short summary while
32+
> the back-office desktop tool wants full order and stock detail. Rather than exposing one shared
33+
> API that every client has to filter or over-fetch from, the company stands up a small BFF service
34+
> for the mobile clients and a separate BFF service for the intranet clients. Each BFF calls only
35+
> the downstream microservices its client needs and returns a payload shaped for that client.
36+
37+
In plain words
38+
39+
> Give every kind of client its own tailor-made backend, instead of forcing all clients through one
40+
> one-size-fits-all API.
41+
42+
Sam Newman, who popularized the pattern, says
43+
44+
> Create separate backend services to be consumed by specific frontend applications or interfaces.
45+
46+
## Architecture Diagram
47+
48+
```
49+
node mobile{
50+
component iosapp as "ios app"
51+
component androidapp as "android app"
52+
}
53+
node intranet{
54+
component desktop as "desktop app"
55+
component chatbot
56+
}
57+
component bff as "BFF server"{
58+
component iosbff as "ios BFF"
59+
component androidbff as "android BFF"
60+
component chatbotbff as "chatbot BFF"
61+
component desktopbff as "desktop BFF"
62+
}
63+
node intranetserv as "intranet services server"{
64+
component ss as "supplier service API"
65+
}
66+
cloud onlypublic as "public cloud"{
67+
component cas as "customer authentication service API"
68+
component cs as "cart service API"
69+
}
70+
cloud cloudserv as "managed cloud"{
71+
component os as "order service API"
72+
}
73+
iosapp -- iosbff
74+
androidapp -- androidbff
75+
chatbot -- chatbotbff
76+
desktop -- desktopbff
77+
iosbff -- cas
78+
androidbff -- cas
79+
iosbff -- cs
80+
androidbff -- cs
81+
iosbff -- os
82+
androidbff -- os
83+
chatbotbff -- os
84+
desktopbff -- os
85+
chatbotbff -- ss
86+
desktopbff -- ss
87+
```
88+
89+
This example implements a simplified version of the diagram above with two client-facing BFFs
90+
instead of four, to keep the demo focused: a **Mobile BFF** standing in for the ios/android BFFs,
91+
and a **Desktop BFF** standing in for the desktop/chatbot BFFs. Both call into the same shared
92+
downstream services (`AuthService`, `OrderService`), while `CartService` is only used by the
93+
Mobile BFF and `SupplierService` is only reachable from the Desktop BFF, matching the fan-out
94+
shown in the diagram.
95+
96+
## Class Diagram
97+
98+
![Backends For Frontends class diagram](./etc/backends-for-frontends.png)
99+
100+
## When to Use the Backends For Frontends Pattern in Java
101+
102+
* Different client types (mobile, web, desktop, voice/chat) need meaningfully different shapes,
103+
granularity, or aggregation of the same underlying data.
104+
* A single shared API has grown a large number of client-specific conditional branches, optional
105+
fields, or query parameters to accommodate every consumer.
106+
* Different client teams need to iterate on their own API independently without coordinating
107+
changes through one shared backend team.
108+
* Some clients (e.g. mobile) need aggressively trimmed payloads for bandwidth/latency reasons,
109+
while others (e.g. an internal desktop tool) need much richer data.
110+
111+
## Benefits and Trade-offs of Backends For Frontends Pattern
112+
113+
Benefits:
114+
115+
* Each client gets an API optimized for its own needs, improving performance and simplicity on
116+
the client side.
117+
* Client teams can evolve their BFF independently, reducing cross-team coordination.
118+
* Downstream microservices stay generic and reusable; client-specific logic lives in the BFF
119+
layer instead of leaking into shared services.
120+
121+
Trade-offs:
122+
123+
* Introduces additional services to build, deploy, and operate.
124+
* Logic that is genuinely shared across clients can end up duplicated across BFFs if not
125+
carefully factored out.
126+
* Adds an extra network hop between the client and the downstream services.
127+
128+
## How to Implement Backends For Frontends Pattern in Java
129+
130+
1. Identify the distinct client types that need meaningfully different data shapes.
131+
2. Define the downstream services each client's data actually depends on (`AuthService`,
132+
`CartService`, `OrderService`, `SupplierService` in this example).
133+
3. Create one BFF per client type, implementing a shared `ClientBff<T>` contract, where each BFF
134+
only depends on the downstream services its client needs.
135+
4. Have each BFF aggregate and reshape the downstream data into a response DTO tailored to its
136+
client (`MobileDashboardResponse`, `DesktopDashboardResponse`).
137+
5. Wire the client applications to call their own BFF rather than the downstream services
138+
directly.
139+
140+
## Source Code
141+
142+
* [Pattern: Backends For Frontends](https://samnewman.io/patterns/architectural/bff/) by Sam Newman
143+
* [Microservices Patterns: With examples in Java](https://www.amazon.com/Microservices-Patterns-examples-Chris-Richardson/dp/1617294543) by Chris Richardson
144+
145+
## References and Credits
146+
147+
* [Building Microservices](https://www.oreilly.com/library/view/building-microservices-2nd/9781492034018/) by Sam Newman
148+
* [Pattern: Backend for frontend (microservices.io)](https://microservices.io/patterns/apigateway.html)
133 KB
Loading
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
@startuml
2+
package com.iluwatar.bff {
3+
class App {
4+
- LOGGER : Logger {static}
5+
- USER_ID : String {static}
6+
- PRODUCT_ID : String {static}
7+
- DEMO_PRICE_USD : double {static}
8+
- DEMO_STOCK_LEVEL : int {static}
9+
+ App()
10+
+ main(args : String[]) {static}
11+
}
12+
}
13+
package com.iluwatar.bff.bff {
14+
interface ClientBff<T> {
15+
+ getDashboard(userId : String) : T {abstract}
16+
}
17+
class DesktopBff {
18+
- authService : AuthService
19+
- orderService : OrderService
20+
- supplierService : SupplierService
21+
+ DesktopBff(auth : AuthService, orders : OrderService, suppliers : SupplierService)
22+
+ getDashboard(userId : String) : DesktopDashboardResponse
23+
}
24+
class MobileBff {
25+
- MAX_RECENT_ORDERS : int {static}
26+
- authService : AuthService
27+
- cartService : CartService
28+
- orderService : OrderService
29+
+ MobileBff(auth : AuthService, cart : CartService, orders : OrderService)
30+
+ getDashboard(userId : String) : MobileDashboardResponse
31+
}
32+
}
33+
package com.iluwatar.bff.dto {
34+
class DesktopDashboardResponse {
35+
- greeting : String
36+
- loyaltyTier : String
37+
- orderStatuses : List<String>
38+
- supplierStockSummaries : List<String>
39+
+ DesktopDashboardResponse(greeting : String, loyaltyTier : String, orderStatuses : List<String>, supplierStockSummaries : List<String>)
40+
}
41+
class MobileDashboardResponse {
42+
- greeting : String
43+
- cartItemCount : int
44+
- cartTotalUsd : double
45+
- recentOrderSummaries : List<String>
46+
+ MobileDashboardResponse(greeting : String, cartItemCount : int, cartTotalUsd : double, recentOrderSummaries : List<String>)
47+
}
48+
}
49+
package com.iluwatar.bff.model {
50+
class CartItem {
51+
- product : Product
52+
- quantity : int
53+
+ CartItem(product : Product, quantity : int)
54+
+ lineTotal() : double
55+
}
56+
class Order {
57+
- id : String
58+
- productName : String
59+
- status : String
60+
+ Order(id : String, productName : String, status : String)
61+
}
62+
class Product {
63+
- id : String
64+
- name : String
65+
- priceUsd : double
66+
+ Product(id : String, name : String, priceUsd : double)
67+
}
68+
class SupplierRecord {
69+
- productId : String
70+
- supplierName : String
71+
- stockLevel : int
72+
+ SupplierRecord(productId : String, supplierName : String, stockLevel : int)
73+
}
74+
class User {
75+
- id : String
76+
- displayName : String
77+
- loyaltyTier : String
78+
+ User(id : String, displayName : String, loyaltyTier : String)
79+
}
80+
}
81+
package com.iluwatar.bff.service {
82+
interface AuthService {
83+
+ getUser(userId : String) : User {abstract}
84+
}
85+
interface CartService {
86+
+ getCart(userId : String) : List<CartItem> {abstract}
87+
}
88+
interface OrderService {
89+
+ getOrders(userId : String) : List<Order> {abstract}
90+
}
91+
interface SupplierService {
92+
+ getSupplierRecords(productId : String) : List<SupplierRecord> {abstract}
93+
}
94+
}
95+
package com.iluwatar.bff.service.impl {
96+
class InMemoryAuthService {
97+
- users : Map<String, User>
98+
+ InMemoryAuthService(userData : Map<String, User>)
99+
+ getUser(userId : String) : User
100+
}
101+
class InMemoryCartService {
102+
- cartsByUserId : Map<String, List<CartItem>>
103+
+ InMemoryCartService(carts : Map<String, List<CartItem>>)
104+
+ getCart(userId : String) : List<CartItem>
105+
}
106+
class InMemoryOrderService {
107+
- ordersByUserId : Map<String, List<Order>>
108+
+ InMemoryOrderService(orders : Map<String, List<Order>>)
109+
+ getOrders(userId : String) : List<Order>
110+
}
111+
class InMemorySupplierService {
112+
- recordsByProductId : Map<String, List<SupplierRecord>>
113+
+ InMemorySupplierService(records : Map<String, List<SupplierRecord>>)
114+
+ getSupplierRecords(productId : String) : List<SupplierRecord>
115+
}
116+
}
117+
DesktopBff ..|> ClientBff
118+
MobileBff ..|> ClientBff
119+
DesktopBff --> "-authService" AuthService
120+
DesktopBff --> "-orderService" OrderService
121+
DesktopBff --> "-supplierService" SupplierService
122+
MobileBff --> "-authService" AuthService
123+
MobileBff --> "-cartService" CartService
124+
MobileBff --> "-orderService" OrderService
125+
InMemoryAuthService ..|> AuthService
126+
InMemoryCartService ..|> CartService
127+
InMemoryOrderService ..|> OrderService
128+
InMemorySupplierService ..|> SupplierService
129+
CartItem --> "-product" Product
130+
@enduml

backends-for-frontends/pom.xml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
5+
6+
The MIT License
7+
Copyright © 2014-2022 Ilkka Seppälä
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
-->
28+
<project xmlns="http://maven.apache.org/POM/4.0.0"
29+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
30+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
31+
<modelVersion>4.0.0</modelVersion>
32+
<parent>
33+
<groupId>com.iluwatar</groupId>
34+
<artifactId>java-design-patterns</artifactId>
35+
<version>1.26.0-SNAPSHOT</version>
36+
</parent>
37+
38+
<artifactId>backends-for-frontends</artifactId>
39+
40+
<dependencies>
41+
<dependency>
42+
<groupId>org.junit.jupiter</groupId>
43+
<artifactId>junit-jupiter-engine</artifactId>
44+
<scope>test</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.slf4j</groupId>
48+
<artifactId>slf4j-api</artifactId>
49+
</dependency>
50+
</dependencies>
51+
52+
<build>
53+
<plugins>
54+
<plugin>
55+
<groupId>org.apache.maven.plugins</groupId>
56+
<artifactId>maven-jar-plugin</artifactId>
57+
<configuration>
58+
<archive>
59+
<manifest>
60+
<mainClass>com.iluwatar.bff.App</mainClass>
61+
</manifest>
62+
</archive>
63+
</configuration>
64+
</plugin>
65+
</plugins>
66+
</build>
67+
</project>

0 commit comments

Comments
 (0)