DEV Community

CESAR NIKOLAS CAMAC MELENDEZ
CESAR NIKOLAS CAMAC MELENDEZ

Posted on

🏢 Enterprise Design Patterns: Building Scalable Systems with Fowler’s Patterns in Go

Inspired by Martin Fowler’s Catalog of Patterns of Enterprise Application Architecture

Enterprise applications are complex by nature. They manage large datasets, coordinate multiple services, and must remain maintainable and scalable over time.

Martin Fowler’s Enterprise Application Architecture Patterns provides a timeless catalog of design patterns that help developers structure large-scale systems.

In this article, we’ll explore Enterprise Design Patterns, understand their key categories, and implement a real-world example in Go (Golang) that demonstrates how they work in practice.


💡 What Are Enterprise Design Patterns?

Enterprise Design Patterns are reusable solutions to common architectural problems in enterprise applications — such as managing transactions, persistence, or business logic.

Fowler classifies them into several major categories:

Category Description
Domain Logic Patterns Organize business logic (e.g., Transaction Script, Domain Model, Table Module)
Data Source Architectural Patterns Handle how data is accessed and stored (e.g., Active Record, Data Mapper)
Object-Relational Behavioral Patterns Manage interaction between objects and relational data (e.g., Identity Map, Unit of Work)
Web Presentation Patterns Structure the UI and interaction (e.g., Model View Controller)
Distribution Patterns Manage distributed system behavior (e.g., Remote Facade, Data Transfer Object)

⚙️ Example: Repository + Unit of Work Pattern in Go

Let’s take a practical example of an enterprise-grade approach to managing persistence in a Go web service.

We’ll combine two Fowler patterns:

  1. 🧩 Repository Pattern — provides an abstraction layer over data access logic.
  2. 🔄 Unit of Work Pattern — tracks changes to objects during a transaction and coordinates the writing out of changes.

🧱 Project Structure


go-enterprise-patterns/
├── main.go
├── domain/
│   └── user.go
├── repository/
│   └── user_repository.go
└── unitofwork/
└── unit_of_work.go

Enter fullscreen mode Exit fullscreen mode

🧩 domain/user.go

package domain

// User represents a simple domain entity.
type User struct {
    ID    int
    Name  string
    Email string
}
Enter fullscreen mode Exit fullscreen mode

📦 repository/user_repository.go

package repository

import (
    "fmt"
    "go-enterprise-patterns/domain"
)

// UserRepository simulates a simple in-memory data storage.
type UserRepository struct {
    data map[int]*domain.User
}

// NewUserRepository initializes a new repository instance.
func NewUserRepository() *UserRepository {
    return &UserRepository{data: make(map[int]*domain.User)}
}

// Add inserts a new user into the repository.
func (r *UserRepository) Add(user *domain.User) {
    r.data[user.ID] = user
    fmt.Println("🟢 Added user:", user.Name)
}

// GetAll retrieves all stored users.
func (r *UserRepository) GetAll() []*domain.User {
    users := []*domain.User{}
    for _, u := range r.data {
        users = append(users, u)
    }
    return users
}
Enter fullscreen mode Exit fullscreen mode

🔄 unitofwork/unit_of_work.go

package unitofwork

import (
    "fmt"
    "go-enterprise-patterns/domain"
    "go-enterprise-patterns/repository"
)

// UnitOfWork tracks pending changes and commits them as a single transaction.
type UnitOfWork struct {
    newEntities []*domain.User
    repo        *repository.UserRepository
}

// NewUnitOfWork creates a new UnitOfWork.
func NewUnitOfWork(repo *repository.UserRepository) *UnitOfWork {
    return &UnitOfWork{
        newEntities: []*domain.User{},
        repo:        repo,
    }
}

// RegisterNew queues a new user for persistence.
func (u *UnitOfWork) RegisterNew(user *domain.User) {
    fmt.Println("📝 Registering new user:", user.Name)
    u.newEntities = append(u.newEntities, user)
}

// Commit saves all queued users in a single transaction.
func (u *UnitOfWork) Commit() {
    for _, user := range u.newEntities {
        u.repo.Add(user)
    }
    u.newEntities = []*domain.User{}
    fmt.Println("✅ Transaction committed.")
}
Enter fullscreen mode Exit fullscreen mode

🚀 main.go

package main

import (
    "go-enterprise-patterns/domain"
    "go-enterprise-patterns/repository"
    "go-enterprise-patterns/unitofwork"
)

func main() {
    // Initialize repository and unit of work
    repo := repository.NewUserRepository()
    uow := unitofwork.NewUnitOfWork(repo)

    // Create domain entities
    user1 := &domain.User{ID: 1, Name: "Alice", Email: "alice@example.com"}
    user2 := &domain.User{ID: 2, Name: "Bob", Email: "bob@example.com"}

    // Register new entities for persistence
    uow.RegisterNew(user1)
    uow.RegisterNew(user2)

    // Commit transaction
    uow.Commit()

    // Retrieve all users from repository
    for _, user := range repo.GetAll() {
        println("👤", user.Name, "-", user.Email)
    }
}
Enter fullscreen mode Exit fullscreen mode

🧠 Why It Matters

This pattern combination helps ensure that:

  • Business logic stays independent from database details
  • 🧩 You can batch commit all new entities in one transaction
  • 🧪 It becomes easier to test or swap out data sources later (e.g., move from in-memory to PostgreSQL)

This approach scales as your system grows — a foundational step for clean, maintainable enterprise applications.


🔗 GitHub Repository

👉 Full source code is available on GitHub:
https://github.com/yourusername/go-enterprise-patterns


🏁 Final Thoughts

Enterprise patterns aren’t just academic ideas — they are battle-tested abstractions that help teams manage complexity in real-world systems.

By implementing these patterns in languages like Go, Rust, or Python, you create software that’s modular, testable, and adaptable to change — all core values in modern enterprise development.


📚 Further Reading


💬 If you found this article useful, drop a ❤️ on Dev.to or follow me for more deep dives into software architecture and Go!

Top comments (0)