Skip to content

Commit 9f2129f

Browse files
committed
Add Server-Side Page Fragment Composition pattern #2697
- Implement complete Server-Side Page Fragment Composition design pattern - Add microservices for header, content, and footer fragments - Create composition service for server-side page assembly - Include comprehensive documentation and examples - Add unit tests with 17 test cases covering all functionality - Support both synchronous and asynchronous composition - Include caching, personalization, and performance monitoring - Follow project contribution guidelines and coding standards Closes #2697
1 parent ede37bd commit 9f2129f

File tree

17 files changed

+2311
-0
lines changed

17 files changed

+2311
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
<module>serialized-lob</module>
212212
<module>servant</module>
213213
<module>server-session</module>
214+
<module>server-side-fragment-composition</module>
214215
<module>service-layer</module>
215216
<module>service-locator</module>
216217
<module>service-stub</module>
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
---
2+
title: Server-Side Page Fragment Composition
3+
category: Architectural
4+
language: en
5+
tag:
6+
- Microservices
7+
- Web development
8+
- Scalability
9+
- Performance
10+
- Composition
11+
---
12+
13+
## Intent
14+
15+
Compose web pages from various fragments that are managed by different microservices, enabling modular development, independent deployment, and enhanced scalability.
16+
17+
## Explanation
18+
19+
Real-world example
20+
21+
> Consider a modern e-commerce website where different teams manage different parts of the page. The header team manages navigation and branding, the product team manages the main content area, and the marketing team manages the footer with promotions. Using Server-Side Page Fragment Composition, each team can develop, deploy, and scale their components independently while the composition service assembles them into cohesive pages.
22+
23+
In plain words
24+
25+
> Server-Side Page Fragment Composition allows you to build web pages by combining fragments from different microservices on the server side, promoting modularity and independent scaling.
26+
27+
Wikipedia says
28+
29+
> Server-side composition involves assembling a complete webpage from fragments rendered by different services on the server before sending the result to the client. This approach optimizes performance and enables independent development of page components.
30+
31+
## Programmatic Example
32+
33+
In our Server-Side Page Fragment Composition implementation, we have different microservices responsible for different page fragments:
34+
35+
```java
36+
// Create microservices
37+
var headerService = new HeaderService();
38+
var contentService = new ContentService();
39+
var footerService = new FooterService();
40+
41+
// Create composition service
42+
var compositionService = new CompositionService();
43+
compositionService.registerService("header", headerService);
44+
compositionService.registerService("content", contentService);
45+
compositionService.registerService("footer", footerService);
46+
47+
// Compose complete page
48+
var completePage = compositionService.composePage("home");
49+
```
50+
51+
Each fragment service manages its own rendering logic:
52+
53+
```java
54+
public class HeaderService {
55+
public String generateFragment(PageContext context) {
56+
LOGGER.info("HeaderService: Processing request for page {}", context.getPageId());
57+
return headerFragment.render(context);
58+
}
59+
}
60+
```
61+
62+
The fragments implement a common interface:
63+
64+
```java
65+
public interface Fragment {
66+
String render(PageContext context);
67+
String getType();
68+
int getPriority();
69+
default boolean isCacheable() { return true; }
70+
default int getCacheTimeout() { return 300; }
71+
}
72+
```
73+
74+
Each fragment renders its specific portion of the page:
75+
76+
```java
77+
public class HeaderFragment implements Fragment {
78+
@Override
79+
public String render(PageContext context) {
80+
var userInfo = context.getUserId() != null
81+
? String.format("Welcome, %s!", context.getUserId())
82+
: "Welcome, Guest!";
83+
84+
return String.format("""
85+
<header class="site-header">
86+
<h1>%s</h1>
87+
<nav><!-- navigation links --></nav>
88+
<div class="user-info">%s</div>
89+
</header>
90+
""", context.getTitle(), userInfo);
91+
}
92+
}
93+
```
94+
95+
The composition service orchestrates the assembly:
96+
97+
```java
98+
public class CompositionService {
99+
public String composePage(String pageId) {
100+
var context = createPageContext(pageId);
101+
102+
// Fetch fragments from microservices
103+
var headerContent = headerServices.get("header").generateFragment(context);
104+
var mainContent = contentServices.get("content").generateFragment(context);
105+
var footerContent = footerServices.get("footer").generateFragment(context);
106+
107+
return pageComposer.composePage(headerContent, mainContent, footerContent);
108+
}
109+
}
110+
```
111+
112+
The page composer assembles the final HTML:
113+
114+
```java
115+
public class PageComposer {
116+
public String composePage(String headerContent, String mainContent, String footerContent) {
117+
return String.format("""
118+
<!DOCTYPE html>
119+
<html>
120+
<head><!-- head content --></head>
121+
<body>
122+
%s <!-- Header -->
123+
%s <!-- Main Content -->
124+
%s <!-- Footer -->
125+
</body>
126+
</html>
127+
""", headerContent, mainContent, footerContent);
128+
}
129+
}
130+
```
131+
132+
The pattern also supports asynchronous composition for better performance:
133+
134+
```java
135+
public CompletableFuture<String> composePageAsync(String pageId) {
136+
var context = createPageContext(pageId);
137+
138+
// Generate fragments in parallel
139+
var headerFuture = CompletableFuture.supplyAsync(() -> generateHeaderFragment(context));
140+
var contentFuture = CompletableFuture.supplyAsync(() -> generateContentFragment(context));
141+
var footerFuture = CompletableFuture.supplyAsync(() -> generateFooterFragment(context));
142+
143+
return CompletableFuture.allOf(headerFuture, contentFuture, footerFuture)
144+
.thenApply(v -> pageComposer.composePage(
145+
headerFuture.join(), contentFuture.join(), footerFuture.join()));
146+
}
147+
```
148+
149+
## Class diagram
150+
151+
![Server-Side Fragment Composition](./etc/server-side-fragment-composition.urm.png)
152+
153+
## Applicability
154+
155+
Use Server-Side Page Fragment Composition when:
156+
157+
* You have multiple teams working on different parts of web pages
158+
* You need independent deployment and scaling of page components
159+
* You want to optimize performance by server-side assembly
160+
* You need to maintain consistent user experience across fragments
161+
* You're implementing micro-frontend architecture
162+
* Different fragments require different technologies or update cycles
163+
* You want to enable fault isolation between page components
164+
* You need to support A/B testing of different page fragments
165+
166+
## Known uses
167+
168+
* Netflix's homepage composition system
169+
* Amazon's product page assembly
170+
* Modern e-commerce platforms with modular components
171+
* Content management systems with widget-based layouts
172+
* Social media platforms with personalized content feeds
173+
* News websites with different content sections
174+
175+
## Consequences
176+
177+
Benefits:
178+
* **Independent Development**: Teams can work on fragments independently
179+
* **Scalability**: Each service can be scaled based on its specific needs
180+
* **Performance**: Server-side assembly reduces client-side processing
181+
* **Technology Diversity**: Different services can use different technologies
182+
* **Fault Isolation**: Issues in one fragment don't affect others
183+
* **Deployment Flexibility**: Services can be deployed independently
184+
* **Caching**: Fragments can be cached independently based on their characteristics
185+
* **SEO Friendly**: Complete HTML is served to search engines
186+
187+
Drawbacks:
188+
* **Increased Complexity**: More complex orchestration and service management
189+
* **Network Latency**: Communication between services adds latency
190+
* **Consistency Challenges**: Maintaining UI/UX consistency across fragments
191+
* **Testing Complexity**: Integration testing becomes more complex
192+
* **Dependency Management**: Managing dependencies between fragments
193+
* **Error Handling**: More complex error handling and fallback strategies
194+
* **Monitoring**: Need comprehensive monitoring across all services
195+
196+
## Related patterns
197+
198+
* [Microservices Aggregator](../microservices-aggregator/) - Similar service composition approach
199+
* [Facade](../facade/) - Provides unified interface like composition service
200+
* [Composite](../composite/) - Structural pattern for building object hierarchies
201+
* [Template Method](../template-method/) - Defines algorithm structure for composition
202+
* [Strategy](../strategy/) - Different composition strategies for different page types
203+
* [Observer](../observer/) - Services can observe changes in page context
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
@startuml
2+
!theme plain
3+
4+
package "Server-Side Fragment Composition Pattern" {
5+
6+
interface Fragment {
7+
+render(context: PageContext): String
8+
+getType(): String
9+
+getPriority(): int
10+
+isCacheable(): boolean
11+
+getCacheTimeout(): int
12+
}
13+
14+
class HeaderFragment implements Fragment {
15+
+render(context: PageContext): String
16+
+getType(): String
17+
+getPriority(): int
18+
+isCacheable(): boolean
19+
+getCacheTimeout(): int
20+
}
21+
22+
class ContentFragment implements Fragment {
23+
+render(context: PageContext): String
24+
+getType(): String
25+
+getPriority(): int
26+
+isCacheable(): boolean
27+
-generateContentByPageType(pageId: String): String
28+
}
29+
30+
class FooterFragment implements Fragment {
31+
+render(context: PageContext): String
32+
+getType(): String
33+
+getPriority(): int
34+
+isCacheable(): boolean
35+
}
36+
37+
class HeaderService {
38+
-headerFragment: Fragment
39+
+generateFragment(context: PageContext): String
40+
+getServiceInfo(): String
41+
+isHealthy(): boolean
42+
+getFragmentType(): String
43+
-simulateProcessing(): void
44+
}
45+
46+
class ContentService {
47+
-contentFragment: Fragment
48+
+generateFragment(context: PageContext): String
49+
+getServiceInfo(): String
50+
+isHealthy(): boolean
51+
+getFragmentType(): String
52+
+getContentStats(): String
53+
-simulateContentProcessing(): void
54+
-applyPersonalization(context: PageContext): void
55+
}
56+
57+
class FooterService {
58+
-footerFragment: Fragment
59+
+generateFragment(context: PageContext): String
60+
+getServiceInfo(): String
61+
+isHealthy(): boolean
62+
+getFragmentType(): String
63+
+getPromotionalStatus(): String
64+
-simulateFooterProcessing(): void
65+
-addPromotionalContent(context: PageContext): void
66+
}
67+
68+
class CompositionService {
69+
-headerServices: Map<String, HeaderService>
70+
-contentServices: Map<String, ContentService>
71+
-footerServices: Map<String, FooterService>
72+
-pageComposer: PageComposer
73+
+registerService(type: String, service: Object): void
74+
+composePage(pageId: String): String
75+
+composePageAsync(pageId: String): CompletableFuture<String>
76+
+getHealthStatus(): String
77+
-validateRequiredServices(): void
78+
-createPageContext(pageId: String): PageContext
79+
-getPageTitle(pageId: String): String
80+
-getCurrentUserId(): String
81+
-generateHeaderFragment(context: PageContext): String
82+
-generateContentFragment(context: PageContext): String
83+
-generateFooterFragment(context: PageContext): String
84+
}
85+
86+
class PageComposer {
87+
+composePage(header: String, content: String, footer: String): String
88+
+composePageWithCustomStyling(header: String, content: String, footer: String, css: String): String
89+
-buildHtmlPage(header: String, content: String, footer: String): String
90+
-validateFragment(name: String, content: String): void
91+
}
92+
93+
class PageContext {
94+
-pageId: String
95+
-title: String
96+
-userId: String
97+
-attributes: Map<String, Object>
98+
+getAttribute(key: String): Object
99+
+setAttribute(key: String, value: Object): void
100+
+hasAttribute(key: String): boolean
101+
}
102+
103+
class App {
104+
+main(args: String[]): void
105+
-runDemo(): void
106+
-demonstrateSynchronousComposition(service: CompositionService): void
107+
-demonstrateAsynchronousComposition(service: CompositionService): void
108+
-demonstrateErrorHandling(): void
109+
-getPagePreview(page: String): String
110+
}
111+
}
112+
113+
' Relationships
114+
CompositionService --> PageComposer
115+
CompositionService --> HeaderService
116+
CompositionService --> ContentService
117+
CompositionService --> FooterService
118+
CompositionService --> PageContext
119+
120+
HeaderService --> HeaderFragment
121+
ContentService --> ContentFragment
122+
FooterService --> FooterFragment
123+
124+
Fragment --> PageContext
125+
126+
App --> CompositionService
127+
App --> HeaderService
128+
App --> ContentService
129+
App --> FooterService
130+
131+
PageContext ..> "uses" PageContext
132+
133+
note right of CompositionService
134+
Central orchestrator that coordinates
135+
fragment generation from different
136+
microservices and assembles final pages
137+
end note
138+
139+
note right of Fragment
140+
Common interface for all page fragments
141+
enabling consistent composition process
142+
end note
143+
144+
note bottom of PageContext
145+
Carries page-specific data and user
146+
context for fragment personalization
147+
end note
148+
149+
@enduml

0 commit comments

Comments
 (0)