Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 195 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Go Project Template

A production-ready Go project template following Clean Architecture principles, optimized for building scalable applications with PostgreSQL or MySQL.
A production-ready Go project template following Clean Architecture and Domain-Driven Design principles, optimized for building scalable applications with PostgreSQL or MySQL.

## Features

- **Modular Domain Architecture** - Domain-based code organization for scalability
- **Clean Architecture** - Separation of concerns with domain, repository, use case, and presentation layers
- **Multiple Database Support** - PostgreSQL and MySQL via unified repository layer
- **Database Migrations** - Separate migrations for PostgreSQL and MySQL using golang-migrate
Expand Down Expand Up @@ -33,17 +34,30 @@ go-project-template/
│ ├── database/ # Database connection and transaction management
│ │ ├── database.go
│ │ └── txmanager.go
│ ├── domain/ # Domain entities
│ │ └── entities.go
│ ├── http/ # HTTP server and handlers
│ ├── http/ # HTTP server and shared infrastructure
│ │ ├── middleware.go
│ │ ├── server.go
│ │ └── user_handler.go
│ ├── repository/ # Data access layer
│ │ ├── outbox_repository.go
│ │ └── user_repository.go
│ ├── usecase/ # Business logic
│ │ └── user_usecase.go
│ │ ├── response.go
│ │ └── server.go
│ ├── httputil/ # HTTP utility functions
│ │ └── response.go
│ ├── outbox/ # Outbox domain module
│ │ ├── domain/ # Outbox entities
│ │ │ └── outbox_event.go
│ │ └── repository/ # Outbox data access
│ │ └── outbox_repository.go
│ ├── user/ # User domain module
│ │ ├── domain/ # User entities
│ │ │ └── user.go
│ │ ├── http/ # User HTTP handlers
│ │ │ ├── dto/ # Request/response DTOs
│ │ │ │ ├── request.go
│ │ │ │ ├── response.go
│ │ │ │ └── mapper.go
│ │ │ └── user_handler.go
│ │ ├── repository/ # User data access
│ │ │ └── user_repository.go
│ │ └── usecase/ # User business logic
│ │ └── user_usecase.go
│ └── worker/ # Background workers
│ └── event_worker.go
├── migrations/
Expand All @@ -58,6 +72,25 @@ go-project-template/
└── go.sum
```

### Domain Module Structure

The project follows a modular domain architecture where each business domain is organized in its own directory with clear separation of concerns:

- **`domain/`** - Contains entities, value objects, and domain types (pure internal representation)
- **`usecase/`** - Implements business logic and orchestrates operations
- **`repository/`** - Handles data persistence and retrieval
- **`http/`** - Contains HTTP handlers and DTOs (Data Transfer Objects)
- **`dto/`** - Request/response DTOs and mappers (API contracts)

### Shared Utilities

- **`httputil/`** - Shared HTTP utility functions used across all domain modules (e.g., `MakeJSONResponse`)
- **`config/`** - Application-wide configuration
- **`database/`** - Database connection and transaction management
- **`worker/`** - Background processing infrastructure

This structure makes it easy to add new domains (e.g., `internal/product/`, `internal/order/`) without affecting existing modules.

## Prerequisites

- Go 1.25 or higher
Expand Down Expand Up @@ -297,12 +330,135 @@ make docker-run-migrate

## Architecture

### Modular Domain Architecture

The project follows a modular domain-driven structure where each business domain is self-contained:

**User Domain** (`internal/user/`)
- `domain/` - User entity and types
- `usecase/` - User registration, authentication logic
- `repository/` - User data persistence
- `http/` - User HTTP endpoints and handlers
- `dto/` - Request/response DTOs and mappers

**Outbox Domain** (`internal/outbox/`)
- `domain/` - OutboxEvent entity and status types
- `repository/` - Event persistence and retrieval

**Shared Infrastructure**
- `config/` - Application configuration
- `database/` - Database connection and transaction management
- `http/` - HTTP server, middleware, and shared utilities
- `httputil/` - Reusable HTTP utilities (JSON responses, error handling)
- `worker/` - Background event processing

### Benefits of This Structure

1. **Scalability** - Easy to add new domains without affecting existing code
2. **Encapsulation** - Each domain is self-contained with clear boundaries
3. **Team Collaboration** - Teams can work on different domains independently
4. **Maintainability** - Related code is co-located, making it easier to understand and modify

### Adding New Domains

To add a new domain (e.g., `product`):

```
internal/product/
├── domain/
│ └── product.go # Domain entity (no JSON tags)
├── usecase/
│ └── product_usecase.go # Business logic
├── repository/
│ └── product_repository.go # Data access
└── http/
├── dto/
│ ├── request.go # API request DTOs
│ ├── response.go # API response DTOs
│ └── mapper.go # DTO-to-domain mappers
└── product_handler.go # HTTP handlers
```

**Tips:**
- Use the shared `httputil.MakeJSONResponse` function in your HTTP handlers for consistent JSON responses
- Keep domain models free of JSON tags - use DTOs for API serialization
- Implement validation in your request DTOs
- Create mapper functions to convert between DTOs and domain models

### Clean Architecture Layers

1. **Domain Layer** (`internal/domain`) - Contains business entities and rules
2. **Repository Layer** (`internal/repository`) - Data access implementations using sqlutil
3. **Use Case Layer** (`internal/usecase`) - Application business logic
4. **Presentation Layer** (`internal/http`) - HTTP handlers and server
1. **Domain Layer** - Contains business entities and rules (e.g., `internal/user/domain`)
2. **Repository Layer** - Data access implementations using sqlutil (e.g., `internal/user/repository`)
3. **Use Case Layer** - Application business logic (e.g., `internal/user/usecase`)
4. **Presentation Layer** - HTTP handlers and server (e.g., `internal/user/http`)
5. **Utility Layer** - Shared utilities and helpers (e.g., `internal/httputil`)

### Data Transfer Objects (DTOs)

The project enforces clear boundaries between internal domain models and external API contracts using DTOs:

**Domain Models** (`internal/user/domain/user.go`)
- Pure internal representation of business entities
- No JSON tags - completely decoupled from API serialization
- Focus on business rules and domain logic

**DTOs** (`internal/user/http/dto/`)
- `request.go` - API request structures with validation
- `response.go` - API response structures
- `mapper.go` - Conversion functions between DTOs and domain models

**Example:**

```go
// Request DTO
type RegisterUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}

