|
| 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 | + |
| 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 |
0 commit comments