With the rise of cloud computing, AI, and distributed systems, traditional design patterns can no longer meet the demands of modern software development. This article introduces the hottest software development design patterns in 2026, with concise and easy-to-understand code examples.
1. Cloud Native & Microservices Patterns#
1. Sidecar Pattern#
Core Idea: Deploy auxiliary functions (logging, monitoring, configuration) as independent processes on the same host as the main application, sharing network and storage.
Use Cases: Log collection in Kubernetes, service mesh proxies.
// Main Application Service
public class MainApplication {
public static void main(String[] args) {
// Main business logic
System.out.println("Main application started, listening on port 8080");
// Sidecar accesses through shared network namespace
}
}
// Sidecar Process (Independent Log Collector)
public class LogSidecar {
public static void main(String[] args) {
// Monitor main application's log files or ports
System.out.println("Sidecar started, responsible for log collection and reporting");
// Send logs to centralized logging system
}
}Kubernetes Deployment Example:
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
- name: main-app
image: my-app:latest
- name: log-sidecar
image: log-collector:latest # Sidecar container2. Circuit Breaker#
Core Idea: When a downstream service fails, fail fast to prevent cascading crashes. Similar to an electrical fuse.
Use Cases: Microservice calls, external API calls.
// Implementation using Resilience4j
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
public class PaymentService {
private final CircuitBreaker circuitBreaker;
public PaymentService() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // Trigger circuit when failure rate exceeds 50%
.waitDurationInOpenState(Duration.ofSeconds(30)) // Try recovery after 30 seconds
.slidingWindowSize(10) // Sliding window size
.build();
this.circuitBreaker = CircuitBreaker.of("payment", config);
}
public String processPayment(String orderId) {
return circuitBreaker.executeSupplier(() -> {
// Call downstream payment service
return callPaymentGateway(orderId);
});
}
// Fallback when circuit is open
public String processPaymentFallback(String orderId, Exception e) {
return "Payment service temporarily unavailable, please try again later";
}
}3. Saga Pattern#
Core Idea: Break down distributed transactions into a series of local transactions, each with a corresponding compensation operation. Execute compensation to rollback on failure.
Use Cases: E-commerce order placement (inventory deduction → payment → shipping creation).
// Order Creation Saga
public class OrderSaga {
private final InventoryService inventoryService;
private final PaymentService paymentService;
private final ShippingService shippingService;
public void createOrder(Order order) {
try {
// Step 1: Deduct inventory
inventoryService.deductStock(order.getProductId(), order.getQuantity());
try {
// Step 2: Process payment
paymentService.charge(order.getUserId(), order.getAmount());
try {
// Step 3: Create shipping order
shippingService.createShipment(order);
System.out.println("Order created successfully!");
} catch (Exception e) {
// Compensate Step 2: Refund
paymentService.refund(order.getUserId(), order.getAmount());
throw e;
}
} catch (Exception e) {
// Compensate Step 1: Restore inventory
inventoryService.restoreStock(order.getProductId(), order.getQuantity());
throw e;
}
} catch (Exception e) {
System.out.println("Order creation failed: " + e.getMessage());
}
}
}4. CQRS (Command Query Responsibility Segregation)#
Core Idea: Use different models and databases for read and write operations, optimizing each for performance.
Use Cases: High-concurrency systems, complex query scenarios.
// Command Model (Write Operations)
public class OrderCommandService {
private final OrderWriteRepository writeRepo;
public void createOrder(CreateOrderCommand cmd) {
Order order = new Order(cmd.getUserId(), cmd.getProducts());
writeRepo.save(order);
// Publish event to notify read model to update
eventBus.publish(new OrderCreatedEvent(order));
}
public void updateOrderStatus(UpdateStatusCommand cmd) {
Order order = writeRepo.findById(cmd.getOrderId());
order.setStatus(cmd.getStatus());
writeRepo.save(order);
eventBus.publish(new OrderUpdatedEvent(order));
}
}
// Query Model (Read Operations)
public class OrderQueryService {
private final OrderReadRepository readRepo; // Can be a different database
public OrderDTO getOrder(Long orderId) {
return readRepo.findById(orderId);
}
public List<OrderDTO> getOrdersByUser(Long userId) {
return readRepo.findByUserId(userId);
}
}
// Read model can be an optimized view
@Entity
@Table(name = "order_view")
public class OrderDTO {
private Long id;
private String userName; // Denormalized, avoid joins
private String productNames; // Denormalized
private String status;
private LocalDateTime createdAt;
}2. AI/LLM Application Patterns#
1. RAG (Retrieval-Augmented Generation)#
Core Idea: First retrieve relevant documents from a knowledge base, then pass them as context to the LLM to improve answer accuracy.
Use Cases: Enterprise knowledge base Q&A, intelligent customer service.
// Node.js + LangChain Implementation
import { OpenAI } from "openai";
import { PineconeClient } from "@pinecone-database/pinecone";
class RAGService {
constructor() {
this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
this.pinecone = new PineconeClient();
}
async answer(question) {
// 1. Convert question to embedding
const questionEmbedding = await this.openai.embeddings.create({
model: "text-embedding-3-small",
input: question
});
// 2. Retrieve relevant documents from vector database
const relevantDocs = await this.pinecone
.index("knowledge-base")
.query({
vector: questionEmbedding.data[0].embedding,
topK: 3
});
// 3. Build augmented prompt
const context = relevantDocs.matches.map(m => m.metadata.text).join("\n\n");
const prompt = `
Answer the question based on the following reference materials. If the information is not in the materials, please state so.
Reference Materials:
${context}
Question: ${question}
Answer:`;
// 4. Call LLM to generate answer
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: prompt }]
});
return response.choices[0].message.content;
}
}2. Agent Pattern#
Core Idea: AI agents can autonomously make decisions, call tools, and execute multi-step tasks.
Use Cases: Automated workflows, intelligent assistants.
// Simple AI Agent Implementation in Node.js
class AIAgent {
constructor() {
this.tools = {
search: this.search.bind(this),
calculate: this.calculate.bind(this),
sendEmail: this.sendEmail.bind(this)
};
}
async execute(task) {
let step = 0;
let context = { task, results: [] };
while (step < 5) { // Maximum 5 steps
// Let LLM decide the next action
const decision = await this.decideAction(context);
if (decision.action === "finish") {
return decision.result;
}
// Execute tool call
const result = await this.tools[decision.action](decision.params);
context.results.push({ action: decision.action, result });
step++;
}
return "Task execution timeout";
}
async decideAction(context) {
const prompt = `
Current task: ${context.task}
Executed steps: ${JSON.stringify(context.results)}
Available tools: search, calculate, sendEmail
Decide the next action, return JSON format:
{"action": "tool_name or finish", "params": {...}, "result": "result if finish"}
`;
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
response_format: { type: "json_object" }
});
return JSON.parse(response.choices[0].message.content);
}
async search(query) {
// Call search API
return `Search results: Information about ${query}...`;
}
async calculate(expression) {
return eval(expression);
}
async sendEmail(params) {
console.log(`Sending email to ${params.to}: ${params.subject}`);
return "Email sent successfully";
}
}
// Usage Example
const agent = new AIAgent();
const result = await agent.execute("Check tomorrow's weather in Beijing and send an email to boss@company.com");3. Prompt Chaining#
Core Idea: Break down complex tasks into multiple LLM calls, where the output of one becomes the input of the next.
Use Cases: Content generation pipelines, code generation.
class PromptChain {
async generateArticle(topic) {
// Step 1: Generate outline
const outline = await this.generateOutline(topic);
// Step 2: Generate content for each section
const sections = await this.generateSections(outline);
// Step 3: Polish and optimize
const article = await this.polishArticle(sections);
// Step 4: Generate summary
const summary = await this.generateSummary(article);
return { article, summary };
}
async generateOutline(topic) {
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages: [{
role: "user",
content: `Generate an outline for article "${topic}", return as JSON array`
}]
});
return JSON.parse(response.choices[0].message.content);
}
async generateSections(outline) {
const sections = [];
for (const section of outline) {
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages: [{
role: "user",
content: `Write content based on outline "${section.title}", key points: ${section.points.join(",")}`
}]
});
sections.push({
title: section.title,
content: response.choices[0].message.content
});
}
return sections;
}
async polishArticle(sections) {
const content = sections.map(s => `## ${s.title}\n${s.content}`).join("\n\n");
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages: [{
role: "user",
content: `Please polish the following article to make it more fluent:\n${content}`
}]
});
return response.choices[0].message.content;
}
async generateSummary(article) {
const response = await this.openai.chat.completions.create({
model: "gpt-4",
messages: [{
role: "user",
content: `Generate a 100-word summary for the following article:\n${article}`
}]
});
return response.choices[0].message.content;
}
}3. Data Processing Patterns#
1. Outbox Pattern#
Core Idea: Ensure atomicity of database updates and message sending. Write messages to an Outbox table first, then send by an independent process.
Use Cases: Event notification between microservices.
// Order Service
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepo;
@Autowired
private OutboxRepository outboxRepo;
@Transactional
public void createOrder(Order order) {
// 1. Save order
orderRepo.save(order);
// 2. Write event to Outbox table (same transaction)
OutboxEvent event = new OutboxEvent();
event.setAggregateType("Order");
event.setAggregateId(order.getId());
event.setEventType("OrderCreated");
event.setPayload(objectMapper.writeValueAsString(order));
outboxRepo.save(event);
}
}
// Outbox Table Structure
@Entity
@Table(name = "outbox")
public class OutboxEvent {
@Id
private UUID id;
private String aggregateType;
private String aggregateId;
private String eventType;
private String payload;
private LocalDateTime createdAt;
}
// Independent Message Publisher
@Component
public class OutboxPublisher {
@Scheduled(fixedRate = 1000)
public void publishEvents() {
List<OutboxEvent> events = outboxRepo.findTop100ByOrderByCreatedAtAsc();
for (OutboxEvent event : events) {
try {
kafkaTemplate.send(event.getAggregateType(), event.getPayload());
outboxRepo.delete(event); // Delete after successful send
} catch (Exception e) {
log.error("Failed to send event: {}", event.getId());
}
}
}
}2. CDC (Change Data Capture)#
Core Idea: Real-time capture of database changes to trigger downstream processing.
Use Cases: Data synchronization, real-time analytics, cache updates.
// Using Debezium to capture MySQL changes
@Component
public class OrderCDCProcessor {
@KafkaListener(topics = "dbserver1.inventory.orders")
public void processOrderChange(ChangeEvent<String, String> event) {
JSONObject payload = new JSONObject(event.getValue());
String operation = payload.getJSONObject("payload").getString("op");
switch (operation) {
case "c": // Create
handleOrderCreated(payload);
break;
case "u": // Update
handleOrderUpdated(payload);
break;
case "d": // Delete
handleOrderDeleted(payload);
break;
}
}
private void handleOrderCreated(JSONObject payload) {
JSONObject after = payload.getJSONObject("payload").getJSONObject("after");
Long orderId = after.getLong("id");
// Update Elasticsearch index
searchService.indexOrder(orderId);
// Update Redis cache
cacheService.cacheOrder(orderId, after.toString());
// Send notification
notificationService.notifyNewOrder(orderId);
}
}4. Reliability Patterns#
1. Retry with Exponential Backoff#
Core Idea: Retry after failure, but with exponentially increasing wait time to avoid avalanche effects.
public class RetryClient {
private static final int MAX_RETRIES = 5;
private static final long INITIAL_DELAY_MS = 100;
public <T> T executeWithRetry(Supplier<T> operation) {
int attempt = 0;
long delay = INITIAL_DELAY_MS;
while (attempt < MAX_RETRIES) {
try {
return operation.get();
} catch (Exception e) {
attempt++;
if (attempt >= MAX_RETRIES) {
throw new RuntimeException("Retry attempts exhausted", e);
}
System.out.println("Retry attempt " + attempt + ", waiting " + delay + "ms");
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
delay *= 2; // Exponential growth
// Add random jitter to avoid multiple clients retrying simultaneously
delay += (long) (delay * 0.1 * Math.random());
}
}
throw new RuntimeException("Should not reach here");
}
}
// Usage Example
RetryClient client = new RetryClient();
String result = client.executeWithRetry(() -> {
return externalApi.call(); // Call that might fail
});2. Bulkhead Pattern#
Core Idea: Limit resource usage for each service to prevent one service from exhausting all resources.
// Implementation using Resilience4j
import io.github.resilience4j.bulkhead.Bulkhead;
import io.github.resilience4j.bulkhead.BulkheadConfig;
@Service
public class ExternalService {
// Set up independent bulkheads for different services
private final Bulkhead paymentBulkhead;
private final Bulkhead notificationBulkhead;
public ExternalService() {
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(10) // Maximum concurrent calls
.maxWaitDuration(Duration.ofSeconds(1))
.build();
this.paymentBulkhead = Bulkhead.of("payment", config);
this.notificationBulkhead = Bulkhead.of("notification", config);
}
public String callPayment(String orderId) {
return paymentBulkhead.executeSupplier(() -> {
return paymentClient.process(orderId);
});
}
public void sendNotification(String message) {
notificationBulkhead.executeRunnable(() -> {
notificationClient.send(message);
});
}
}5. Frontend Architecture Patterns#
1. BFF (Backend for Frontend)#
Core Idea: Provide customized backend APIs for different clients (Web, Mobile, Mini-programs).
// Node.js + Express Implementation
// Web BFF
const webBff = express();
webBff.get('/product/:id', async (req, res) => {
const productId = req.params.id;
// Aggregate multiple backend services
const [product, reviews, recommendations] = await Promise.all([
productService.getDetail(productId),
reviewService.getReviews(productId, { limit: 10 }),
recommendationService.getRelated(productId, { limit: 5 })
]);
// Customized response format for Web
res.json({
product: {
id: product.id,
name: product.name,
price: product.price,
description: product.fullDescription, // Web shows full description
images: product.highResImages // Web uses high-res images
},
reviews,
recommendations
});
});
// Mobile BFF
const mobileBff = express();
mobileBff.get('/product/:id', async (req, res) => {
const productId = req.params.id;
const product = await productService.getDetail(productId);
// Customized response format for Mobile
res.json({
product: {
id: product.id,
name: product.name,
price: product.price,
description: product.shortDescription, // Mobile shows short description
thumbnail: product.thumbnailImage // Mobile uses thumbnail
}
// Mobile doesn't return reviews and recommendations to reduce data
});
});2. Islands Architecture#
Core Idea: Embed interactive “islands” in static HTML to reduce JavaScript bundle size.
// Astro Framework Example
// ---
// Most of the static page is pure HTML
// ---
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import InteractiveCounter from '../components/Counter.jsx'; // Interactive island
const posts = await fetch('/api/posts').then(r => r.json());
---
<html>
<head>
<title>Blog Home</title>
</head>
<body>
<Header /> <!-- Static -->
<main>
<!-- Static content, zero JS -->
<section class="posts">
{posts.map(post => (
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</section>
<!-- Interactive island, only this part loads JS -->
<InteractiveCounter client:visible />
</main>
<Footer /> <!-- Static -->
</body>
</html>
// Counter.jsx - Only this component will load JavaScript
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div className="counter">
<button onClick={() => setCount(c => c - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}6. Summary#
| Pattern Category | Core Patterns | Problems Solved |
|---|---|---|
| Cloud Native | Sidecar, Circuit Breaker, Saga, CQRS | Service governance, fault tolerance, distributed transactions |
| AI/LLM | RAG, Agent, Prompt Chaining | Knowledge enhancement, autonomous decision-making, task decomposition |
| Data Processing | Outbox, CDC | Data consistency, real-time synchronization |
| Reliability | Retry, Bulkhead | Failure recovery, resource isolation |
| Frontend Architecture | BFF, Islands | Client customization, performance optimization |
When choosing design patterns, follow the simplicity first principle: start with the simplest solution, and introduce complex patterns only when bottlenecks are encountered. Over-engineering is often worse than no design at all.