func (r *RegisterUserRequest) Validate() error {
if r.Name == "" {
return errors.New("name is required")
}
// ... validation logic
}

// Response DTO
type UserResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// Mapper functions
func ToRegisterUserInput(req RegisterUserRequest) usecase.RegisterUserInput {
return usecase.RegisterUserInput{
Name: req.Name,
Email: req.Email,
Password: req.Password,
}
}

func ToUserResponse(user *domain.User) UserResponse {
return UserResponse{
ID: user.ID,
Name: user.Name,
Email: user.Email,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
}
```

**Benefits:**
1. **Separation of Concerns** - Domain models evolve independently from API contracts
2. **Security** - Sensitive fields (like passwords) are never exposed in API responses
3. **Flexibility** - Different API views of the same domain model (e.g., summary vs detailed)
4. **Versioning** - Easy to maintain multiple API versions with different DTOs
5. **Validation** - Request validation happens at the DTO level before reaching domain logic

### Transaction Management

Expand All @@ -316,6 +472,30 @@ type TxManager interface {

Transactions are automatically injected into the context and used by repositories.

### HTTP Utilities

The `httputil` package provides shared HTTP utilities used across all domain modules:

**MakeJSONResponse** - Standardized JSON response formatting:

```go
import "github.com/allisson/go-project-template/internal/httputil"

func (h *ProductHandler) GetProduct(w http.ResponseWriter, r *http.Request) {
product, err := h.productUseCase.GetProduct(r.Context(), productID)
if err != nil {
httputil.MakeJSONResponse(w, http.StatusNotFound, map[string]string{
"error": "product not found",
})
return
}

httputil.MakeJSONResponse(w, http.StatusOK, product)
}
```

This ensures consistent response formatting across all HTTP endpoints and eliminates code duplication.

### Transactional Outbox Pattern

User registration demonstrates the transactional outbox pattern:
Expand Down
15 changes: 8 additions & 7 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
"github.com/allisson/go-project-template/internal/config"
"github.com/allisson/go-project-template/internal/database"
"github.com/allisson/go-project-template/internal/http"
"github.com/allisson/go-project-template/internal/repository"
"github.com/allisson/go-project-template/internal/usecase"
outboxRepository "github.com/allisson/go-project-template/internal/outbox/repository"
userRepository "github.com/allisson/go-project-template/internal/user/repository"
userUsecase "github.com/allisson/go-project-template/internal/user/usecase"
"github.com/allisson/go-project-template/internal/worker"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/mysql"
Expand Down Expand Up @@ -99,16 +100,16 @@ func runServer(ctx context.Context) error {

// Initialize components
txManager := database.NewTxManager(db)
userRepo := repository.NewUserRepository(db, cfg.DBDriver)
outboxRepo := repository.NewOutboxEventRepository(db, cfg.DBDriver)
userRepo := userRepository.NewUserRepository(db, cfg.DBDriver)
outboxRepo := outboxRepository.NewOutboxEventRepository(db, cfg.DBDriver)

userUseCase, err := usecase.NewUserUseCase(txManager, userRepo, outboxRepo)
userUseCaseInstance, err := userUsecase.NewUserUseCase(txManager, userRepo, outboxRepo)
if err != nil {
return fmt.Errorf("failed to create user use case: %w", err)
}

// Create HTTP server
server := http.NewServer(cfg.ServerHost, cfg.ServerPort, logger, userUseCase)
server := http.NewServer(cfg.ServerHost, cfg.ServerPort, logger, userUseCaseInstance)

// Setup graceful shutdown
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
Expand Down Expand Up @@ -191,7 +192,7 @@ func runWorker(ctx context.Context) error {

// Initialize components
txManager := database.NewTxManager(db)
outboxRepo := repository.NewOutboxEventRepository(db, cfg.DBDriver)
outboxRepo := outboxRepository.NewOutboxEventRepository(db, cfg.DBDriver)

workerConfig := worker.Config{
Interval: cfg.WorkerInterval,
Expand Down
Loading
Loading