Skip to content

Framingo is an open-source project framework that includes various small modules and components, making it easier for users to start their own projects.

License

Notifications You must be signed in to change notification settings

xhanio/framingo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

53 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Framingo

License Go Version

Framingo is a modular, service-oriented Go framework for building production-ready HTTP API applications. It provides a comprehensive set of tools, utilities, and architectural patterns to help you quickly develop scalable, maintainable backend services.

Features

  • Service-Oriented Architecture - Built-in service lifecycle management with automatic dependency resolution

  • HTTP API Server - Production-ready API server with routing, middleware, and rate limiting

  • Rich Utilities - Extensive collection of utility packages for common operations

  • Modular Design - Pick and choose components based on your needs

  • Configuration Management - YAML-based configuration with environment variable overrides

  • Database Integration - Database manager with connection pooling and migrations

  • CLI Support - Built-in command-line interface framework integration

  • Production Ready - Error handling, logging, graceful shutdown, and profiling support

Table of Contents

Quick Start

Installation

go get github.com/xhanio/framingo

Your First Application

Create a simple HTTP API server in minutes:

// cmd/myapp/main.go
package main

import (
    "fmt"
    "os"

    "github.com/xhanio/framingo/example/pkg/components/cmd/example"
)

func main() {
    rootCmd := example.NewRootCmd()
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}
# config.yaml
log:
  level: 0  # 0=Debug, 1=Info, 2=Warn, 3=Error

api:
  http:
    host: 0.0.0.0
    port: 8080
    prefix: /api/v1
# Build and run
go build -o myapp cmd/myapp/main.go
./myapp daemon -c config.yaml

# Test the API
curl -X GET "http://localhost:8080/api/v1/demo/example?message=Hello"
# Response: {"id":1,"message":"Hello","created_at":"...","updated_at":"..."}

For a complete tutorial, see the Building Your First Application section and Complete Application Guide.

Architecture

Framingo follows a layered architecture pattern:

graph TB
    subgraph "Application Layer"
        CLI[CLI Interface]
        Config[Configuration]
    end

    subgraph "Service Orchestration"
        Controller[Service Controller<br/>Lifecycle & Dependencies]
    end

    subgraph "Core Components"
        Services[Services<br/>Business Logic]
        API[API Layer<br/>HTTP Server]
        Utils[Utilities<br/>Helper Functions]
    end

    CLI --> Controller
    Config --> Controller
    Controller --> Services
    Controller --> API
    Controller --> Utils

    style CLI fill:#e1f5ff
    style Config fill:#e1f5ff
    style Controller fill:#fff4e1
    style Services fill:#f0f0f0
    style API fill:#f0f0f0
    style Utils fill:#f0f0f0
Loading

Request Flow

sequenceDiagram
    participant Client
    participant APIServer as API Server
    participant Middleware as Middleware Pipeline
    participant Router as Router/Handler
    participant Service as Service Layer
    participant DB as Database/External Services

    Client->>APIServer: HTTP Request
    APIServer->>Middleware: Process Request
    Note over Middleware: Authentication<br/>Logging<br/>Rate Limiting
    Middleware->>Router: Validated Request
    Router->>Service: Business Operation
    Service->>DB: Data Access
    DB-->>Service: Data
    Service-->>Router: Result
    Router-->>Middleware: Response
    Middleware-->>APIServer: Formatted Response
    APIServer-->>Client: HTTP Response
Loading

Core Modules

Framingo is organized into four core module categories under pkg/:

Services (pkg/services/)

Production-ready service implementations that provide core functionality:

  • api/server - HTTP API server with Echo framework integration

    • Route management and handler registration
    • Built-in middleware (error handling, logging, rate limiting)
    • Multiple server support with TLS
    • YAML-based route configuration
    • Request/response lifecycle management
  • api/client - HTTP client utilities for making API requests

  • controller - Service lifecycle orchestration

    • Automatic dependency resolution via topological sort
    • Service registration and initialization
    • Graceful startup and shutdown
    • Signal handling
  • db - Database manager with GORM integration

    • Connection pooling with configurable limits
    • Migration support
    • Multiple database types (PostgreSQL, MySQL, SQLite)
    • Context-aware operations
  • planner - Task scheduling and planning service

  • pubsub - Publish-subscribe messaging pattern implementation

