Monolith
The Monolith is a traditional software architectural style that structures an application as a single, self-contained unit. All components – user interface, business logic, data access, and database – are bundled together and deployed as one. This approach simplifies initial development and deployment, as everything resides in a single codebase and environment.
However, as the application grows in complexity, the monolith can become difficult to understand, maintain, and scale. Changes in one part of the application can have unintended consequences in others, and the entire application needs to be redeployed for even minor updates. Despite these drawbacks, the monolith remains a common starting point for many projects, especially those with limited scope or resources.
Usage
The Monolith pattern is commonly used in:
- Small to Medium-Sized Applications: Where the complexity is manageable and the benefits of microservices don’t outweigh the overhead.
- Rapid Prototyping: Its simplicity allows for quick development and iteration.
- Legacy Systems: Many older applications were originally built as monoliths and are still in use today.
- Teams with Limited DevOps Experience: Deploying and managing a single unit is easier than coordinating multiple microservices.
Examples
- WordPress: Initially designed as a monolithic application, WordPress handles content management, user authentication, themes, and plugins within a single codebase. While it has evolved to support some plugin isolation, the core remains a monolith.
- Ruby on Rails Applications (Early Stages): A typical “Rails” application, especially when first created, often follows a monolithic architecture. All the application’s layers (model, view, controller) are tightly integrated within the same deployment unit. As these applications grow, developers often consider breaking them down into microservices.
- Early Netflix: Before its widespread adoption of microservices, Netflix was a monolithic application. It handled everything from user accounts and recommendations to video streaming within a single system. The challenges of scaling and maintaining this monolith led to its eventual decomposition.
Specimens
15 implementationsThe Monolith pattern involves building an application as a single, unified unit. All features and functionalities are tightly coupled and deployed as one. While often criticized for scaling challenges, it offers simplicity in development, testing, and initial deployment. This Dart example showcases a simplistic “Monolith” by containing all business logic—user management and product catalog—within the same app.dart file and a single MyApp class. There’s no separation into microservices or distinct modules. This is a direct reflection of how smaller Dart applications are frequently structured, utilizing classes to encapsulate state and behavior.
// app.dart
class User {
String name;
String email;
User({required this.name, required this.email});
String greet() => 'Hello, ${name}!';
}
class Product {
String name;
double price;
Product({required this.name, required this.price});
String displayPrice() => '\$${price.toStringAsFixed(2)}';
}
class MyApp {
List<User> users = [];
List<Product> products = [];
void addUser(User user) {
users.add(user);
}
void addProduct(Product product) {
products.add(product);
}
List<String> getUserGreetings() {
return users.map((user) => user.greet()).toList();
}
List<String> getProductDetails() {
return products.map((product) => '${product.name} - ${product.displayPrice()}').toList();
}
}
void main() {
final app = MyApp();
app.addUser(User(name: 'Alice', email: 'alice@example.com'));
app.addUser(User(name: 'Bob', email: 'bob@example.com'));
app.addProduct(Product(name: 'Laptop', price: 1200.00));
app.addProduct(Product(name: 'Mouse', price: 25.00));
print('User Greetings:');
for (final greeting in app.getUserGreetings()) {
print(greeting);
}
print('\nProduct Details:');
for (final detail in app.getProductDetails()) {
print(detail);
}
}
The Monolith pattern represents a tightly coupled, single-tier software application built as a unified unit. While often criticized for scaling and deployment challenges, it simplifies initial development and can be performant for smaller applications. This Scala example shows a basic blueprint for a monolithic application. It combines data handling, business logic, and presentation (via simple println statements) all within a single object, simulating a classic monolith structure. This approach reflects Scala’s capability for concise, object-oriented programs where application logic resides within objects. No explicit interfaces or loose coupling techniques are used, demonstrating the tightly-integrated nature of a monolith.
object Monolith {
// Data Model
case class User(id: Int, name: String, email: String)
// Data Store (in-memory for simplicity)
private var users: List[User] = List.empty
// Business Logic
def createUser(name: String, email: String): User = {
val id = users.length + 1
val newUser = User(id, name, email)
users = newUser :: users
newUser
}
def getUserById(id: Int): Option[User] = {
users.find(_.id == id)
}
def getAllUsers(): List[User] = {
users
}
// Presentation Layer (Console Output)
def main(args: Array[String]): Unit = {
val user1 = createUser("Alice", "alice@example.com")
val user2 = createUser("Bob", "bob@example.com")
println(s"Created user: $user1")
println(s"Created user: $user2")
println(s"User with ID 1: ${getUserById(1)}")
println(s"All Users: ${getAllUsers()}")
}
}
The Monolith pattern represents a traditional software architecture where all functionalities are tightly coupled and deployed as a single, indivisible unit. This example showcases a simple PHP monolith handling user registration and basic greetings. All related code – database connection, user handling, and view output – resides within a single script. This simplicity is characteristic of monoliths, though it can become unwieldy at scale. The PHP implementation directly reflects this direct, procedural approach, focusing on immediate execution without extensive class or interface definitions. This is common for smaller PHP projects or quick prototypes where the complexity doesn’t yet warrant a more distributed architecture.
<?php
// Database configuration
$db_host = "localhost";
$db_user = "root";
$db_pass = "";
$db_name = "monolith_example";
// Connect to the database
$conn = new mysqli($db_host, $db_user, $db_pass, $db_name);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// Handle user registration
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = $_POST["username"];
$email = $_POST["email"];
$sql = "INSERT INTO users (username, email) VALUES ('$username', '$email')";
if ($conn->query($sql) === TRUE) {
$message = "Registration successful!";
} else {
$message = "Error: " . $sql . "<br>" . $conn->error;
}
}
// Retrieve and display users
$sql = "SELECT id, username FROM users";
$result = $conn->query($sql);
$users = [];
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$users[] = $row;
}
}
// Output HTML
?>
<!DOCTYPE html>
<html>
<head>
<title>Monolith Example</title>
</head>
<body>
<h1>User Registration</h1>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
Username: <input type="text" name="username"><br>
Email: <input type="email" name="email"><br>
<input type="submit" value="Register">
</form>
<?php if (isset($message)): ?>
<p><?php echo $message; ?></p>
<?php endif; ?>
<h2>Registered Users</h2>
<ul>
<?php foreach ($users as $user): ?>
<li><?php echo $user["username"]; ?> (ID: <?php echo $user["id"]; ?>)</li>
<?php endforeach; ?>
</ul>
</body>
</html>
<?php
$conn->close();
?>
The Monolith pattern represents an architectural style where an application is built as a single, unified unit. All functionalities are tightly coupled and deployed together. This contrasts with microservices, which decompose an application into independently deployable services. This Ruby example showcases a basic monolith by encapsulating user management and product catalog operations within a single Shop class. While simple, it illustrates the core idea of a single codebase handling all aspects of the application. Ruby’s flexible class structure allows for easy organization of related functionalities within the monolith, though it can lead to a large and complex codebase over time.
# shop.rb
class Shop
def initialize
@users = []
@products = {}
end
# User Management
def add_user(user)
@users << user
end
def get_user(username)
@users.find { |u| u.username == username }
end
# Product Catalog
def add_product(product)
@products[product.id] = product
end
def get_product(id)
@products[id]
end
def list_products
@products.values
end
end
class User
attr_accessor :username
def initialize(username)
@username = username
end
end
class Product
attr_accessor :id, :name, :price
def initialize(id, name, price)
@id = id
@name = name
@price = price
end
end
# Example Usage
shop = Shop.new
user1 = User.new("Alice")
user2 = User.new("Bob")
shop.add_user(user1)
shop.add_user(user2)
product1 = Product.new(1, "Shirt", 25)
product2 = Product.new(2, "Pants", 50)
shop.add_product(product1)
shop.add_product(product2)
puts "Usernames: #{@users.map(&:username)}"
puts "Products:"
shop.list_products.each do |product|
puts "- #{product.name} ($#{product.price})"
end
The Monolith pattern represents a traditional, unified application structure where all components are tightly coupled and deployed as a single unit. Our Swift example will simulate this by having a single App class managing all functionality – user management, data handling, and a simple display mechanism. While modern Swift development often favors modularity, this demonstrates the pattern’s core concept of everything residing within a single codebase. The implementation uses a simple class structure and avoids frameworks to highlight the tightly coupled nature. This fits Swift’s object-oriented capabilities but represents anti-pattern usage from a contemporary perspective. It prioritizes simplicity in demonstration over best practices for large-scale Swift projects.
// App.swift
class App {
private var users: [String: String] = [:] // [username: password]
func registerUser(username: String, password: String) {
users[username] = password
print("User \(username) registered.")
}
func loginUser(username: String, password: String) -> Bool {
if let storedPassword = users[username], storedPassword == password {
print("User \(username) logged in.")
return true
} else {
print("Login failed for user \(username).")
return false
}
}
func displayUsers() {
print("Registered Users:")
for (username, _) in users {
print("- \(username)")
}
}
}
// Example Usage
let app = App()
app.registerUser(username: "alice", password: "password123")
app.registerUser(username: "bob", password: "secret")
app.loginUser(username: "alice", password: "password123")
app.loginUser(username: "charlie", password: "wrongpassword")
app.displayUsers()
The Monolith pattern represents a traditional software architecture where all components of an application are tightly coupled and deployed as a single unit. It’s characterized by a unified codebase, often with shared libraries and data models. This example demonstrates a simplified monolith in Kotlin, representing a basic e-commerce application with Product, Order, and Customer functionalities all contained within the same project. Kotlin’s flexibility allows for both OOP and functional approaches within a monolith, and this example leans towards a relatively OOP structure to model the domain. The lack of explicit separation into microservices, with inter-component calls happening directly, embodies the monolithic nature.
// src/main/kotlin/ecommerce/EcommerceApp.kt
package ecommerce
import java.util.UUID
data class Product(val id: UUID, val name: String, val price: Double)
data class Customer(val id: UUID, val name: String, val email: String)
data class Order(val id: UUID, val customer: Customer, val products: List<Product>, val total: Double)
class Inventory {
private val products = mutableListOf<Product>()
fun addProduct(product: Product) {
products.add(product)
}
fun getProductById(id: UUID): Product? {
return products.find { it.id == id }
}
}
class OrderService(private val inventory: Inventory, private val customerService: CustomerService) {
fun createOrder(customerId: UUID, productIds: List<UUID>): Order? {
val customer = customerService.getCustomerById(customerId) ?: return null
val products = inventory.getProductByIds(productIds)
if (products.isEmpty()) return null
val total = products.sumOf { it.price }
val order = Order(UUID.randomUUID(), customer, products, total)
println("Order created: $order") //Simulating order persistence
return order
}
fun getProductByIds(productIds: List<UUID>): List<Product> {
return productIds.mapNotNull { inventory.getProductById(it) }
}
}
class CustomerService {
private val customers = mutableListOf<Customer>()
fun addCustomer(customer: Customer) {
customers.add(customer)
}
fun getCustomerById(id: UUID): Customer? {
return customers.find { it.id == id }
}
}
fun main() {
val inventory = Inventory()
val customerService = CustomerService()
val orderService = OrderService(inventory, customerService)
val product1 = Product(UUID.randomUUID(), "Laptop", 1200.0)
val product2 = Product(UUID.randomUUID(), "Mouse", 25.0)
inventory.addProduct(product1)
inventory.addProduct(product2)
val customer1 = Customer(UUID.randomUUID(), "Alice", "alice@example.com")
customerService.addCustomer(customer1)
orderService.createOrder(customer1.id, listOf(product1.id, product2.id))
}
The Monolith pattern refers to building an application as a single, unified unit. All functionalities and concerns are tightly coupled within one codebase. This contrasts with microservices, where functionality is broken down into independently deployable services. This Rust example embodies the Monolith pattern by placing web server logic, data access, and application logic within the same main.rs file and module structure, avoiding separate crates for different concerns. The tight coupling, demonstrated by direct function calls between components, is characteristic of this pattern. Rust’s module system helps organize the monolithic structure, but doesn’t inherently prevent it.
// main.rs
mod api;
mod database;
mod models;
use actix_web::{web, App, HttpResponse, HttpServer};
use database::Database;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db = Database::new("my_data.db").await;
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(db))
.service(api::get_data)
.service(api::add_data)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
// api.rs
use actix_web::{get, post, web, HttpResponse, Responder};
use crate::database::Database;
use crate::models::Data;
#[get("/data")]
pub async fn get_data(db: web::Data<Database>) -> impl Responder {
let data = db.get_all_data().await;
HttpResponse::Ok().json(data)
}
#[post("/data")]
pub async fn add_data(db: web::Data<Database>, item: web::Json<Data>) -> impl Responder {
db.insert_data(item.into_inner()).await;
HttpResponse::Created().finish()
}
// database.rs
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
use crate::models::Data;
pub struct Database {
pool: SqlitePool,
}
impl Database {
pub async fn new(database_url: &str) -> Self {
let pool = SqlitePoolOptions::new()
.max_connections(5)
.connect(database_url)
.await
.expect("Failed to connect to database");
Self { pool }
}
pub async fn get_all_data(&self) -> Vec<Data> {
sqlx::query_as::<_, Data>("SELECT id, name FROM data")
.fetch_all(&self.pool)
.await
.expect("Failed to fetch data")
}
pub async fn insert_data(&self, data: Data) {
sqlx::query("INSERT INTO data (name) VALUES (?)")
.bind(data.name)
.execute(&self.pool)
.await
.expect("Failed to insert data");
}
}
// models.rs
#[derive(sqlx::FromRow, serde::Serialize)]
pub struct Data {
pub id: i64,
pub name: String,
}
The Monolith pattern structures an application as a single, unified unit. All functionalities, from database interactions to UI logic, reside within the same codebase and are typically deployed as a single process. This example models a simple e-commerce application where product management, user accounts, and order processing are all integrated into a single main.go file. This is typical of Go’s encouragement of simple, self-contained executables. While not scalable in the same way as microservices, a monolith demonstrates simplicity in initial development and deployment, leveraging Go’s built-in concurrency features for internal parallelism.
// main.go
package main
import (
"fmt"
)
// Product represents an item for sale.
type Product struct {
ID int
Name string
Price float64
}
// User represents a customer.
type User struct {
ID int
Name string
Email string
}
// Order represents a customer's purchase.
type Order struct {
ID int
UserID int
Items []Product
Total float64
}
var (
products = []Product{
{ID: 1, Name: "Laptop", Price: 1200},
{ID: 2, Name: "Mouse", Price: 25},
}
users = []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
orders = []Order{}
)
// addProduct adds a new product to the system.
func addProduct(name string, price float64) {
newID := len(products) + 1
products = append(products, Product{ID: newID, Name: name, Price: price})
fmt.Printf("Product %s added with ID %d\n", name, newID)
}
// createOrder creates a new order for a user.
func createOrder(userID int, productIDs []int) {
orderItems := []Product{}
total := 0.0
for _, productID := range productIDs {
for _, product := range products {
if product.ID == productID {
orderItems = append(orderItems, product)
total += product.Price
}
}
}
newOrderID := len(orders) + 1
orders = append(orders, Order{ID: newOrderID, UserID: userID, Items: orderItems, Total: total})
fmt.Printf("Order %d created for User %d with a total of $%.2f\n", newOrderID, userID, total)
}
func main() {
addProduct("Keyboard", 75.00)
createOrder(1, []int{1, 2, 3})
createOrder(2, []int{2})
fmt.Println("\nProducts:")
for _, p := range products {
fmt.Printf("ID: %d, Name: %s, Price: %.2f\n", p.ID, p.Name, p.Price)
}
fmt.Println("\nOrders:")
for _, o := range orders {
fmt.Printf("ID: %d, UserID: %d, Total: %.2f\n", o.ID, o.UserID, o.Total)
}
}
The Monolith pattern represents a traditional, unified application architecture where all components are tightly coupled and deployed as a single unit. This contrasts with microservices, where applications are composed of independent services. The implementation here shows a basic simulation of an order processing system—receiving orders, validating them, updating inventory, and processing payments—all contained within a single order_system.c file and main function. This monolithic approach is common in C, where direct dependency management is usually handled at the compilation stage and a large, single executable is often the norm, especially for resource-constrained or simple applications. There’s minimal attempt at abstraction beyond function definitions, reflecting C’s procedural nature and the typical simplicity of monoliths.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Data structures (shared across the monolith)
typedef struct {
int item_id;
int quantity;
} OrderItem;
typedef struct {
int order_id;
OrderItem items[10];
int num_items;
} Order;
// Global state (common in monoliths - be cautious!)
int inventory[10] = {10, 5, 20, 15, 8, 12, 6, 18, 11, 7};
// Function prototypes
int receive_order(Order *order);
int validate_order(Order *order);
int update_inventory(Order *order);
int process_payment(Order *order, double amount);
void print_order(Order *order);
int main() {
Order order;
order.order_id = 123;
order.num_items = 2;
order.items[0].item_id = 0;
order.items[0].quantity = 2;
order.items[1].item_id = 1;
order.items[1].quantity = 1;
if (receive_order(&order) == 0) {
if (validate_order(&order) == 0) {
double total_amount = order.items[0].quantity * 10.0 + order.items[1].quantity * 15.0; // Simple price calculation
if (update_inventory(&order) == 0) {
if (process_payment(&order, total_amount) == 0) {
printf("Order processed successfully!\n");
print_order(&order);
} else {
printf("Payment processing failed.\n");
}
} else {
printf("Inventory update failed.\n");
}
} else {
printf("Order validation failed.\n");
}
} else {
printf("Order reception failed.\n");
}
return 0;
}
// Receive Order (simply assigns values, in a real scenario, this would involve network calls or file reads)
int receive_order(Order *order) {
// In a real system. handle errors, logging, etc.
return 0; // Success
}
// Validate Order
int validate_order(Order *order) {
for (int i = 0; i < order->num_items; i++) {
if (order->items[i].item_id < 0 || order->items[i].item_id >= 10) {
printf("Invalid item ID: %d\n", order->items[i].item_id);
return -1; // Failure
}
if (order->items[i].quantity <= 0) {
printf("Invalid quantity: %d\n", order->items[i].quantity);
return -1; // Failure
}
}
return 0; // Success
}
// Update Inventory
int update_inventory(Order *order) {
for (int i = 0; i < order->num_items; i++) {
int item_id = order->items[i].item_id;
int quantity = order->items[i].quantity;
if (inventory[item_id] < quantity) {
printf("Not enough stock for item %d\n", item_id);
return -1; // Failure
}
inventory[item_id] -= quantity;
}
return 0; // Success
}
// Process Payment
int process_payment(Order *order, double amount) {
// Simulate payment processing (e.g., interacting with a payment gateway)
printf("Processing payment of %.2f for order %d\n", amount, order->order_id);
return 0; // Success
}
// Print Order details
void print_order(Order *order) {
printf("Order ID: %d\n", order->order_id);
printf("Items:\n");
for (int i = 0; i < order->num_items; i++) {
printf(" Item ID: %d, Quantity: %d\n", order->items[i].item_id, order->items[i].quantity);
}
}
The Monolith pattern refers to building an application as a single, unified unit. All functionalities and concerns are tightly coupled within a single codebase. This implementation demonstrates a basic example where all related operations – handling data, performing calculations, and displaying results – reside within the Calculator class and the main function. C++ naturally supports this pattern given its procedural and object-oriented nature, and it’s a very common starting point for smaller applications. The tight coupling doesn’t aim for elegance here, rather to show the essence of everything being in one place. No dependency management or modularity is specifically implemented, reflecting the core concept of the pattern.
#include <iostream>
#include <string>
#include <stdexcept>
class Calculator {
public:
Calculator() {}
double add(double a, double b) {
return a + b;
}
double subtract(double a, double b) {
return a - b;
}
double multiply(double a, double b) {
return a * b;
}
double divide(double a, double b) {
if (b == 0) {
throw std::runtime_error("Division by zero!");
}
return a / b;
}
void run() {
std::string operation;
double num1, num2;
std::cout << "Enter an operation (+, -, *, /): ";
std::cin >> operation;
std::cout << "Enter two numbers: ";
std::cin >> num1 >> num2;
try {
double result;
if (operation == "+") {
result = add(num1, num2);
} else if (operation == "-") {
result = subtract(num1, num2);
} else if (operation == "*") {
result = multiply(num1, num2);
} else if (operation == "/") {
result = divide(num1, num2);
} else {
std::cout << "Invalid operation." << std::endl;
return;
}
std::cout << "Result: " << result << std::endl;
} catch (const std::runtime_error& error) {
std::cout << "Error: " << error.what() << std::endl;
}
}
};
int main() {
Calculator calculator;
calculator.run();
return 0;
}
The Monolith pattern, in its most basic form, represents a traditional software architecture where all components of an application are tightly coupled and deployed as a single unit. While often criticized for scalability challenges, it simplifies development and initial deployment. This C# example presents a complete, albeit simplified, application handling basic product operations (add, list) within a single project. It utilizes a single class ProductManager to encapsulate all functionalities, demonstrating the core concept of a monolith – everything resides in one place. While a real-world monolith would be much larger and more complex, this example reflects the architectural style by lacking distinct service boundaries. The use of simple console I/O and a list for storage is common in simple C# applications and reflects a pragmatic approach to demonstrating the pattern.
// Program.cs
using System;
using System.Collections.Generic;
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductManager
{
private readonly List<Product> _products = new List<Product>();
public void AddProduct(string name, decimal price)
{
_products.Add(new Product { Name = name, Price = price });
Console.WriteLine($"Product '{name}' added successfully.");
}
public void ListProducts()
{
if (_products.Count == 0)
{
Console.WriteLine("No products available.");
return;
}
Console.WriteLine("Available Products:");
foreach (var product in _products)
{
Console.WriteLine($"- {product.Name}: ${product.Price}");
}
}
}
public class Program
{
public static void Main(string[] args)
{
ProductManager manager = new ProductManager();
manager.AddProduct("Laptop", 1200.00m);
manager.AddProduct("Mouse", 25.00m);
manager.ListProducts();
}
}
The Monolith pattern advocates for building a single, unified application. All functionalities are bundled and deployed as a single unit. While often discussed negatively in modern microservices architecture, it offers simplicity in development, testing, and initial deployment. This TypeScript example showcases a basic monolithic structure. It avoids unnecessary modularization, placing all related logic (user management, product catalog, and order processing) within a single app.ts file and relying on straightforward function calls for interaction. This is typical of rapidly developed, smaller-scale TypeScript applications where the benefits of extensive modularity don’t yet outweigh the costs.
// app.ts
interface User {
id: number;
name: string;
}
interface Product {
id: number;
name: string;
price: number;
}
interface Order {
id: number;
userId: number;
productId: number;
quantity: number;
}
let users: User[] = [];
let products: Product[] = [];
let orders: Order[] = [];
// User Management
function createUser(name: string): User {
const id = users.length + 1;
const newUser = { id, name };
users.push(newUser);
return newUser;
}
function getUser(id: number): User | undefined {
return users.find(user => user.id === id);
}
// Product Catalog
function createProduct(name: string, price: number): Product {
const id = products.length + 1;
const newProduct = { id, name, price };
products.push(newProduct);
return newProduct;
}
function getProduct(id: number): Product | undefined {
return products.find(product => product.id === id);
}
// Order Processing
function createOrder(userId: number, productId: number, quantity: number): Order {
const id = orders.length + 1;
const newOrder = { id, userId, productId, quantity };
orders.push(newOrder);
return newOrder;
}
function getOrder(id: number): Order | undefined {
return orders.find(order => order.id === id);
}
// Example Usage
const user1 = createUser("Alice");
const product1 = createProduct("Laptop", 1200);
const order1 = createOrder(user1.id, product1.id, 2);
console.log("User:", user1);
console.log("Product:", product1);
console.log("Order:", order1);
The Monolith pattern, in its simplest form, is an architectural style where an application is built as a single, unified unit. All components and functionalities are tightly coupled and deployed as one. This contrasts with microservices, which break down an application into independent services. This example demonstrates a basic JavaScript monolith structuring an e-commerce application with product listing, cart management, and checkout within a single file. It utilizes straightforward function calls and shared state (the cart array) to represent this tight coupling, typical of the pattern. This approach is common in smaller JavaScript applications or quick prototypes, prioritizing simplicity over scalability and maintainability.
// ecommerce-monolith.js
let products = [
{ id: 1, name: 'Laptop', price: 1200 },
{ id: 2, name: 'Mouse', price: 25 },
{ id: 3, name: 'Keyboard', price: 75 },
];
let cart = [];
function listProducts() {
console.log('Available Products:');
products.forEach(product => {
console.log(`${product.name} - $${product.price}`);
});
}
function addToCart(productId) {
const product = products.find(p => p.id === productId);
if (product) {
cart.push(product);
console.log(`${product.name} added to cart.`);
} else {
console.log('Product not found.');
}
}
function viewCart() {
if (cart.length === 0) {
console.log('Cart is empty.');
return;
}
console.log('Your Cart:');
let total = 0;
cart.forEach(item => {
console.log(`${item.name} - $${item.price}`);
total += item.price;
});
console.log(`Total: $${total}`);
}
function checkout() {
if (cart.length === 0) {
console.log('Cannot checkout - cart is empty.');
return;
}
let total = cart.reduce((sum, item) => sum + item.price, 0);
console.log(`Checkout complete. Total amount: $${total}`);
cart = []; // Empty the cart after checkout
}
// Example Usage:
listProducts();
addToCart(1);
addToCart(2);
viewCart();
checkout();
The Monolith pattern structures an application as a single, self-contained unit. All functionalities, including UI, business logic, and data access, are tightly coupled and deployed as one. This simplicity aids initial development and debugging but can gradually lead to complexity, making scaling and independent deployments challenging.
The provided Python example simulates a monolithic e-commerce application. All components – Product, ShoppingCart, OrderProcessing, and a basic main function driving interactions – are defined within the same script. This reflects the core idea of a single codebase handling all concerns. While a real-world monolith would likely be split across modules within a larger project, this concise example demonstrates the architectural principle. Python’s flexible nature allows for easy formulation of such a structure, emphasizing the all-in-one design.
class Product:
def __init__(self, id, name, price):
self.id = id
self.name = name
self.price = price
def __repr__(self):
return f"Product(id={self.id}, name='{self.name}', price={self.price})"
class ShoppingCart:
def __init__(self):
self.items = []
def add_item(self, product, quantity):
self.items.append({"product": product, "quantity": quantity})
def total_price(self):
return sum(item["product"].price * item["quantity"] for item in self.items)
def __repr__(self):
return f"ShoppingCart(items={self.items})"
class OrderProcessing:
def __init__(self, shopping_cart):
self.shopping_cart = shopping_cart
def process_order(self, shipping_address, payment_info):
total = self.shopping_cart.total_price()
print(f"Processing order for total: {total}")
print(f"Shipping to: {shipping_address}")
print(f"Payment info: {payment_info}")
self.shopping_cart.items = [] # Clear cart after processing
return True
def main():
product1 = Product(1, "Shirt", 20)
product2 = Product(2, "Pants", 30)
cart = ShoppingCart()
cart.add_item(product1, 2)
cart.add_item(product2, 1)
print(f"Current Cart: {cart}")
order_processor = OrderProcessing(cart)
order_success = order_processor.process_order("123 Main St", "Visa****1234")
if order_success:
print("Order processed successfully!")
print(f"Updated Cart: {cart}") # Cart should be empty
if __name__ == "__main__":
main()
The Monolith pattern represents a traditional software architecture where an application is built as a single, unified unit. All functionalities are tightly coupled and deployed as one. This example demonstrates a simple e-commerce application built as a monolith. It includes classes for Product, ShoppingCart, Order, and a Main class to orchestrate interactions. The implementation is straightforward Java, utilizing classes and methods to represent the domain logic. It fits the monolithic style by having all components within the same project and relying on direct method calls for communication, avoiding separate services or APIs. This simplicity is characteristic of monoliths, though they can grow complex.
// Product.java
class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
// ShoppingCart.java
import java.util.ArrayList;
import java.util.List;
class ShoppingCart {
private List<Product> items = new ArrayList<>();
public void addItem(Product product) {
items.add(product);
}
public double calculateTotal() {
double total = 0;
for (Product product : items) {
total += product.getPrice();
}
return total;
}
public List<Product> getItems() {
return items;
}
}
// Order.java
import java.util.List;
class Order {
private ShoppingCart cart;
private String customerName;
public Order(ShoppingCart cart, String customerName) {
this.cart = cart;
this.customerName = customerName;
}
public double getTotal() {
return cart.calculateTotal();
}
public String getCustomerName() {
return customerName;
}
public void processOrder() {
System.out.println("Processing order for " + customerName);
System.out.println("Order total: " + getTotal());
System.out.println("Items:");
for (Product product : cart.getItems()) {
System.out.println("- " + product.getName());
}
System.out.println("Order processed successfully!");
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Product product1 = new Product("Laptop", 1200.00);
Product product2 = new Product("Mouse", 25.00);
ShoppingCart cart = new ShoppingCart();
cart.addItem(product1);
cart.addItem(product2);
Order order = new Order(cart, "John Doe");
order.processOrder();
}
}