Documentation
¶
Overview ¶
Package example provides a demonstration of a well-structured Go module using the Uber FX framework for dependency injection. This module showcases common patterns and best practices for organizing Go code in a modular, maintainable way.
The example module is organized into several files, each with a specific responsibility:
- config.go: Contains configuration structures for the module
- domain.go: Defines domain entities and core business logic types
- errors.go: Custom error definitions for the module
- metrics.go: Prometheus metrics collection and reporting
- models.go: Data models used by the module
- module.go: FX module definition for dependency injection
- repository.go: Data access layer implementation
- service.go: Business logic and service layer implementation
This structure follows a clean architecture approach with clear separation of concerns.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrExample is a predefined error for the example module. // // This demonstrates how to create module-specific errors that can be // used throughout the codebase. In a real application, you might have // multiple error types for different error conditions. // // Usage: // return fmt.Errorf("failed to process: %w", example.ErrExample) // // Checking: // if errors.Is(err, example.ErrExample) { // // Handle example error // } ErrExample = errors.New("example error") )
This file defines module-specific error types and values.
Having dedicated error types for a module provides several benefits:
- Enables error handling specific to this module's domain
- Allows for programmatic error type checking
- Improves error message consistency
- Makes debugging and troubleshooting easier
Example:
if err := processExample(); err != nil {
if errors.Is(err, example.ErrExample) {
// Handle specific example error
}
// Handle other errors
}
Functions ¶
func Module ¶
Module creates and returns an FX module for the example package.
This function defines how the example module should be wired into an application using the Uber FX dependency injection framework. It specifies all the components that make up the module and how they depend on each other.
The module includes:
- A named logger for structured logging
- A repository for data access (provided privately)
- A service for business logic (provided publicly)
FX will automatically resolve dependencies and inject them where needed. For example, the Service depends on Config, Repository, Metrics, and Logger, so FX will ensure these are available when creating the Service.
Usage:
app := fx.New(
example.Module(),
// other modules...
)
// The Service can then be injected into other components:
fx.Invoke(func(service *example.Service) {
// Use the service
})
Types ¶
type Config ¶
type Config struct {
// Example is a configuration parameter that demonstrates how to include
// custom configuration values in the module. This could be used for
// feature flags, API endpoints, timeouts, or any other configurable
// aspect of the module.
Example string
}
Config holds the configuration for the example module.
This struct contains all the configuration parameters needed to initialize and configure the example module. Typically, these values would be loaded from environment variables, configuration files, or other configuration sources.
Example:
cfg := example.Config{
Example: "demo-value",
}
service := example.New(cfg, repo, metrics, logger)
type Example ¶
type Example struct {
// In a real application, this would contain fields that represent
// the state and properties of the domain entity.
Value string
}
Example represents a domain entity in the example module.
This struct demonstrates the domain-driven design approach where core business concepts are modeled as domain entities. In a real application, this would contain business logic, validation, and behavior related to the concept it represents.
Domain entities are typically:
- Rich in behavior, not just data
- Responsible for maintaining their own integrity
- Focused on business rules and logic
Example:
// In a real application, this might have methods like:
func (e *Example) Validate() error {
// Validation logic
}
func (e *Example) Process() error {
// Business logic
}
type Metrics ¶
type Metrics struct {
// contains filtered or unexported fields
}
Metrics handles Prometheus metrics collection for the example module.
This struct encapsulates all Prometheus metrics related to the example module. Having a dedicated metrics struct provides a clean way to organize and manage metrics, making it easier to add new metrics and maintain existing ones.
Metrics are important for:
- Monitoring application health and performance
- Tracking business metrics and KPIs
- Setting up alerts and dashboards
- Debugging and troubleshooting issues
Example:
metrics := example.NewMetrics() metrics.IncTotal() // Increment the counter
func NewMetrics ¶
func NewMetrics() *Metrics
NewMetrics creates and initializes a new Metrics instance.
This function serves as a constructor for the Metrics struct, initializing all the Prometheus metrics with their appropriate configuration.
The metrics defined here are:
- example_total: A counter that tracks the total number of examples
Returns:
- *Metrics: A pointer to the newly created Metrics instance
Example:
metrics := example.NewMetrics() // Use metrics in your service service := example.New(config, repo, metrics, logger)
func (*Metrics) IncTotal ¶
func (m *Metrics) IncTotal()
IncTotal increments the total example counter.
This method should be called whenever an example is processed, created, or any other event occurs that should be tracked by the total counter.
Example:
func (s *Service) ProcessExample() error {
// Process the example
s.metrics.IncTotal() // Record the metric
return nil
}
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
Repository handles data access operations for the example module.
This struct represents the repository layer in a clean architecture pattern. The repository is responsible for all data access operations, abstracting the details of data storage and retrieval from the rest of the application.
Benefits of using a repository pattern:
- Separates data access logic from business logic
- Makes it easier to switch data sources (e.g., from SQL to NoSQL)
- Centralizes data access operations
- Improves testability by allowing mock repositories
In a real application, this struct would contain methods for:
- Creating, reading, updating, and deleting (CRUD) data
- Querying data with various filters
- Handling transactions
- Mapping between domain entities and data models
func NewRepository ¶
func NewRepository() *Repository
NewRepository creates and initializes a new Repository instance.
This function serves as a constructor for the Repository struct. In a real application, it would typically accept dependencies like database connections, clients, or configuration needed to initialize the repository.
Returns:
- *Repository: A pointer to the newly created Repository instance
Example:
repo := example.NewRepository() // Use the repository in your service service := example.New(config, repo, metrics, logger)
func (*Repository) Add ¶
func (r *Repository) Add(item Example)
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements the business logic for the example module.
This struct represents the service layer in a clean architecture pattern. The service is responsible for implementing business rules, orchestrating operations between different components, and exposing functionality to the rest of the application.
The Service depends on:
- Config: For configuration parameters
- Repository: For data access operations
- Metrics: For collecting and reporting metrics
- Logger: For structured logging
In a real application, this struct would contain methods for:
- Business operations and workflows
- Coordinating between repositories and other services
- Enforcing business rules and validation
- Handling errors and logging
func New ¶
New creates and initializes a new Service instance.
This function serves as a constructor for the Service struct, accepting all its dependencies as parameters. This approach, known as dependency injection, makes the code more testable and maintainable.
Parameters:
- config: Configuration for the service
- examples: Repository for data access
- metrics: Metrics collector for monitoring
- logger: Logger for structured logging
Returns:
- *Service: A pointer to the newly created Service instance
Example:
config := example.Config{Example: "demo"}
repo := example.NewRepository()
metrics := example.NewMetrics()
logger, _ := zap.NewProduction()
service := example.New(config, repo, metrics, logger)