Self-Contained Systems
Self-Contained Systems is an architectural pattern where an application is structured as a suite of independently deployable services, each with its own database and logic. These systems are designed to be loosely coupled, communicating with each other via well-defined APIs, but without sharing databases or internal state. This approach promotes autonomy, allowing teams to develop, deploy, and scale individual systems independently.
The core principle is to minimize dependencies between components. Each system is responsible for its own data consistency and availability. This contrasts with monolithic architectures or shared-database approaches, where changes in one part of the system can have cascading effects on others. This pattern is often used in microservice architectures, but can be applied at a coarser granularity as well.
Usage
This pattern is commonly used in:
- Microservice Architectures: The most prevalent use case, where each microservice embodies a self-contained system.
- Large-Scale Applications: Breaking down a large application into smaller, manageable systems improves maintainability and scalability.
- Organizations with Multiple Teams: Allows teams to own and operate their systems independently, fostering agility and ownership.
- Systems Requiring High Availability: Isolating failures within a single system prevents them from impacting the entire application.
- Event-Driven Architectures: Systems can react to events published by other systems without direct coupling.
Examples
- Netflix: Netflix famously adopted a microservice architecture built on self-contained systems. Each component, like the recommendation engine, video encoding pipeline, or user account management, operates as an independent service with its own data store. This allows Netflix to scale individual features based on demand and deploy updates without disrupting the entire platform.
- Amazon: Amazon’s e-commerce platform is composed of numerous self-contained systems. For example, the ordering system, the payment processing system, and the shipping system each have their own databases and logic. This separation allows Amazon to handle massive transaction volumes and maintain high availability, even during peak shopping seasons.
- Shopify: Shopify utilizes self-contained systems for different aspects of its platform, such as the storefront, order management, and payment gateway integrations. This allows for independent scaling and development of each feature, catering to the diverse needs of its merchants.
Specimens
15 implementationsThe Self-Contained System pattern focuses on encapsulating all dependencies within a single unit (like a class or function). This eliminates external dependencies, making the system portable, testable, and easier to reason about. It promotes loose coupling and simplifies deployments.
Here, we implement a simple calculator as a self-contained system using a class. All calculator logic, including parsing and performing operations, is encapsulated within the Calculator class, without reliance on external state or libraries beyond Dart’s core functionality. This illustrates how to achieve modularity and reduce dependencies, fitting Dart’s class-based structure and promoting code organization. The calculate method handles all operation logic, ensuring a clear entry point and contained behavior.
// main.dart
class Calculator {
String calculate(String expression) {
try {
// Basic parsing and evaluation. Could use a more robust library
// if complexity increased, but keeping it contained.
final List<String> parts = expression.split(' ');
if (parts.length != 3) {
return "Invalid expression";
}
final num operand1 = double.parse(parts[0]);
final String operator = parts[1];
final num operand2 = double.parse(parts[2]);
num result;
switch (operator) {
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
case '*':
result = operand1 * operand2;
break;
case '/':
if (operand2 == 0) {
return "Division by zero";
}
result = operand1 / operand2;
break;
default:
return "Invalid operator";
}
return result.toString();
} catch (e) {
return "Invalid input";
}
}
}
void main() {
final calculator = Calculator();
print(calculator.calculate('10 + 5'));
print(calculator.calculate('20 / 4'));
print(calculator.calculate('3 * 7'));
print(calculator.calculate('12 - 6'));
print(calculator.calculate('5 + ')); // Invalid
print(calculator.calculate('10 / 0')); // Division by zero
}
The Self-Contained System pattern aims to reduce dependencies by bundling all resources a component needs (configuration, data, etc.) within the component itself. This promotes portability, testability, and isolation. My Scala example demonstrates this by encapsulating the data and logic for a basic user profile within a UserProfile object, including a default configuration. Configuration is handled via a companion object for easy access and modification. No external configuration files or database connections are required—everything needed is available directly within the UserProfile itself. This is idiomatic Scala due to the strong emphasis on immutability with case class and the use of objects for singletons and associated data.
// UserProfile.scala
object UserProfile {
// Default configuration – fully contained within the system
case class Config(defaultName: String = "Anonymous", maxDisplayNameLength: Int = 30)
val defaultConfig = Config()
def apply(userId: Int, config: Config = defaultConfig): UserProfile = {
new UserProfile(userId, config)
}
}
case class UserProfile(userId: Int, config: UserProfile.Config) {
private var _name: String = config.defaultName
def name: String = _name
def setName(newName: String): Unit = {
val trimmedName = newName.take(config.maxDisplayNameLength)
_name = trimmedName
}
override def toString: String = s"User ID: $userId, Name: $name"
}
object Main extends App {
val user1 = UserProfile(1) // Uses default config
println(user1)
val customConfig = UserProfile.Config(defaultName = "Guest", maxDisplayNameLength = 15)
val user2 = UserProfile(2, customConfig)
user2.setName("Very Long Display Name")
println(user2)
}
The Self-Contained System pattern advocates for building independent, deployable units of functionality within a larger application. Each system manages its own dependencies and data, minimizing external coupling. This fosters resilience, testability, and independent development/deployment.
The code demonstrates a simplified “Order Processing” system. It encapsulates order creation, validation (using a dedicated validator class), and persistence (to a simple array for demonstration). Crucially, all dependencies are managed within the system itself: the validator is instantiated inside the OrderProcessor, and persistence is handled directly. This makes the OrderProcessor relatively independent and easier to test in isolation. PHP’s class-based structure naturally aligns with creating these self-contained units. Using dependency injection within the class is often preferred for simpler systems like this in PHP, due to its pragmatic nature.
<?php
class Order {
public function __construct(
public int $orderId,
public string $customerId,
public array $items,
public float $totalAmount
) {}
}
class OrderValidator {
public function validate(array $orderData): bool {
if (!isset($orderData['customerId']) || empty($orderData['customerId'])) {
return false;
}
if (!isset($orderData['items']) || !is_array($orderData['items'])) {
return false;
}
if (!isset($orderData['totalAmount']) || !is_numeric($orderData['totalAmount'])) {
return false;
}
return true;
}
}
class OrderProcessor {
private array $orders = [];
public function __construct() {
$this->validator = new OrderValidator();
}
public function createOrder(array $orderData): ?Order {
if (!$this->validator->validate($orderData)) {
return null; // Validation failed
}
$orderId = count($this->orders) + 1;
$newOrder = new Order($orderId, $orderData['customerId'], $orderData['items'], floatval($orderData['totalAmount']));
$this->orders[$orderId] = $newOrder;
return $newOrder;
}
public function getOrder(int $orderId): ?Order {
return $this->orders[$orderId] ?? null;
}
}
// Example Usage
$processor = new OrderProcessor();
$orderData = [
'customerId' => 'user123',
'items' => ['productA', 'productB'],
'totalAmount' => 50.00,
];
$order = $processor->createOrder($orderData);
if ($order) {
echo "Order created with ID: " . $order->orderId . "\n";
$retrievedOrder = $processor->getOrder($order->orderId);
print_r($retrievedOrder);
} else {
echo "Order creation failed (validation).\n";
}
?>
The Self-Contained System pattern promotes building modular, reusable components that encapsulate all their dependencies. Each system should have a clear interface and minimal external exposure. This fosters independence, simplifies testing, and reduces coupling.
Here, we model a simple EmailService as a self-contained system. It encapsulates its dependency on a message construction object (EmailComposer) within the service itself. The external interface only requires specifying a recipient and subject; the internal composition of the email is handled transparently.
Ruby’s strong support for composition and object instantiation lends itself naturally to this pattern. The EmailService class directly creates the EmailComposer instance it needs, avoiding external dependency injection and keeping all related logic together. This approach is common in Ruby for creating focused, cohesive units of functionality.
# lib/email_service.rb
class EmailComposer
def compose(recipient, subject, body)
"To: #{recipient}\nSubject: #{subject}\n\n#{body}"
end
end
class EmailService
def initialize
@composer = EmailComposer.new
end
def send_email(recipient, subject, body)
email_content = @composer.compose(recipient, subject, body)
send_raw_email(email_content) # Simulate sending - replace with actual SMTP logic
true # Indicate success
end
private
def send_raw_email(content)
puts "Sending email to:\n#{content}"
end
end
# Example Usage:
if __FILE__ == $0
service = EmailService.new
service.send_email("user@example.com", "Hello", "This is the email body.")
end
The Self-Contained Systems pattern aims to reduce coupling by ensuring each system (or module) has everything it needs to operate – its data, dependencies, and logic – without relying excessively on external components. This fosters independence, testability, and reusability.
This Swift implementation models a ReportGenerator as a self-contained system. It includes a DataSource protocol to abstract data access but also provides a concrete InMemoryDataSource directly within the ReportGenerator struct. This eliminates external dependency injection for a simple case, keeping the report generation logic bundled with its data source, making it easier to understand and deploy this specific functionality. Using a struct and protocol-oriented programming aligns with Swift’s emphasis on value types and flexibility.
// ReportGenerator.swift
// Protocol defining data source requirements
protocol DataSource {
func fetchData() -> [String]
}
// Concrete data source implementation – kept within the system for simplicity.
struct InMemoryDataSource: DataSource {
private let data: [String]
init(data: [String]) {
self.data = data
}
func fetchData() -> [String] {
return data
}
}
// The self-contained ReportGenerator system
struct ReportGenerator {
private let dataSource: DataSource
// The DataSource is initialized *within* the struct, making it self-contained.
init() {
self.dataSource = InMemoryDataSource(data: ["Data Point 1", "Data Point 2", "Data Point 3"])
}
func generateReport() -> String {
let data = dataSource.fetchData()
return "Report:\n" + data.joined(separator: "\n")
}
}
// Example usage
let reportGenerator = ReportGenerator()
let report = reportGenerator.generateReport()
print(report)
The Self-Contained Systems pattern focuses on creating modules that have everything required to function – dependencies are minimized or explicitly provided, and the module exports only necessary functionality. This promotes independence, testability, and resilience to external changes. The Kotlin example below demonstrates this through a ReportGenerator class that accepts data and formatting logic as input, avoiding reliance on external, globally accessible state or services. Kotlin’s data classes and function-first approach align well with this by enabling declarative data structures and easy dependency injection via function arguments. The use of immutable data classes is key, contributing to the system’s predictability.
// ReportGenerator.kt
data class ReportData(val title: String, val content: String)
interface ReportFormatter {
fun format(data: ReportData): String
}
class MarkdownFormatter : ReportFormatter {
override fun format(data: ReportData): String {
return "# ${data.title}\n\n${data.content}"
}
}
class ReportGenerator(private val formatter: ReportFormatter) {
fun generateReport(data: ReportData): String {
return formatter.format(data)
}
}
fun main() {
val reportData = ReportData("My Report", "This is the content of my report.")
val markdownFormatter = MarkdownFormatter()
val reportGenerator = ReportGenerator(markdownFormatter)
val report = reportGenerator.generateReport(reportData)
println(report)
}
The Self-Contained System pattern aims to encapsulate all dependencies a component needs within that component itself, minimizing external reliance and improving portability and reproducibility. This is often achieved through embedding data, code, or configuration directly within the component. In Rust, this is naturally supported through its strong ownership model, lack of global state, and ability to embed data directly into structs and enums. The example below creates a simple image processing system where the image data is the system, avoiding external file paths or shared resources. Data cloning is used as needed to allow for non-mutative access.
// src/image_processor.rs
#[derive(Debug, Clone)]
pub struct Image {
pub width: u32,
pub height: u32,
pub pixels: Vec<u8>,
}
impl Image {
pub fn new(width: u32, height: u32, pixels: Vec<u8>) -> Self {
Image {
width,
height,
pixels,
}
}
// A simple grayscale transformation for demonstration
pub fn to_grayscale(&self) -> Image {
let mut new_pixels = Vec::with_capacity(self.pixels.len());
for &pixel in &self.pixels {
let gray = (pixel as f32 * 0.299) as u8;
new_pixels.push(gray);
}
Image::new(self.width, self.height, new_pixels)
}
}
fn main() {
// Example Usage:
let image_data = vec![
255, 0, 0, // Red
0, 255, 0, // Green
0, 0, 255, // Blue
];
let image = Image::new(3, 1, image_data);
println!("Original Image: {:?}", image);
let grayscale_image = image.to_grayscale();
println!("Grayscale Image: {:?}", grayscale_image);
}
The Self-Contained Systems pattern advocates for creating independent, deployable components with well-defined interfaces. Each system handles its own data, logic, and dependencies, minimizing external couplings. This promotes modularity, testability, and independent scaling.
The example below showcases a simple order processing system. The OrderProcessor encapsulates all order-related logic, including validation and persistence (mocked here). It receives orders as strings and returns a processing result. The main function demonstrates how to use the processor without needing to know its internal details. This follows Go’s emphasis on explicit interfaces and package-level encapsulation, making it naturally suited to building these types of systems. Error handling via returned errors is also idiomatic.
// order_processor.go
package main
import (
"fmt"
"errors"
)
// OrderProcessor encapsulates the order processing logic.
type OrderProcessor struct {
// Add dependencies here if needed (e.g., database connection).
}
// NewOrderProcessor creates a new order processor.
func NewOrderProcessor() *OrderProcessor {
return &OrderProcessor{}
}
// ProcessOrder validates and processes an order. In a real system, this would
// interact with a database or other external system.
func (op *OrderProcessor) ProcessOrder(order string) (string, error) {
if order == "" {
return "", errors.New("order cannot be empty")
}
// Simulate order processing.
result := fmt.Sprintf("Order '%s' processed successfully.", order)
return result, nil
}
func main() {
processor := NewOrderProcessor()
order1 := "Book-123"
result1, err1 := processor.ProcessOrder(order1)
if err1 != nil {
fmt.Println("Error processing order:", err1)
} else {
fmt.Println(result1)
}
order2 := ""
result2, err2 := processor.ProcessOrder(order2)
if err2 != nil {
fmt.Println("Error processing order:", err2)
} else {
fmt.Println(result2)
}
}
The Self-Contained Systems pattern advocates encapsulating an entire subsystem within a single unit – often a library or a well-defined set of header/source files. This promotes modularity, reduces dependencies, and simplifies testing. My C implementation models a simple logging system as a self-contained unit. It includes the logging functions and a simple configuration structure – all within the logger.h and logger.c files. This minimizes external dependencies, making it easy to integrate or replace. C’s strengths in creating static libraries perfectly suit this pattern, offering a clear separation of concerns. The use of a struct for configuration and passing it to functions aligns with typical C practices.
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <stdio.h>
typedef struct {
FILE *log_file;
int log_level; // 0-debug, 1-info, 2-warn, 3-error
} LoggerConfig;
void logger_init(LoggerConfig *config, const char *filename);
void logger_log(const LoggerConfig *config, int level, const char *message);
void logger_close(LoggerConfig *config);
#endif // LOGGER_H
// logger.c
#include "logger.h"
#include <stdlib.h>
#include <string.h>
void logger_init(LoggerConfig *config, const char *filename) {
config->log_file = fopen(filename, "a");
if (config->log_file == NULL) {
perror("Error opening log file");
exit(EXIT_FAILURE);
}
config->log_level = 1; // Default to INFO
}
void logger_log(const LoggerConfig *config, int level, const char *message) {
if (level >= config->log_level) {
char log_entry[256];
snprintf(log_entry, sizeof(log_entry), "[Level %d] %s", level, message);
fprintf(config->log_file, "%s\n", log_entry);
fflush(config->log_file);
}
}
void logger_close(LoggerConfig *config) {
if (config->log_file != NULL) {
fclose(config->log_file);
config->log_file = NULL;
}
}
// main.c - Example Usage (not part of the self-contained system)
#include "logger.h"
int main() {
LoggerConfig logger;
logger_init(&logger, "application.log");
logger_log(&logger, 0, "Debug message");
logger_log(&logger, 1, "Info message");
logger_log(&logger, 2, "Warning message");
logger_log(&logger, 3, "Error message");
logger_close(&logger);
return 0;
}
The Self-Contained System pattern aims to minimize dependencies between components by encapsulating all necessary resources within a single unit. This promotes modularity, testability, and reusability. Here, we demonstrate this with a simple Logger class that manages its own output stream. Instead of relying on a global stream or passing one in, the logger creates and uses its own ofstream. This makes the logger independent and easier to reason about. The implementation uses RAII (Resource Acquisition Is Initialization) with the ofstream to ensure the file is properly closed when the Logger object goes out of scope, a common and preferred C++ practice for resource management.
#include <iostream>
#include <fstream>
#include <string>
class Logger {
public:
Logger(const std::string& filename) : file_(filename) {
if (!file_.is_open()) {
std::cerr << "Error opening log file: " << filename << std::endl;
}
}
void log(const std::string& message) {
if (file_.is_open()) {
file_ << message << std::endl;
file_.flush(); // Ensure immediate writing
}
}
~Logger() {
if (file_.is_open()) {
file_.close();
}
}
private:
std::ofstream file_;
};
int main() {
Logger logger("application.log");
logger.log("Application started.");
logger.log("Performing some operation...");
return 0;
}
The Self-Contained System pattern aims to minimize dependencies by bundling everything a component needs—code, configurations, and even runtime components—within itself. This promotes portability, simplifies deployment, and avoids conflicts arising from shared dependencies. The C# example below demonstrates this by creating a simple console application with its own embedded resources for configuration, avoiding external config files or package references beyond the standard .NET runtime. This approach is common in scenarios like developing tools intended for use in restricted environments or where controlling the entire execution context is paramount. It leverages C#’s capability to embed resources directly into an executable.
// SelfContainedApp.cs
using System;
using System.IO;
using System.Reflection;
namespace SelfContainedApp
{
class Program
{
static void Main(string[] args)
{
string configData = ReadEmbeddedResource("SelfContainedApp.Config.txt");
Console.WriteLine("Loaded Configuration:");
Console.WriteLine(configData);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
static string ReadEmbeddedResource(string resourceName)
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (stream == null)
{
return "Error: Resource not found.";
}
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}
}
// SelfContainedApp.Config.txt (embedded resource)
Setting1=Value1
Setting2=Value2
(To make this truly self-contained in a real-world scenario, one would embed potentially other dependencies. However, for clarity of demonstrating the pattern, only a simple config file is embedded.)
The Self-Contained Systems pattern advocates encapsulating a specific functionality within a single, independent component, handling all necessary dependencies internally. This promotes reusability, reduces coupling, and simplifies testing. Our TypeScript example creates a SalesReportGenerator class that encapsulates all logic for generating a sales report from a data source. It receives the raw data, calculates totals, and formats the report as a string, hiding implementation details from the consuming code. This approach aligns with TypeScript’s class-based structure for encapsulation, using a dedicated class and internal methods to manage the report generation process without external interference, improving modularity and maintainability.
// src/sales-report-generator.ts
class SalesReportGenerator {
private data: { product: string; sales: number }[];
constructor(data: { product: string; sales: number }[]) {
this.data = data;
}
private calculateTotalSales(): number {
return this.data.reduce((sum, item) => sum + item.sales, 0);
}
private formatReportData(): string {
return this.data.map(item => `${item.product}: $${item.sales}`).join('\n');
}
public generateReport(): string {
const totalSales = this.calculateTotalSales();
const formattedData = this.formatReportData();
return `
Sales Report:
----------------
${formattedData}
----------------
Total Sales: $${totalSales}
`;
}
}
// Example Usage (can be in a separate file)
const salesData = [
{ product: "Laptop", sales: 1200 },
{ product: "Keyboard", sales: 100 },
{ product: "Mouse", sales: 50 },
];
const reportGenerator = new SalesReportGenerator(salesData);
const report = reportGenerator.generateReport();
console.log(report);
The Self-Contained Systems pattern aims to minimize dependencies between software components, creating modules with well-defined interfaces and internal implementations. This promotes modularity, testability, and reusability. The example below demonstrates this by encapsulating order processing logic within a single OrderProcessor class. It handles order validation and processing internally, exposing only processOrder and getOrderStatus. This style leverages JavaScript’s class-based structure to contain the system, aligning with modern JavaScript practices for creating organized, maintainable code. No external state is required for processing–everything needed is passed to the processOrder function.
/**
* OrderProcessor Class - A self-contained system for handling orders.
*/
class OrderProcessor {
constructor() {
this.orders = {};
this.orderIdCounter = 1;
}
/**
* Processes a new order.
* @param {object} orderData - Data for the order (e.g., items, customer info).
* @returns {number} - The order ID.
* @throws {Error} - If order data is invalid.
*/
processOrder(orderData) {
if (!orderData || !orderData.items || !Array.isArray(orderData.items) || orderData.items.length === 0) {
throw new Error("Invalid order data provided.");
}
const orderId = this.orderIdCounter++;
this.orders[orderId] = {
...orderData,
status: 'pending'
};
return orderId;
}
/**
* Gets the status of an order.
* @param {number} orderId - The ID of the order.
* @returns {string | undefined} - The order status, or undefined if the order is not found.
*/
getOrderStatus(orderId) {
return this.orders[orderId]?.status;
}
/**
* Updates the status of an order (internal method).
* @param {number} orderId - The ID of the order.
* @param {string} newStatus - The new status to set.
*/
updateOrderStatus(orderId, newStatus) {
if (this.orders[orderId]) {
this.orders[orderId].status = newStatus;
}
}
}
// Example usage
const processor = new OrderProcessor();
const orderId = processor.processOrder({ items: [{ id: 1, quantity: 2 }, { id: 2, quantity: 1 }] });
console.log(`Order ID: ${orderId}`);
console.log(`Order Status: ${processor.getOrderStatus(orderId)}`);
processor.updateOrderStatus(orderId, 'shipped');
console.log(`Order Status: ${processor.getOrderStatus(orderId)}`);
The Self-Contained Systems pattern aims to minimize dependencies between different parts of an application, making them easier to understand, test, and modify. Each subsystem encapsulates all its dependencies and logic internally, exposing only a well-defined interface. This reduces the blast radius of changes and allows for independent evolution.
The Python example below demonstrates this through a ReportGenerator class which includes its own data source (a simple list), conversion logic, and formatting functions – isolating its operation. Rather than relying on global data structures or externally defined functions, all required functionality is bundled within the class. This aligns with Python’s emphasis on encapsulation and clear module boundaries, making the component portable and maintainable.
# report_generator.py
class ReportGenerator:
"""
A self-contained system for generating reports from internal data.
"""
def __init__(self, data):
self.data = data
def _convert_data(self):
"""Internal conversion logic."""
return [str(x) for x in self.data]
def _format_report(self, converted_data):
"""Internal formatting logic."""
return "\n".join(converted_data)
def generate_report(self):
"""Public interface to generate the report."""
converted_data = self._convert_data()
return self._format_report(converted_data)
if __name__ == "__main__":
sample_data = [1, 2, 3, 4, 5]
report_generator = ReportGenerator(sample_data)
report = report_generator.generate_report()
print(report)
The Self-Contained System pattern aims to create modules with minimal external dependencies, encapsulating all necessary resources within themselves. This improves portability, testability, and reduces potential conflicts when integrating with other systems. This Java example demonstrates this by creating a CsvReportGenerator class that includes its own CSV building logic and doesn’t rely on globally accessible data or external CSV libraries. The data it processes is passed in directly, and the output is a String, encouraging focused functionality and clear input/output. This approach is idiomatic for Java, as classes are designed to operate as independent units.
// CsvReportGenerator.java
import java.util.List;
public class CsvReportGenerator {
public String generateReport(List<ReportData> data) {
if (data == null || data.isEmpty()) {
return "";
}
StringBuilder csv = new StringBuilder();
// Header
csv.append("Name,Value\n");
// Data Rows
for (ReportData item : data) {
csv.append(item.name).append(",").append(item.value).append("\n");
}
return csv.toString();
}
// Simple data class for demonstration
public static class ReportData {
public String name;
public double value;
public ReportData(String name, double value) {
this.name = name;
this.value = value;
}
}
public static void main(String[] args) {
CsvReportGenerator generator = new CsvReportGenerator();
List<ReportData> reportData = List.of(
new ReportData("MetricA", 123.45),
new ReportData("MetricB", 67.89)
);
String report = generator.generateReport(reportData);
System.out.println(report);
}
}