Freedom Framework

repository·master·Indexed 25 days ago

https://github.com/8treenet/freedom

A Go framework focused on Domain-Driven Design (DDD) and Hexagonal Architecture. It provides a complete toolchain including a CLI for code generation, built-in support for enterprise patterns like CQS and Aggregate Roots, and multi-level caching. The framework features a request-isolated environment via a Worker interface, dependency injection through the Initiator interface, and a structured layer system comprising domain, adapter, config, and infra.

Tokens
31K
Snippets
78
Records
128
Agent score
80%

What's inside freedom

  1. Overview of Freedom DDD Framework

    master

    Freedom is a Go-based framework designed for Domain-Driven Design (DDD) using Hexagonal Architecture (Ports and Adapters). It provides tools for building maintainable enterprise applications with support for Dependency Injection (DI), Dependency Inversion (DIP), and plugin-based design.

    Core Capabilities:

    • Architecture: Hexagonal architecture, DDD best practices, and AOP (Aspect-Oriented Programming).
    • Integration: Seamlessly integrates with the Iris Web framework, supports HTTP/H2C, and includes Prometheus monitoring.
    • Domain Modeling: Supports Aggregate Roots, Domain Events, CQS (Command Query Separation), Entities, and Value Objects.
    • Data Handling: Automated CRUD generation, PO code generation, and a multi-level cache architecture (L1 Memory, L2 Distributed, and Cache Breakdown protection).
    • Messaging: Integrated message queue components and event-driven architecture support.
  2. FShop DDD Architecture Overview

    master

    FShop is a complete e-commerce system example demonstrating how to build enterprise-level applications using the Freedom Framework and Domain-Driven Design (DDD) principles. It covers core scenarios like products, shopping carts, orders, and shipping.

    Project Structure

    • adapter/ (Adapter Layer): Handles external interfaces such as HTTP controllers, repository implementations, and event consumers.
    • domain/ (Domain Layer): The core business logic layer. It contains aggregates (using CQS pattern), entities, persistence objects (PO), value objects (VO), domain events, and dependency interfaces. It does not depend on external implementations.
    • infra/ (Infrastructure Layer): Provides common capabilities like event handling, caching, and transaction management.
  3. What is the Worker runtime and how does it work?

    master

    The Worker runtime is a core mechanism in the Freedom framework that provides an independent runtime environment for every request. It enables request-level dependency injection and lifecycle management, allowing developers to share request context across the entire call chain without manually passing context.Context objects.

    Key features include:

    • Request Isolation: Each request has its own independent Worker instance.
    • Automatic Injection: The framework automatically injects the Worker into components.
    • Lifecycle Management: The Worker's lifecycle is tied to the request.
    • Non-intrusive Propagation: Avoids manual context passing.
    • Request-level Storage: Provides a dedicated storage space for the duration of the request.
  4. Understand the Freedom Framework architecture

    master

    Freedom is a Go framework based on Domain-Driven Design (DDD) principles. It uses a hexagonal architecture (ports and adapters) to maintain clear separation of concerns. The project structure is organized into several layers:

    • domain/: The core business logic layer containing aggregate, entity, event, vo (Value Objects), po (Persistence Objects), and domain services.
    • adapter/: The ports and adapters layer. controller acts as the input adapter (handling external requests), and repository acts as the output adapter (persisting domain objects).
    • config/: Application configuration management.
    • infra/: Infrastructure components providing technical support.

    This architecture ensures that business logic is decoupled from technical implementation details like databases or web frameworks.

  5. Inject dependencies into a Service

    master

    Freedom uses dependency injection to manage Service dependencies. For injection to work, dependency fields in your Service struct must be exported (start with an uppercase letter).

    Supported injectable types include:

    • freedom.Worker: The request runtime (one instance per request).
    • Repository interfaces: For data access (e.g., dependency.CartRepo).
    • Factory pointers: For creating aggregates (e.g., *aggregate.CartFactory).
    • Infrastructure components: Such as *domainevent.EventTransaction for transaction management.
  6. Implement Dependency Injection with Interfaces

    master

    The framework encourages an interface-based dependency injection pattern. Define your business logic in interfaces, implement them in repositories or services, and inject the interface into your service structs.

    // 1. 定义接口
    type GoodsInterface interface {
        GetGoods(goodsID int) vo.GoodsModel
    }
    
    // 2. 注入服务
    type ShopService struct {
        Worker freedom.Worker
        Goods  repository.GoodsInterface  // 注入接口
    }
    
    // 3. 实现接口
    type GoodsRepository struct {
        freedom.Repository
    }
    
    func (repo *GoodsRepository) GetGoods(goodsID int) vo.GoodsModel {
        // 实现细节
    }
  7. How Persistent Objects (PO) manage changes

    master

    Generated PO structs include a private changes map used for change tracking. Instead of updating all fields in a database row, the PO tracks which fields have been modified via Set or Update methods.

    When GetChanges() is called, it returns the map of modified fields and clears the internal tracker. This allows the repository to perform efficient updates using only the changed columns.

    // Example of the internal change tracking structure
    type User struct {
        ID        int       `gorm:"primaryKey;column:id"` 
        Name      string    `gorm:"column:name"` 
        // ... other fields
        changes   map[string]interface{}
    }
    
    func (obj *User) Update(name string, value interface{}) {
        if obj.changes == nil {
            obj.changes = make(map[string]interface{})
        }
        obj.changes[name] = value
    }
  8. How EventManager ensures data consistency

    master

    The EventManager component implements the Transactional Outbox pattern to solve the problem of consistency between local database transactions and external message queues (Kafka).

    Workflow

    1. Transaction Phase: When EventManager.Save is called within a repository transaction, the event is marshaled and saved into a local pubEventObject table (the message table) using the same database connection. This ensures that if the business transaction rolls back, the event is also rolled back.
    2. Dispatch Phase: After the transaction successfully commits, the EventManager asynchronously pushes the message to Kafka.
    3. Cleanup: Once Kafka acknowledges receipt, the record is deleted from the local message table.
    4. Retry Mechanism: If the Kafka push fails, the record remains in the local table. A background retry process periodically scans this table and attempts to re-publish failed messages based on a configured retry policy.

    Consistency Guarantees

    • Atomicity: Business data changes and event records are committed in a single local transaction.
    • Reliability: Messages are only deleted from the local table after successful Kafka delivery.
    • Eventual Consistency: The retry mechanism ensures that even if Kafka is temporarily unavailable, the message will eventually be delivered.
  9. Understand the Application and Request lifecycles

    master

    Freedom manages two distinct lifecycles:

    Application Lifecycle

    Used for global setup and teardown:

    1. Global Middleware Registration: Application.InstallMiddleware
    2. Database Installation: Application.InstallDB
    3. Component Initialization: infra.Booting (singleton components)
    4. Pre-startup Callbacks: Initiator.BindBooting
    5. Local Initialization: freedom.Prepare (local components)
    6. Service Startup: http.Run
    7. Shutdown Callbacks: infra.RegisterShutdown
    8. Application Shutdown: Application.Close

    Request Lifecycle

    For every request, Freedom creates an isolated set of objects to ensure thread safety and high performance via object pooling:

    • Worker: Manages request context.
    • Controller: Handles the request logic.
    • Service: Contains business logic.
    • Factory: Creates objects.
    • Repository: Handles data access.
    • Infra: Provides infrastructure support.
  10. Core DDD Concepts in Freedom

    master

    The following table summarizes the core Domain-Driven Design (DDD) abstractions used in the framework:

    ConceptDescriptionExample
    EntityAn object with a unique identity that encapsulates business behavior.User, Goods, Cart
    AggregateA cluster of related entities managed as a single unit by an Aggregate Root.CartAddCmd, CartItemQuery
    FactoryEncapsulates the logic for creating Aggregate Roots.CartFactory, ShopFactory
    ServiceCoordinates domain objects to implement complex business processes.CartService, OrderService
    RepositoryAn abstraction for data access, following the Dependency Inversion Principle.CartRepo, GoodsRepo
    Domain EventA notification representing a significant change in state.ShopGoods, OrderPay
  11. Use the Freedom Middleware system

    master

    Freedom uses a middleware system to provide request processing capabilities like logging, monitoring, and retries. Middleware is installed globally using requests.InstallMiddleware(...).

    Middleware Interface

    To implement a custom middleware, satisfy the Middleware interface:

    • Next(): Continue to the next middleware/request.
    • Stop(err ...error): Halt execution and return errors.
    • GetRequest(): Access the current *http.Request.
    • GetRespone(): Access the *Response object.
    • Context(): Access the request context.Context.
    type Middleware interface {
        Next()                      
        Stop(err ...error)         
        GetRequest() *http.Request 
        GetRespone() *Response     
        Context() context.Context  
    }
    
    // Global installation
    requests.InstallMiddleware(
        NewLogMiddleware(),
        NewTraceMiddleware(),
        NewRetryMiddleware(3),
    )
  12. How Aggregates and CQS work in Freedom DDD

    master

    Aggregates are designed using the CQS (Command Query Separation) principle. An Aggregate Root composes entities rather than inheriting from freedom.Entity.

    Command (Write Operations)

    • Purpose: Change the system state.
    • Return Value: Typically returns an error.
    • Execution: Uses a Run() method.
    • Side Effects: Has side effects (e.g., updating a database).

    Query (Read Operations)

    • Purpose: Read data.
    • Return Value: Returns data.
    • Execution: Uses custom methods (e.g., VisitAllItem()).
    • Side Effects: No side effects.

    Key Rules

    • Aggregate roots should inherit from an Entity, not freedom.Entity.
    • Commands and Queries must be strictly separated.
    • Commands must ensure atomicity.
    • Avoid direct references across different aggregates.
    • Define clear aggregate boundaries.