Types (pkg/types/)

Core interfaces and type definitions used throughout the framework:

  • common - Common interfaces

    • Service - Base service interface with name and dependencies
    • Daemon - Start/Stop lifecycle methods
    • Initializable - Initialization interface
    • Named, Unique, Weighted - Utility interfaces
  • api - HTTP API related types

    • Router, Middleware, Handler interfaces
    • Request/Response types
    • Error categories
  • info - Application metadata (version, build info, commit hash)

Data Structures (pkg/structs/)

Efficient, generic data structures for common algorithms:

  • buffer - Ring buffer implementation with fixed capacity
  • graph - Generic graph structure with topological sort (used by controller)
  • lease - Time-based lease management for resource allocation
  • queue - FIFO queue implementation
  • staque - Hybrid stack/queue data structure
  • trie - Prefix tree for efficient string matching

Utilities (pkg/utils/)

Helper packages for common tasks:

  • certutil - Certificate and TLS utilities
  • cmdutil - Command execution helpers
  • infra - Infrastructure helpers (signals, profiling, pprof)
  • ioutil - I/O operations and file utilities
  • job - Background job execution framework
  • log - Structured logging with zap integration and file rotation
  • maputil - Map and set utilities
  • netutil - Network utilities
  • pathutil - Path manipulation helpers
  • printutil - Pretty printing utilities
  • reflectutil - Reflection helpers
  • sliceutil - Slice manipulation utilities
  • strutil - String utilities
  • task - Task management utilities
  • timeutil - Time-related utilities

Building Your First Application

Step 1: Project Setup

# Create your project structure
mkdir -p myapp/{cmd/myapp,pkg/{services,routers,middlewares,types/{api,entity,orm},components/{cmd,server},utils}}
cd myapp

# Initialize Go module
go mod init github.com/yourorg/myapp

# Get Framingo
go get github.com/xhanio/framingo

# Your project structure should look like:
# myapp/
# ├── cmd/
# │   └── myapp/           # Application entry point
# ├── pkg/
# │   ├── components/
# │   │   ├── cmd/         # CLI commands
# │   │   └── server/      # Server orchestration
# │   ├── services/        # Business logic
# │   ├── routers/         # HTTP handlers
# │   ├── middlewares/     # HTTP middleware
# │   ├── types/           # Type definitions
# │   │   ├── api/         # API request/response types with validation
# │   │   ├── entity/      # Business entities (pure domain models)
# │   │   └── orm/         # ORM models for database operations
# │   └── utils/           # Utilities
# └── config.yaml          # Configuration

Step 2: Define Your Service

Services contain your business logic with lifecycle management.

// pkg/services/hello/model.go
package hello

import (
    "context"
    "github.com/xhanio/framingo/pkg/types/common"
)

type Manager interface {
    common.Service
    common.Initializable
    common.Daemon
    SayHello(ctx context.Context, name string) (string, error)
}
// pkg/services/hello/manager.go
package hello

import (
    "context"
    "fmt"
    "github.com/xhanio/framingo/pkg/types/common"
    "github.com/xhanio/framingo/pkg/utils/log"
)

type manager struct {
    log log.Logger
}

// Option pattern for optional dependencies
type Option func(*manager)

func WithLogger(logger log.Logger) Option {
    return func(m *manager) { m.log = logger }
}

func New(opts ...Option) Manager {
    m := &manager{}
    for _, opt := range opts {
        opt(m)
    }
    if m.log == nil {
        m.log = log.Default
    }
    return m
}

func (m *manager) Name() string { return "hello-service" }
func (m *manager) Dependencies() []common.Service { return nil }
func (m *manager) Init() error {
    m.log.Info("Initializing hello service")
    return nil
}
func (m *manager) Start(ctx context.Context) error {
    m.log.Info("Starting hello service")
    return nil
}
func (m *manager) Stop(wait bool) error {
    m.log.Info("Stopping hello service")
    return nil
}

