// Example represents a basic example entity type Example struct { Id int`json:"id" gorm:"column:id;primaryKey;autoIncrement" structs:",omitempty,underline"` Name string`json:"name" gorm:"column:name;type:varchar(255);not null" structs:",omitempty,underline"` Alias string`json:"alias" gorm:"column:alias;type:varchar(255)" structs:",omitempty,underline"` CreatedAt time.Time `json:"created_at" gorm:"column:created_at;autoCreateTime" structs:",omitempty,underline"` UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at;autoUpdateTime" structs:",omitempty,underline"` }
// TableName returns the table name for the Example model func(e Example) TableName() string { return"example" }
// domain/service/iexample_service.go package service
import ( "context"
"go-hexagonal/domain/model" )
// IExampleService defines the interface for example service // This allows the application layer to depend on interfaces rather than concrete implementations, // facilitating testing and adhering to the dependency inversion principle type IExampleService interface { // Create creates a new example Create(ctx context.Context, example *model.Example) (*model.Example, error)
// Delete deletes an example by ID Delete(ctx context.Context, id int) error
// Update updates an example Update(ctx context.Context, example *model.Example) error
// Get retrieves an example by ID Get(ctx context.Context, id int) (*model.Example, error)
// FindByName finds examples by name FindByName(ctx context.Context, name string) (*model.Example, error) }
// ExampleService handles business logic for Example entity type ExampleService struct { Repository repo.IExampleRepo EventBus event.EventBus }
// NewExampleService creates a new example service instance funcNewExampleService(repository repo.IExampleRepo) *ExampleService { return &ExampleService{ Repository: repository, } }
// Create creates a new example func(s *ExampleService) Create(ctx context.Context, example *model.Example) (*model.Example, error) { // Create a no-operation transaction tr := repo.NewNoopTransaction(s.Repository)
createdExample, err := s.Repository.Create(ctx, tr, example) if err != nil { log.SugaredLogger.Errorf("Failed to create example: %v", err) returnnil, fmt.Errorf("failed to create example: %w", err) }
// Publish event if event bus is available if s.EventBus != nil { evt := event.NewExampleCreatedEvent(createdExample.Id, createdExample.Name, createdExample.Alias) if err := s.EventBus.Publish(ctx, evt); err != nil { log.SugaredLogger.Warnf("Failed to publish event: %v", err) return createdExample, fmt.Errorf("failed to publish example created event: %w", err) } }
// EventHandler defines the event handler interface type EventHandler interface { // HandleEvent processes an event HandleEvent(ctx context.Context, event Event) error // InterestedIn checks if the handler is interested in the event InterestedIn(eventName string) bool }
// EventBus defines the event bus interface type EventBus interface { // Publish publishes an event Publish(ctx context.Context, event Event) error // Subscribe registers an event handler Subscribe(handler EventHandler) // Unsubscribe removes an event handler Unsubscribe(handler EventHandler) }
// InMemoryEventBus implements an in-memory event bus type InMemoryEventBus struct { handlers []EventHandler mu sync.RWMutex }
// NewInMemoryEventBus creates a new in-memory event bus funcNewInMemoryEventBus() *InMemoryEventBus { return &InMemoryEventBus{ handlers: make([]EventHandler, 0), } }
// Publish publishes an event to all interested handlers func(b *InMemoryEventBus) Publish(ctx context.Context, event Event) error { b.mu.RLock() defer b.mu.RUnlock()
for _, handler := range b.handlers { if handler.InterestedIn(event.EventName()) { if err := handler.HandleEvent(ctx, event); err != nil { return err } } } returnnil }
// CreateUseCase handles the create example use case type CreateUseCase struct { exampleService service.IExampleService converter service.Converter txFactory repo.TransactionFactory }
// Execute processes the create example request func(uc *CreateUseCase) Execute(ctx context.Context, input any) (any, error) { // Convert input to domain model using the converter example, err := uc.converter.FromCreateRequest(input) if err != nil { returnnil, fmt.Errorf("failed to convert request: %w", err) }
// Create a real transaction for atomic operations tx, err := uc.txFactory.NewTransaction(ctx, repo.MySQLStore, nil) if err != nil { returnnil, fmt.Errorf("failed to create transaction: %w", err) } defer tx.Rollback()
// Call domain service createdExample, err := uc.exampleService.Create(ctx, example) if err != nil { returnnil, fmt.Errorf("failed to create example: %w", err) }
// Commit transaction if err = tx.Commit(); err != nil { returnnil, fmt.Errorf("failed to commit transaction: %w", err) }
// Convert domain model to response using the converter result, err := uc.converter.ToExampleResponse(createdExample) if err != nil { returnnil, fmt.Errorf("failed to convert response: %w", err) }
// TryGetSet tries to get a value from cache, if not found executes the loader and sets the result func(c *EnhancedCache) TryGetSet(ctx context.Context, key string, dest interface{}, ttl time.Duration, loader func() (interface{}, error)) error { // Try to get from cache first err := c.Get(ctx, key, dest) if err == nil { // Cache hit, return success returnnil }
// If it's not a NotFound error, return the error if !apperrors.IsNotFoundError(err) { return err }
// Execute within a lock to prevent cache stampede lockKey := "lock:" + key return c.WithLock(ctx, lockKey, func()error { // Try to get again (might have been set by another process while waiting for lock) err := c.Get(ctx, key, dest) if err == nil { // Cache hit, return success returnnil }
// If it's not a NotFound error, return the error if !apperrors.IsNotFoundError(err) { return err }
// Execute the loader result, err := loader() if err != nil { // If loader failed, set negative cache if enabled if c.options.EnableNegativeCache { _ = c.SetNegative(ctx, key) } return err }
// If nil result, set negative cache if result == nil { if c.options.EnableNegativeCache { _ = c.SetNegative(ctx, key) } return apperrors.New(apperrors.ErrorTypeNotFound, "loader returned nil result") }
// Set the value in cache if err := c.Set(ctx, key, result, ttl); err != nil { return err }
// Convert the result back to the destination resultBytes, err := json.Marshal(result) if err != nil { return apperrors.Wrapf(err, apperrors.ErrorTypeSystem, "failed to marshal loader result") }
// StandardResponse defines the standard API response structure type StandardResponse struct { Code int`json:"code"`// Status code Message string`json:"message"`// Response message Data interface{} `json:"data,omitempty"`// Response data DocRef string`json:"doc_ref,omitempty"`// Documentation reference }
// Error represents a standardized API error type Error struct { Code int`json:"code"`// Error code Msg string`json:"message"`// Error message Details []string`json:"details,omitempty"`// Optional error details HTTP int`json:"-"`// HTTP status code (not exposed in JSON) DocRef string`json:"doc_ref,omitempty"`// Reference to documentation }
// NewError creates a new Error instance with the specified code and message funcNewError(code int, msg string) *Error { if _, ok := codes[code]; ok { panic(fmt.Sprintf("error code %d already exists, please replace one", code)) }