Internal Architecture
This document describes the clean architecture implementation for the POS backend with complete separation of concerns between database entities, business models, and constants.
📁 Package Structure
/constants - Business Constants
- Purpose: All business logic constants, enums, and validation helpers
- Usage: Used by models, services, and validation layers
- Features:
- Type-safe enums (UserRole, OrderStatus, PaymentStatus, etc.)
- Business validation functions (IsValidUserRole, etc.)
- Default values and limits
- No dependencies on database or frameworks
/entities - Database Models
- Purpose: Database-specific models with GORM tags and hooks
- Usage: ONLY used by repository layer for database operations
- Features:
- GORM annotations (
gorm:tags) - Database relationships and constraints
- BeforeCreate/AfterCreate hooks
- Table name specifications
- SQL-specific data types
- Never used in business logic
- GORM annotations (
/models - Business Models
- Purpose: Pure business domain models without any framework dependencies
- Usage: Used by services, handlers, and business logic
- Features:
- Clean JSON serialization (
json:tags) - Validation rules (
validate:tags) - Request/Response DTOs
- Zero GORM dependencies
- Zero database annotations
- Uses constants package for type safety
- Pure business logic methods
- Clean JSON serialization (
/mappers - Data Transformation
- Purpose: Convert between entities and business models
- Usage: Bridge between repository and service layers
- Features:
- Entity ↔ Model conversion functions
- Request DTO → Entity conversion
- Entity → Response DTO conversion
- Null-safe conversions
- Slice/collection conversions
- Type conversions between constants and entities
/repository - Data Access Layer
- Purpose: Database operations using entities exclusively
- Usage: Only works with database entities
- Features:
- CRUD operations with entities
- Query methods with entities
- Private repository implementations
- Interface-based contracts
- Never references business models
🔄 Data Flow
API Request (JSON)
↓
Request DTO (models)
↓
Business Logic (services with models + constants)
↓
Entity (via mapper)
↓
Repository Layer (entities only)
↓
Database
↓
Entity (from database)
↓
Business Model (via mapper)
↓
Response DTO (models)
↓
API Response (JSON)
🎯 Key Design Principles
✅ Clean Business Models
type User struct {
ID uuid.UUID `json:"id"`
Role constants.UserRole `json:"role"`
}
type User struct {
ID uuid.UUID `gorm:"primaryKey" json:"id"`
Role string `gorm:"size:50" json:"role"`
}
✅ Type-Safe Constants
type UserRole string
const (
RoleAdmin UserRole = "admin"
)
func IsValidUserRole(role UserRole) bool { /* ... */ }
const AdminRole = "admin" ```
### ✅ **Repository Isolation**
```go
func (r *userRepository) Create(ctx context.Context, user *entities.User) error {
return r.db.Create(user).Error
}
func (r *userRepository) Create(ctx context.Context, user *models.User) error {
}
📊 Example Usage
Service Layer (Business Logic)
func (s *userService) CreateUser(req *models.UserCreateRequest) (*models.UserResponse, error) {
if !constants.IsValidUserRole(req.Role) {
return nil, errors.New("invalid role")
}
entity := mappers.UserCreateRequestToEntity(req, hashedPassword)
err := s.userRepo.Create(ctx, entity)
if err != nil {
return nil, err
}
return mappers.UserEntityToResponse(entity), nil
}
Repository Layer (Data Access)
func (r *userRepository) Create(ctx context.Context, user *entities.User) error {
return r.db.WithContext(ctx).Create(user).Error
}
Handler Layer (API)
func (h *userHandler) CreateUser(c *gin.Context) {
var req models.UserCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
resp, err := h.userService.CreateUser(&req)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(201, resp)
}
🏗️ Architecture Benefits
- 🎯 Single Responsibility: Each package has one clear purpose
- 🔒 Zero Database Leakage: Business logic never sees database concerns
- 🧪 Testability: Easy to mock interfaces and test business logic
- 🔧 Maintainability: Changes to database don't affect business models
- 🚀 Flexibility: Can change ORM without touching business logic
- 📜 API Stability: Business models provide stable contracts
- 🛡️ Type Safety: Constants package prevents invalid states
- 🧹 Clean Code: No mixed concerns anywhere in the codebase
📋 Development Guidelines
Constants Package (/constants)
- ✅ Define all business enums and constants
- ✅ Provide validation helper functions
- ✅ Include default values and limits
- ❌ Never import database or framework packages
- ❌ No business logic, only constants and validation
Models Package (/models)
- ✅ Pure business structs with JSON tags only
- ✅ Use constants package for type safety
- ✅ Include validation tags for input validation
- ✅ Separate Request/Response DTOs
- ✅ Add business logic methods (validation, calculations)
- ❌ NEVER include GORM tags or database annotations
- ❌ NEVER import database packages
- ❌ No database relationships or foreign keys
Entities Package (/entities)
- ✅ Include GORM tags and database constraints
- ✅ Define relationships and foreign keys
- ✅ Add database hooks (BeforeCreate, etc.)
- ✅ Use database-specific types
- ❌ NEVER use in business logic or handlers
- ❌ NEVER add business validation rules
Mappers Package (/mappers)
- ✅ Always check for nil inputs
- ✅ Handle type conversions between constants and strings
- ✅ Provide slice conversion helpers
- ✅ Keep conversions simple and direct
- ❌ No business logic in mappers
- ❌ No database operations
Repository Package (/repository)
- ✅ Work exclusively with entities
- ✅ Use private repository implementations
- ✅ Provide clean interface contracts
- ❌ NEVER reference business models
- ❌ NEVER import models package
🚀 Migration Complete
All packages have been successfully reorganized:
- ✅ 4 Constants files - All business constants moved to type-safe enums
- ✅ 10 Clean Model files - Zero GORM dependencies, pure business logic
- ✅ 11 Entity files - Database-only models with GORM tags
- ✅ 11 Repository files - Updated to use entities exclusively
- ✅ 2 Mapper files - Handle conversions between layers
- ✅ Complete separation - No cross-layer dependencies
The codebase now follows strict clean architecture principles with complete separation of database concerns from business logic! 🎉