func (m *manager) SayHello(ctx context.Context, name string) (string, error) {
    m.log.Infof("Saying hello to %s", name)
    return fmt.Sprintf("Hello, %s!", name), nil
}

Step 3: Create HTTP Router

Routers define HTTP endpoints and handlers using YAML configuration.

// pkg/routers/hello/router.go
package hello

import (
    _ "embed"

    "github.com/labstack/echo/v4"
    "github.com/xhanio/framingo/pkg/types/api"
    "github.com/xhanio/framingo/pkg/types/common"
    "myapp/pkg/services/hello"
)

//go:embed router.yaml
var config []byte

type router struct {
    helloSvc hello.Manager
}

func New(svc hello.Manager) api.Router {
    return &router{helloSvc: svc}
}

func (r *router) Name() string { return "hello-router" }
func (r *router) Dependencies() []common.Service { return []common.Service{r.helloSvc} }
func (r *router) Config() []byte { return config }

func (r *router) Handlers() map[string]echo.HandlerFunc {
    return map[string]echo.HandlerFunc{
        "Hello": r.Hello,
    }
}

func (r *router) Hello(c echo.Context) error {
    name := c.QueryParam("name")
    if name == "" {
        name = "World"
    }

    msg, err := r.helloSvc.SayHello(c.Request().Context(), name)
    if err != nil {
        return err
    }

    return c.JSON(200, map[string]string{"message": msg})
}
# pkg/routers/hello/router.yaml
server: http
prefix: /hello
handlers:
  - method: GET
    path: /
    func: Hello

Step 4: Wire It All Together

Create a server component to orchestrate your services and routers.

// pkg/components/server/myapp/manager.go
package myapp

import (
    "context"
    "fmt"
    "os"

    "github.com/spf13/viper"
    "github.com/xhanio/framingo/pkg/services/api/server"
    "github.com/xhanio/framingo/pkg/services/controller"
    "github.com/xhanio/framingo/pkg/types/common"
    "github.com/xhanio/framingo/pkg/utils/infra"
    "github.com/xhanio/framingo/pkg/utils/log"

    "myapp/pkg/services/hello"
    helloRouter "myapp/pkg/routers/hello"
)

type Manager interface {
    common.Daemon
}

type manager struct {
    log      log.Logger
    services *controller.Manager
    api      server.Manager
    helloSvc hello.Manager
}

func New() Manager {
    return &manager{}
}

func (m *manager) Init() error {
    // Initialize logger
    m.log = log.New(log.WithLevel(viper.GetInt("log.level")))

    // Initialize service controller
    m.services = controller.New(controller.WithLogger(m.log))

    // Create API server
    m.api = server.New(server.WithLogger(m.log))
    httpConfig := viper.Sub("api.http")
    if httpConfig != nil {
        m.api.Add("http",
            server.WithEndpoint(
                httpConfig.GetString("host"),
                httpConfig.GetInt("port"),
                httpConfig.GetString("prefix"),
            ),
        )
    }

    // Initialize your service (with optional logger)
    m.helloSvc = hello.New(hello.WithLogger(m.log))

    // Register services
    m.services.Register(m.helloSvc)

    // Resolve dependencies
    if err := m.services.TopoSort(); err != nil {
        return err
    }

    // Add API server (must be last)
    m.services.Register(m.api)

    // Initialize all services
    if err := m.services.Init(); err != nil {
        return err
    }

    // Register routers
    return m.api.RegisterRouters(helloRouter.New(m.helloSvc))
}

func (m *manager) Start(ctx context.Context) error {
    // Handle signals
    go infra.HandleSignals(m.log, m.services)

    // Start all services
    return m.services.Start(ctx)
}

func (m *manager) Stop(wait bool) error {
    return m.services.Stop(wait)
}
// cmd/myapp/main.go
package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"

    "myapp/pkg/components/server/myapp"
)

