25 KiB
Go Implementation Plan for AutoStore
Overview
Implementation of AutoStore system using Go, following Clean Architecture principles. The system stores items with expiration dates and automatically orders new items when they expire.
Architecture Approach
- Clean Architecture with clear separation of concerns
- Domain-Driven Design with rich domain models
- Hexagonal Architecture with dependency inversion
- Repository Pattern for data persistence
- CQRS-like command/query separation
- Dependency Injection using Go's interface system and a DI container
Core Domain Logic
ItemExpirationSpec - Single Source of Truth for Expiration
File: internal/domain/specifications/item_expiration_spec.go
Purpose: Centralized expiration checking logic - the single source of truth for determining if items are expired
Key Methods:
IsExpired(item *ItemEntity, currentTime time.Time) bool- Checks if item expiredGetSpec(currentTime time.Time) Specification[ItemEntity]- Returns specification for repository queries
Place in the flow:
- Called by
AddItemCommand.Execute()to check newly created items for immediate expiration - Called by
HandleExpiredItemsCommand.Execute()to find expired items for processing - Used by
ItemRepository.FindWhere()to query database for expired items
Detailed Implementation Plan
Domain Layer
1. Entities
File: internal/domain/entities/item.go
Purpose: Core business entity representing an item
Key Methods:
NewItem(id ItemID, name string, expirationDate ExpirationDate, orderURL string, userID UserID) (*ItemEntity, error)- Creates item with validationGetID() ItemID- Returns item IDGetName() string- Returns item nameGetExpirationDate() ExpirationDate- Returns expiration dateGetOrderURL() string- Returns order URLGetUserID() UserID- Returns user ID
Place in the flow:
- Created by
AddItemCommand.Execute() - Retrieved by
ItemRepositorymethods - Passed to
ItemExpirationSpec.IsExpired()for expiration checking
File: internal/domain/entities/user.go
Purpose: User entity for item ownership and authentication purposes
Key Methods:
NewUser(id UserID, username string, passwordHash string) (*UserEntity, error)- Creates user with validationGetID() UserID- Returns user IDGetUsername() string- Returns usernameGetPasswordHash() string- Returns password hashValidatePassword(password string) bool- Validates password
2. Value Objects
File: internal/domain/value_objects/item_id.go
Purpose: Strong typing for item identifiers
Key Methods:
NewItemID(value string) (ItemID, error)- Validates UUID formatString() string- Returns string valueEquals(other ItemID) bool- Compares with another ItemID
File: internal/domain/value_objects/user_id.go
Purpose: Strong typing for user identifiers
Key Methods:
NewUserID(value string) (UserID, error)- Validates UUID formatString() string- Returns string valueEquals(other UserID) bool- Compares with another UserID
File: internal/domain/value_objects/expiration_date.go
Purpose: Immutable expiration date with validation
Key Methods:
NewExpirationDate(value time.Time) (ExpirationDate, error)- Validates date format (allows past dates per business rules)Time() time.Time- Returns time.Time objectString() string- Returns ISO string formatIsExpired(currentTime time.Time) bool- Checks if date is expired
Place in the flow:
- Used by
ItemEntityconstructor for type-safe date handling - Validated by
ItemExpirationSpec.IsExpired()for expiration logic
3. Specifications
File: internal/domain/specifications/specification.go
Purpose: Generic specification pattern interface
Key Methods:
IsSatisfiedBy(candidate T) bool- Evaluates specificationAnd(other Specification[T]) Specification[T]- Combines with ANDOr(other Specification[T]) Specification[T]- Combines with ORNot() Specification[T]- Negates specification
File: internal/domain/specifications/item_expiration_spec.go
Purpose: Implementation of expiration specification
Key Methods:
IsExpired(item *ItemEntity, currentTime time.Time) bool- Checks if item is expiredGetSpec(currentTime time.Time) Specification[ItemEntity]- Returns specification for repository queries
Place in the flow:
- Implemented by
ItemExpirationSpecfor type-safe specifications - Used by
ItemRepository.FindWhere()for database queries
Application Layer
4. Commands
File: internal/application/commands/add_item_command.go
Purpose: Use case for creating new items with expiration handling
Key Methods:
NewAddItemCommand(itemRepo IItemRepository, orderService IOrderService, timeProvider ITimeProvider, expirationSpec *ItemExpirationSpec, logger ILogger) *AddItemCommand- Constructor with dependency injectionExecute(ctx context.Context, name string, expirationDate time.Time, orderURL string, userID string) (string, error)- Creates item, handles expired items immediately
Flow:
ItemsController.CreateItem()callsAddItemCommand.Execute()- Creates
ItemEntitywith validated data - Calls
ItemExpirationSpec.IsExpired()to check if item is expired - If expired:
- calls
OrderHTTPService.OrderItem() - returns item ID (business rule: expired items trigger ordering but still return ID field that might be empty or invalid)
- calls
- If not expired: calls
ItemRepository.Save()and returns item ID
File: internal/application/commands/handle_expired_items_command.go
Purpose: Background command to process expired items
Key Methods:
NewHandleExpiredItemsCommand(itemRepo IItemRepository, orderService IOrderService, timeProvider ITimeProvider, expirationSpec *ItemExpirationSpec, logger ILogger) *HandleExpiredItemsCommand- Constructor with dependency injectionExecute(ctx context.Context) error- Finds and processes all expired items
Flow:
ExpiredItemsScheduler.Run()callsHandleExpiredItemsCommand.Execute()- Gets current time from
ITimeProvider - Calls
ItemExpirationSpec.GetSpec()to get expiration specification - Calls
ItemRepository.FindWhere()to find expired items - For each expired item: calls
OrderHTTPService.OrderItem()thenItemRepository.Delete()
File: internal/application/commands/delete_item_command.go
Purpose: Use case for deleting user items
Key Methods:
NewDeleteItemCommand(itemRepo IItemRepository, logger ILogger) *DeleteItemCommand- Constructor with dependency injectionExecute(ctx context.Context, itemID string, userID string) error- Validates ownership and deletes item
Flow:
ItemsController.DeleteItem()callsDeleteItemCommand.Execute()- Calls
ItemRepository.FindByID()to retrieve item - Validates ownership by comparing user IDs
- Calls
ItemRepository.Delete()to remove item
File: internal/application/commands/login_user_command.go
Purpose: User authentication use case
Key Methods:
NewLoginUserCommand(authService IAuthService, logger ILogger) *LoginUserCommand- Constructor with dependency injectionExecute(ctx context.Context, username string, password string) (string, error)- Authenticates and returns JWT token
5. Queries
File: internal/application/queries/get_item_query.go
Purpose: Retrieves single item by ID with authorization
Key Methods:
NewGetItemQuery(itemRepo IItemRepository, logger ILogger) *GetItemQuery- Constructor with dependency injectionExecute(ctx context.Context, itemID string, userID string) (*ItemEntity, error)- Validates ownership and returns item
Flow:
ItemsController.GetItem()callsGetItemQuery.Execute()- Calls
ItemRepository.FindByID()to retrieve item - Validates ownership by comparing user IDs
- Returns item entity
File: internal/application/queries/list_items_query.go
Purpose: Retrieves all items for authenticated user
Key Methods:
NewListItemsQuery(itemRepo IItemRepository, logger ILogger) *ListItemsQuery- Constructor with dependency injectionExecute(ctx context.Context, userID string) ([]*ItemEntity, error)- Returns user's items
Flow:
ItemsController.ListItems()callsListItemsQuery.Execute()- Calls
ItemRepository.FindByUserID()to retrieve user's items - Returns array of item entities
6. DTOs
File: internal/application/dto/create_item_dto.go
Purpose: Request validation for item creation
Key Properties:
Name string- Item name (validation: not empty, max 255 chars)ExpirationDate time.Time- Date (validation: valid date)OrderURL string- Order URL (validation: valid URL format)
Key Methods:
Validate() error- Validates DTO fields
Place in the flow:
- Used by
ItemsController.CreateItem()for request body validation
File: internal/application/dto/item_response_dto.go
Purpose: Standardized item response format
Key Properties:
ID string- Item IDName string- Item nameExpirationDate time.Time- Expiration dateOrderURL string- Order URLUserID string- Owner user IDCreatedAt time.Time- Creation timestamp
Key Methods:
FromEntity(item *ItemEntity) *ItemResponseDTO- Creates DTO from entity
Place in the flow:
- Used by all item controller methods for response transformation
File: internal/application/dto/login_dto.go
Purpose: Login request validation
Key Properties:
Username string- Username (validation: not empty)Password string- Password (validation: not empty)
Key Methods:
Validate() error- Validates DTO fields
7. Interfaces
File: internal/application/interfaces/item_repository.go
Purpose: Repository interface for item persistence
Key Methods:
Save(ctx context.Context, item *ItemEntity) errorFindByID(ctx context.Context, id ItemID) (*ItemEntity, error)FindByUserID(ctx context.Context, userID UserID) ([]*ItemEntity, error)FindWhere(ctx context.Context, spec Specification[ItemEntity]) ([]*ItemEntity, error)Delete(ctx context.Context, id ItemID) errorExists(ctx context.Context, id ItemID) (bool, error)
File: internal/application/interfaces/user_repository.go
Purpose: Repository interface for user persistence
Key Methods:
FindByUsername(ctx context.Context, username string) (*UserEntity, error)FindByID(ctx context.Context, id UserID) (*UserEntity, error)Save(ctx context.Context, user *UserEntity) error
File: internal/application/interfaces/auth_service.go
Purpose: Authentication service interface
Key Methods:
Authenticate(ctx context.Context, username string, password string) (string, error)ValidateToken(ctx context.Context, token string) (bool, error)GetUserIDFromToken(ctx context.Context, token string) (string, error)
File: internal/application/interfaces/order_service.go
Purpose: Order service interface
Key Methods:
OrderItem(ctx context.Context, item *ItemEntity) error
File: internal/application/interfaces/time_provider.go
Purpose: Time provider interface for testing
Key Methods:
Now() time.Time
File: internal/application/interfaces/logger.go
Purpose: Logger interface
Key Methods:
Info(ctx context.Context, msg string, fields ...interface{})Error(ctx context.Context, msg string, fields ...interface{})Debug(ctx context.Context, msg string, fields ...interface{})Warn(ctx context.Context, msg string, fields ...interface{})
Infrastructure Layer
8. Repositories
File: internal/infrastructure/repositories/file_item_repository.go
Purpose: File-based implementation of item repository using JSON files
Key Methods:
Save(ctx context.Context, item *ItemEntity) error- Persists item entityFindByID(ctx context.Context, id ItemID) (*ItemEntity, error)- Finds by IDFindByUserID(ctx context.Context, userID UserID) ([]*ItemEntity, error)- Finds by userFindWhere(ctx context.Context, spec Specification[ItemEntity]) ([]*ItemEntity, error)- Finds by specification usingItemExpirationSpecDelete(ctx context.Context, id ItemID) error- Deletes itemExists(ctx context.Context, id ItemID) (bool, error)- Checks existence
Place in the flow:
- Called by all commands and queries for data persistence and retrieval
- Uses
ItemExpirationSpecfor finding expired items
File: internal/infrastructure/repositories/file_user_repository.go
Purpose: File-based implementation of user repository using JSON files
Key Methods:
FindByUsername(ctx context.Context, username string) (*UserEntity, error)- Finds by usernameFindByID(ctx context.Context, id UserID) (*UserEntity, error)- Finds by IDSave(ctx context.Context, user *UserEntity) error- Saves user
9. HTTP Services
File: internal/infrastructure/http/order_http_service.go
Purpose: HTTP implementation of order service
Key Methods:
NewOrderHTTPService(client *http.Client, logger ILogger) *OrderHTTPService- Constructor with dependency injectionOrderItem(ctx context.Context, item *ItemEntity) error- Sends POST request to order URL
Place in the flow:
- Called by
AddItemCommand.Execute()for expired items - Called by
HandleExpiredItemsCommand.Execute()for batch processing
10. Authentication
File: internal/infrastructure/auth/jwt_auth_service.go
Purpose: JWT implementation of authentication service
Key Methods:
NewJWTAuthService(userRepo IUserRepository, secretKey string, logger ILogger) *JWTAuthService- Constructor with dependency injectionAuthenticate(ctx context.Context, username string, password string) (string, error)- Validates credentials and generates JWTValidateToken(ctx context.Context, token string) (bool, error)- Validates JWT tokenGetUserIDFromToken(ctx context.Context, token string) (string, error)- Extracts user ID from token
Place in the flow:
- Called by
LoginUserCommand.Execute()for user authentication - Used by
JWTMiddlewarefor route protection
11. Time Provider
File: internal/infrastructure/time/system_time_provider.go
Purpose: System time implementation
Key Methods:
NewSystemTimeProvider() *SystemTimeProvider- ConstructorNow() time.Time- Returns current system time
12. Logger
File: internal/infrastructure/logging/standard_logger.go
Purpose: Standard library logger implementation
Key Methods:
NewStandardLogger(writer io.Writer) *StandardLogger- ConstructorInfo(ctx context.Context, msg string, fields ...interface{})- Logs info messageError(ctx context.Context, msg string, fields ...interface{})- Logs error messageDebug(ctx context.Context, msg string, fields ...interface{})- Logs debug messageWarn(ctx context.Context, msg string, fields ...interface{})- Logs warning message
Presentation Layer
13. Controllers
File: internal/presentation/controllers/items_controller.go
Purpose: REST API endpoints for item management
Key Methods:
NewItemsController(addItemCmd *AddItemCommand, getItemQry *GetItemQuery, listItemsQry *ListItemsQuery, deleteItemCmd *DeleteItemCommand, logger ILogger) *ItemsController- Constructor with dependency injectionCreateItem(c *gin.Context)- POST /itemsGetItem(c *gin.Context)- GET /items/:idListItems(c *gin.Context)- GET /itemsDeleteItem(c *gin.Context)- DELETE /items/:id
Flow:
- Receives HTTP requests and validates input
- Calls appropriate commands/queries based on HTTP method
- Returns standardized responses with DTOs
File: internal/presentation/controllers/auth_controller.go
Purpose: Authentication endpoints
Key Methods:
NewAuthController(loginUserCmd *LoginUserCommand, logger ILogger) *AuthController- Constructor with dependency injectionLogin(c *gin.Context)- POST /login
14. Middleware
File: internal/presentation/middleware/jwt_middleware.go
Purpose: JWT authentication middleware
Key Methods:
NewJWTMiddleware(authService IAuthService, logger ILogger) *JWTMiddleware- Constructor with dependency injectionMiddleware() gin.HandlerFunc- Returns gin middleware function
Place in the flow:
- Applied to all protected routes by Gin router group
- Uses
JWTAuthServicefor token validation
15. Server
File: internal/presentation/server/server.go
Purpose: HTTP server setup and configuration
Key Methods:
NewServer(config *Config, logger ILogger) *Server- Constructor with dependency injectionStart() error- Starts the HTTP serverSetupRoutes() *gin.Engine- Sets up routes and middleware
Background Processing
File: internal/infrastructure/scheduler/expired_items_scheduler.go
Purpose: Scheduled job for processing expired items using a ticker
Key Methods:
NewExpiredItemsScheduler(handleExpiredItemsCmd *HandleExpiredItemsCommand, logger ILogger) *ExpiredItemsScheduler- Constructor with dependency injectionStart(ctx context.Context) error- Starts the schedulerStop() error- Stops the schedulerprocessExpiredItems(ctx context.Context) error- Processes expired items
Flow:
- On startup:
processExpiredItems()immediately callsHandleExpiredItemsCommand.Execute() - Every minute: Ticker triggers
processExpiredItems()to process expired items - All methods use context for cancellation and error handling
- Comprehensive logging for monitoring and debugging
Configuration:
- Uses
time.Tickerfor periodic execution - Implements graceful shutdown with context cancellation
- Configurable interval (default: 1 minute)
Dependency Injection
File: internal/container/container.go
Purpose: Dependency injection container setup
Key Methods:
NewContainer(config *Config) *Container- ConstructorInitialize() error- Initializes all dependenciesGetItemRepository() IItemRepository- Returns item repositoryGetUserRepository() IUserRepository- Returns user repositoryGetAuthService() IAuthService- Returns auth serviceGetOrderService() IOrderService- Returns order serviceGetTimeProvider() ITimeProvider- Returns time providerGetLogger() ILogger- Returns loggerGetAddItemCommand() *AddItemCommand- Returns add item commandGetHandleExpiredItemsCommand() *HandleExpiredItemsCommand- Returns handle expired items commandGetDeleteItemCommand() *DeleteItemCommand- Returns delete item commandGetLoginUserCommand() *LoginUserCommand- Returns login user commandGetGetItemQuery() *GetItemQuery- Returns get item queryGetListItemsQuery() *ListItemsQuery- Returns list items queryGetItemsController() *ItemsController- Returns items controllerGetAuthController() *AuthController- Returns auth controllerGetJWTMiddleware() *JWTMiddleware- Returns JWT middlewareGetExpiredItemsScheduler() *ExpiredItemsScheduler- Returns expired items scheduler
Configuration
File: internal/config/config.go
Purpose: Application configuration
Key Properties:
ServerPort int- Server portJWTSecret string- JWT secret keyDataDirectory string- Directory for data filesLogLevel string- Log levelSchedulerInterval time.Duration- Scheduler interval
Key Methods:
Load() (*Config, error)- Loads configuration from environment variablesValidate() error- Validates configuration
Main Application
File: cmd/main.go
Purpose: Application entry point
Key Methods:
main()- Main functionsetupGracefulShutdown(server *Server, scheduler *ExpiredItemsScheduler)- Sets up graceful shutdown
Flow:
- Loads configuration
- Initializes dependency container
- Creates server and scheduler
- Starts scheduler
- Starts server
- Sets up graceful shutdown
Complete Flow Summary
Item Creation Flow
POST /items
├── JWTMiddleware (authentication)
├── CreateItemDTO validation
├── ItemsController.CreateItem()
│ ├── AddItemCommand.Execute()
│ │ ├── ItemEntity constructor (validation)
│ │ ├── ItemExpirationSpec.IsExpired() ← SINGLE SOURCE OF TRUTH
│ │ ├── If expired: OrderHTTPService.OrderItem()
│ │ └── If not expired: ItemRepository.Save()
│ └── ItemResponseDTO transformation
└── HTTP response
Expired Items Processing Flow
Scheduler (every minute)
└── ExpiredItemsScheduler.processExpiredItems()
└── HandleExpiredItemsCommand.Execute()
├── ITimeProvider.Now()
├── ItemExpirationSpec.GetSpec() ← SINGLE SOURCE OF TRUTH
├── ItemRepository.FindWhere() (using spec)
├── For each expired item:
│ ├── OrderHTTPService.OrderItem()
│ └── ItemRepository.Delete()
└── Logging
Item Retrieval Flow
GET /items/:id
├── JWTMiddleware (authentication)
├── ItemsController.GetItem()
│ ├── GetItemQuery.Execute()
│ │ ├── ItemRepository.FindByID()
│ │ ├── Ownership validation
│ │ └── Return ItemEntity
│ └── ItemResponseDTO transformation
└── HTTP response
Key Design Principles
- Single Source of Truth:
ItemExpirationSpecis the only component that determines expiration logic - Clear Flow: Each component has a well-defined place in the execution chain
- Dependency Inversion: High-level modules don't depend on low-level modules
- Separation of Concerns: Each layer has distinct responsibilities
- Testability: All components can be tested in isolation
- Context Propagation: All methods accept context for cancellation, deadlines, and tracing
- Error Handling: Comprehensive error handling with proper error types
- Configuration Management: Environment-based configuration with validation
Go-Specific Best Practices
- Package Organization: Follow Go's standard package layout with
internal/for private packages - Error Handling: Use Go's error handling pattern with proper error wrapping
- Context Usage: Use context for cancellation, deadlines, and request-scoped values
- Interface Segregation: Keep interfaces small and focused
- Naming Conventions: Follow Go's naming conventions (camelCase for exported, camelCase for unexported)
- Pointer vs Value: Use pointers for mutable state and large structs, values for immutable data
- Concurrency: Use goroutines and channels for concurrent operations
- Testing: Write comprehensive tests with table-driven tests
- Documentation: Use Go's documentation comments for all exported types and functions
- Dependencies: Use Go modules for dependency management
Directory Structure
golang/
├── cmd/
│ └── main.go # Application entry point
├── internal/
│ ├── application/
│ │ ├── commands/ # Command handlers
│ │ ├── queries/ # Query handlers
│ │ ├── dto/ # Data transfer objects
│ │ └── interfaces/ # Application interfaces
│ ├── domain/
│ │ ├── entities/ # Domain entities
│ │ ├── value_objects/ # Value objects
│ │ └── specifications/ # Domain specifications
│ ├── infrastructure/
│ │ ├── repositories/ # Repository implementations
│ │ ├── http/ # HTTP services
│ │ ├── auth/ # Authentication services
│ │ ├── time/ # Time provider
│ │ ├── logging/ # Logger implementations
│ │ └── scheduler/ # Background scheduler
│ ├── presentation/
│ │ ├── controllers/ # HTTP controllers
│ │ ├── middleware/ # HTTP middleware
│ │ └── server/ # HTTP server
│ ├── config/
│ │ └── config.go # Configuration
│ └── container/
│ └── container.go # Dependency injection container
├── pkg/
│ └── specifications/ # Reusable specification patterns
├── data/ # Data files (JSON)
├── docker/
│ ├── docker-compose.yml # Docker compose
│ └── Dockerfile # Docker image
├── go.mod # Go module file
├── go.sum # Go module checksums
└── README.md # Go implementation README
This implementation plan ensures consistent development regardless of the implementer, providing clear flow definitions and emphasizing ItemExpirationSpec as the centralized source for expiration logic, while following Go best practices and idioms.