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:
- 🧩 Repository Pattern — provides an abstraction layer over data access logic.
- 🔄 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
🧩 domain/user.go
package domain
// User represents a simple domain entity.
type User struct {
ID int
Name string
Email string
}
📦 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
}
🔄 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.")
}
🚀 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)
}
}
🧠 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
- Martin Fowler – Patterns of Enterprise Application Architecture
- Refactoring Guru – Repository Pattern
- Go Clean Architecture Example
💬 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)