func main() {
    var configFile string

    rootCmd := &cobra.Command{Use: "myapp"}

    daemonCmd := &cobra.Command{
        Use: "daemon",
        RunE: func(cmd *cobra.Command, args []string) error {
            // Load configuration
            viper.SetConfigFile(configFile)
            viper.SetEnvPrefix("MYAPP")
            viper.AutomaticEnv()

            if err := viper.ReadInConfig(); err != nil {
                return fmt.Errorf("failed to read config: %w", err)
            }

            // Initialize and start server
            mgr := myapp.New()
            if err := mgr.Init(); err != nil {
                return err
            }
            return mgr.Start(cmd.Context())
        },
    }
    daemonCmd.Flags().StringVarP(&configFile, "config", "c", "config.yaml", "config file path")

    rootCmd.AddCommand(daemonCmd)
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}

Step 5: Run and Test

# Build your application
go build -o myapp cmd/myapp/main.go

# Run the server
./myapp daemon -c config.yaml

# Test the endpoint
curl http://localhost:8080/api/v1/hello?name=Framingo
# Response: {"message": "Hello, Framingo!"}

For more details, see the Complete Application Guide with comprehensive examples of all components.

Documentation

Comprehensive Guides

Framework API Reference

Browse the framework source code:

  • Services API - API server, controller, database, pubsub
  • Types - Common interfaces and API types
  • Utilities - Logger, infrastructure, and helper packages
  • Data Structures - Graph, queue, buffer, trie implementations

Go Documentation

View package documentation using go doc:

# View package documentation
go doc github.com/xhanio/framingo/pkg/services/api/server
go doc github.com/xhanio/framingo/pkg/services/controller
go doc github.com/xhanio/framingo/pkg/utils/log

Examples

The example/ directory contains a complete, production-ready application demonstrating all framework features:

example/
+-- pkg/
    +-- components/
    |   +-- cmd/example/        # CLI with Cobra (daemon, version commands)
    |   +-- server/example/     # Server orchestration and lifecycle management
    +-- services/example/       # Business service with lifecycle methods
    +-- routers/example/        # HTTP routes with YAML configuration
    +-- middlewares/example/    # Custom middleware (deflate compression)
    +-- types/                  # Type definitions (separated by purpose)
    |   +-- api/                # API request/response types with validation
    |   +-- entity/             # Business entities (pure domain models)
    |   +-- orm/                # ORM models for database operations
    +-- utils/                  # Utility modules
        +-- infra/              # Infrastructure utilities
+-- README.md                   # Complete application guide

Running the Example

# Clone the repository
git clone https://github.com/xhanio/framingo.git
cd framingo

# Create a config file
cat > config.yaml <<EOF
log:
  level: 0
api:
  http:
    host: 0.0.0.0
    port: 8080
    prefix: /api/v1
EOF

# Create a main.go that uses the example components
cat > cmd/example/main.go <<EOF
package main

import (
    "fmt"
    "os"
    "github.com/xhanio/framingo/example/pkg/components/cmd/example"
)

func main() {
    rootCmd := example.NewRootCmd()
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}
EOF

# Build and run
go build -o myapp cmd/example/main.go
./myapp daemon -c config.yaml

# Test the API
curl -X GET "http://localhost:8080/api/v1/demo/example?message=Hello%20World"
# Response: {"id":1,"message":"Hello World","created_at":"...","updated_at":"..."}

For detailed setup and configuration, see example/README.md

Example Features Demonstrated

  • ✅ Service lifecycle management with dependency injection
  • ✅ HTTP API with routing and request validation
  • ✅ Type separation (API/Entity/ORM) for clean architecture
  • ✅ Database integration with ORM models
  • ✅ Custom middleware (deflate compression)
  • ✅ YAML-based configuration with environment overrides
  • ✅ CLI with multiple commands (daemon, version)
  • ✅ Graceful shutdown and signal handling
  • ✅ Structured logging with file rotation
  • ✅ Service dependencies with automatic resolution
  • ✅ Multiple API servers support

Key Concepts

Type Separation

Framingo promotes clean architecture through type separation:

// API Types - Request/response with validation
type CreateUserRequest struct {
    Username string `json:"username" validate:"required"`
    Email    string `json:"email" validate:"required,email"`
}

// Entity Types - Pure business models
type User struct {
    ID       int64  `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
}

// ORM Types - Database models
type User struct {
    ID       int64  `gorm:"primaryKey"`
    Username string `gorm:"type:varchar(100);not null"`
    Email    string `gorm:"type:varchar(255);not null"`
}

func (User) TableName() string { return "users" }

Benefits:

  • API contracts independent of storage
  • Business logic isolated from infrastructure
  • Easy to test and maintain
  • Flexibility to change database without affecting API

Service Lifecycle

All services implement a standard lifecycle:

type Service interface {
    Name() string
    Dependencies() []common.Service
}

type Initializable interface {
    Init() error
}

type Daemon interface {
    Start(context.Context) error
    Stop(wait bool) error
}

Dependency Management

Services declare their dependencies via constructor arguments (required) and options (optional):

// Required dependencies as constructor arguments
func New(database db.Manager, opts ...Option) Manager {
    m := &manager{db: database}
    for _, opt := range opts {
        opt(m)
    }
    return m
}

// Service declares dependencies for lifecycle management
func (s *myService) Dependencies() []common.Service {
    return []common.Service{s.database}
}

// Controller automatically starts: database -> myService

Router Configuration

Routers use YAML configuration for declarative route definition:

server: http
prefix: /api/users
handlers:
  - method: GET
    path: /:id
    func: GetUser
  - method: POST
    path: /
    func: CreateUser
    middleware: auth

Middleware Pipeline

Middlewares process requests in order:

Request -> Recover -> Info -> Throttle -> Logger -> Custom -> Handler -> Response

Configuration

Framingo uses Viper for configuration management with priority:

  1. Command-line flags (highest)
  2. Environment variables
  3. YAML configuration file
  4. Default values (lowest)
# config.yaml
log:
  level: 0  # 0=Debug, 1=Info, 2=Warn, 3=Error
  file: /var/log/app.log

db:
  type: postgres
  source:
    host: localhost
    port: 5432

api:
  http:
    host: 0.0.0.0
    port: 8080
    prefix: /api/v1
    throttle:
      rps: 100.0
      burst_size: 200

Environment variable override:

export FRAMINGO_API_HTTP_PORT=9090

Production Deployment

Docker

FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o app cmd/app/main.go

FROM alpine:latest
COPY --from=builder /app/app /usr/local/bin/
COPY config.yaml /etc/app/
CMD ["app", "daemon", "-c", "/etc/app/config.yaml"]

Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        ports:
        - containerPort: 8080

Systemd

[Unit]
Description=My Framingo App

[Service]
ExecStart=/usr/local/bin/myapp daemon -c /etc/myapp/config.yaml
Restart=on-failure

[Install]
WantedBy=multi-user.target

Best Practices

  1. Architecture & Types

    • Separate types by purpose: api/ for requests, entity/ for business, orm/ for database
    • Pass required dependencies as constructor arguments, not options
    • Keep business logic independent of infrastructure concerns
    • Use interfaces for testability and flexibility
  2. Service Design

    • Keep services focused and single-purpose
    • Declare dependencies explicitly in Dependencies() method
    • Implement standard lifecycle methods (Init, Start, Stop)
    • Convert between ORM and entity types in service layer
  3. Error Handling

    • Use error wrapping for context (errors.Wrap)
    • Return appropriate HTTP status codes (BadRequest, NotFound, etc.)
    • Log errors at service boundaries
    • Validate requests using API types with validation tags
  4. Configuration

    • Use YAML for structure and hierarchy
    • Use env vars for secrets and environment-specific values
    • Validate configuration on startup
    • Follow configuration priority: flags > env vars > file > defaults
  5. Testing

    • Test services independently with mocked dependencies
    • Test handlers with API type validation
    • Integration tests for full request flow
    • Test ORM to entity conversions
  6. Performance

    • Enable database connection pooling
    • Configure rate limiting per endpoint or server-wide
    • Use pprof for profiling bottlenecks
    • Cache frequently accessed data when appropriate

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Resources & Support

Documentation

Community

Acknowledgments

Built with:

  • Echo - High performance HTTP framework
  • Cobra - CLI framework
  • Viper - Configuration management
  • GORM - ORM library

Start building with Framingo today!

About

Framingo is an open-source project framework that includes various small modules and components, making it easier for users to start their own projects.

Resources

License

Stars

Watchers

Forks

Packages

No packages published