README
¶
Building Applications with Framingo
This directory contains a complete example application demonstrating how to build production-ready HTTP API services using the Framingo framework. This guide walks you through all components and shows how they work together.
Table of Contents
- Overview
- Project Structure
- Quick Start
- Architecture
- Building Your Application
- Component Reference
- Development Workflow
- Production Deployment
Overview
The Framingo framework provides a modular, service-oriented architecture for building scalable HTTP APIs. This example demonstrates:
- Service Layer: Business logic with lifecycle management
- HTTP API Layer: RESTful endpoints with routing and middleware
- Command Layer: CLI interface for application control
- Configuration Management: YAML-based config with environment variable overrides
- Dependency Management: Automatic service dependency resolution
Project Structure
example/
+-- components/ # Application components
| +-- cmd/ # CLI interface (Cobra)
| | +-- example/ # Command implementations
| +-- server/ # Server component
| +-- example/ # Server orchestration
+-- services/ # Business logic services
| +-- example/ # Example service implementation
+-- routers/ # HTTP route handlers
| +-- example/ # Example router with handlers
+-- middlewares/ # HTTP middleware
| +-- example/ # Example middleware (deflate compression)
+-- types/ # Type definitions
| +-- entity/ # Business entities
+-- utils/ # Utility modules
| +-- infra/ # Infrastructure utilities
+-- README.md # This file
Quick Start
1. Create Your Main Application
// cmd/myapp/main.go
package main
import (
"fmt"
"os"
"github.com/xhanio/framingo/example/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)
}
}
2. Create Configuration File
# config.yaml
log:
level: 0 # Debug
file: /var/log/myapp.log
rotation:
max_size: 100
max_backups: 3
max_age: 7
db:
type: postgres
source:
host: 127.0.0.1
port: 5432
user: dbuser
password: dbpass
dbname: mydb
connection:
max_open: 10
max_idle: 5
max_lifetime: 1h
api:
http:
host: 0.0.0.0
port: 8080
prefix: /api/v1
throttle:
rps: 100.0
burst_size: 200
3. Build and Run
# Build the application
go build -o myapp cmd/myapp/main.go
# Run the server
./myapp daemon -c config.yaml
# Check version
./myapp version
4. Test the API
# Test the example endpoint
curl http://localhost:8080/api/v1/demo/example
# Response: Good
Architecture
Layer Overview
graph TB
subgraph "Application Layer"
CLI[CLI Layer<br/>components/cmd<br/>User interface & argument parsing]
end
subgraph "Server Component"
Server[Server Component<br/>components/server<br/>Orchestration & lifecycle management]
end
subgraph "Core Layers"
Services[Services<br/>services/<br/>Business Logic]
Routers[Routers<br/>routers/<br/>HTTP Handlers]
Middlewares[Middlewares<br/>middlewares/<br/>Request Processing]
end
CLI --> Server
Server --> Services
Server --> Routers
Server --> Middlewares
style CLI fill:#e1f5ff
style Server fill:#fff4e1
style Services fill:#e8f5e9
style Routers fill:#fce4ec
style Middlewares fill:#f3e5f5
Request Flow
flowchart TD
Start([HTTP Request]) --> APIServer
subgraph APIServer[API Server - pkg/services/api/server]
RouteMatch[Route Matching]
BuiltInMW[Built-in Middleware Chain]
end
APIServer --> CustomMW
subgraph CustomMW[Custom Middleware - middlewares/]
Preprocessing[Request Preprocessing]
Auth[Authentication]
Validation[Validation]
end
CustomMW --> RouterHandler
subgraph RouterHandler[Router Handler - routers/]
ParseRequest[Request Parsing]
CallService[Call Service Layer]
end
RouterHandler --> ServiceLayer
subgraph ServiceLayer[Service Layer - services/]
BusinessLogic[Business Logic]
DataAccess[Data Access]
end
ServiceLayer --> External[Database / External Services]
External --> ServiceLayer
ServiceLayer --> RouterHandler
RouterHandler --> CustomMW
CustomMW --> APIServer
APIServer --> End([HTTP Response])
style Start fill:#4caf50,color:#fff
style End fill:#4caf50,color:#fff
Building Your Application
Step 1: Define Your Entities
Create entity types for your domain models:
// types/entity/user.go
package entity
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
See types/entity/ for examples.
Step 2: Create a Service
Services contain your business logic and implement lifecycle management.
// services/user/model.go
package user
import (
"context"
"github.com/xhanio/framingo/pkg/types/common"
"github.com/xhanio/framingo/example/types/entity"
)
type Manager interface {
common.Service
common.Initializable
common.Daemon
GetUser(ctx context.Context, id string) (*entity.User, error)
CreateUser(ctx context.Context, user *entity.User) error
}
// services/user/manager.go
package user
type manager struct {
log log.Logger
db db.Manager
}
func New(db db.Manager, logger log.Logger) Manager {
return &manager{
log: logger,
db: db,
}
}
func (m *manager) GetUser(ctx context.Context, id string) (*entity.User, error) {
// Business logic here
return &entity.User{ID: id}, nil
}
Learn more: Service Documentation
Step 3: Create a Router
Routers define HTTP endpoints and handlers.
// routers/user/router.yaml
server: http
prefix: /users
handlers:
- method: GET
path: /:id
func: GetUser
- method: POST
path: /
func: CreateUser
// routers/user/handler.go
package user
import (
"github.com/labstack/echo/v4"
"github.com/xhanio/framingo/example/services/user"
)
type router struct {
userService user.Manager
}
func (r *router) GetUser(c echo.Context) error {
id := c.Param("id")
user, err := r.userService.GetUser(c.Request().Context(), id)
if err != nil {
return err
}
return c.JSON(200, user)
}
Learn more: Router Documentation
Step 4: Create Middleware (Optional)
Middlewares process requests before they reach handlers.
// middlewares/auth/middleware.go
package auth
func (m *middleware) Func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
token := c.Request().Header.Get("Authorization")
if !m.validateToken(token) {
return errors.Unauthorized.New("invalid token")
}
return next(c)
}
}
Learn more: Middleware Documentation
Step 5: Wire Everything Together
Update the server component to register your services, routers, and middlewares.
// components/server/example/api.go
func (m *manager) initAPI() error {
middlewares := []api.Middleware{
authmw.New(),
}
routers := []api.Router{
user.New(m.userService, m.log),
}
m.api.RegisterMiddlewares(middlewares...)
return m.api.RegisterRouters(routers...)
}
// components/server/example/manager.go - Init() method
func (m *manager) Init() error {
// ... (logger, db setup)
// Initialize your services
m.userService = user.New(m.db, m.log)
// Register services
m.services.Register(
m.db,
m.userService,
)
// Resolve dependencies
if err := m.services.TopoSort(); err != nil {
return errors.Wrap(err)
}
// Add API server (must be last)
m.services.Register(m.api)
// Initialize all services
if err := m.services.Init(); err != nil {
return err
}
// Initialize API components
return m.initAPI()
}
Learn more: Server Component Documentation
Step 6: Build and Deploy
# Build with version info
go build -ldflags="-X github.com/xhanio/framingo/pkg/types/info.Version=1.0.0" \
-o myapp cmd/myapp/main.go
# Run
./myapp daemon -c config.yaml
Learn more: CLI Documentation
Component Reference
Services Layer
Purpose: Implement business logic with lifecycle management
Key Features:
- Dependency injection
- Lifecycle methods (Init, Start, Stop)
- Context-aware operations
- Automatic dependency resolution
Documentation: services/README.md
Example: services/example/
Routers Layer
Purpose: Define HTTP endpoints and handle requests
Key Features:
- YAML-based route configuration
- Automatic handler discovery via reflection
- Service dependency injection
- Echo framework integration
Documentation: routers/README.md
Example: routers/example/
Middlewares Layer
Purpose: Process HTTP requests before handlers
Key Features:
- Request/response modification
- Authentication and authorization
- Rate limiting (built-in)
- Error handling (built-in)
Documentation: middlewares/README.md
Example: middlewares/example/
Server Component
Purpose: Orchestrate all services and manage lifecycle
Key Features:
- Configuration management (Viper)
- Service dependency resolution
- Multiple API servers
- Graceful shutdown
- Signal handling
- pprof profiling
Documentation: components/server/README.md
Example: components/server/example/
CMD Component
Purpose: Provide CLI interface for the application
Key Features:
- Command-line argument parsing (Cobra)
- Multiple commands support
- Help generation
- Version information
Documentation: components/cmd/README.md
Example: components/cmd/example/
Development Workflow
Project Setup
- Clone or create your project structure:
mkdir -p myapp/{cmd/myapp,services,routers,middlewares,types/entity,components/{cmd,server}}
- Initialize Go module:
cd myapp
go mod init github.com/yourorg/myapp
go get github.com/xhanio/framingo
- Copy example files as templates:
# Use example files as starting point
cp -r $FRAMINGO_PATH/example/services/example myapp/services/myservice
# Modify as needed
Development Cycle
- Define your entities (types/entity/)
- Implement services (services/)
- Create routers (routers/)
- Add middlewares if needed (middlewares/)
- Wire in server component (components/server/)
- Test locally:
go run cmd/myapp/main.go daemon -c config.yaml
Testing
# Run tests
go test ./...
# Run with coverage
go test -cover ./...
# Run specific service tests
go test ./services/myservice/...
Debugging
Using pprof:
# config.yaml
pprof:
port: 6060
# Start server
./myapp daemon -c config.yaml
# Access profiling
go tool pprof http://localhost:6060/debug/pprof/profile
Using signals:
# Print service info
kill -USR1 $(pgrep myapp)
# Print stack traces
kill -USR2 $(pgrep myapp)
# Graceful shutdown
kill -INT $(pgrep myapp)
Production Deployment
Build for Production
#!/bin/bash
VERSION=$(git describe --tags --always)
COMMIT=$(git rev-parse HEAD)
BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
go build -ldflags=" \
-X github.com/xhanio/framingo/pkg/types/info.Version=${VERSION} \
-X github.com/xhanio/framingo/pkg/types/info.Commit=${COMMIT} \
-X github.com/xhanio/framingo/pkg/types/info.BuildTime=${BUILD_TIME}" \
-o myapp cmd/myapp/main.go
Docker Deployment
# Dockerfile
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp cmd/myapp/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /root/
COPY --from=builder /app/myapp .
COPY config.yaml .
EXPOSE 8080
CMD ["./myapp", "daemon", "-c", "config.yaml"]
# Build and run
docker build -t myapp:latest .
docker run -p 8080:8080 myapp:latest
Kubernetes Deployment
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
env:
- name: FRAMINGO_DB_HOST
valueFrom:
secretKeyRef:
name: db-secret
key: host
volumeMounts:
- name: config
mountPath: /etc/myapp
volumes:
- name: config
configMap:
name: myapp-config
Systemd Service
# /etc/systemd/system/myapp.service
[Unit]
Description=My Framingo Application
After=network.target postgresql.service
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp daemon -c /etc/myapp/config.yaml
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Configuration Management
Environment-specific configs:
# Development
./myapp daemon -c config.dev.yaml
# Staging
./myapp daemon -c config.staging.yaml
# Production
./myapp daemon -c config.prod.yaml
Environment variable overrides:
export FRAMINGO_DB_HOST=prod-db.example.com
export FRAMINGO_DB_PASSWORD=$(cat /run/secrets/db_password)
export FRAMINGO_API_HTTP_PORT=8080
./myapp daemon -c config.yaml
Best Practices
Project Organization
- Separate concerns: Keep services, routers, and middlewares in separate packages
- Use entity types: Define clear data structures in types/entity
- Dependency injection: Pass dependencies explicitly, don't use globals
- Interface-based design: Define interfaces in model.go files
Configuration
- Use YAML for structure: Define server config, DB settings in YAML
- Use env vars for secrets: Override sensitive data via environment variables
- Validate on startup: Check configuration during Init() phase
- Document defaults: Provide sensible defaults for all config values
Error Handling
- Use error wrapping: Use
errors.Wrap()to add context - Return appropriate HTTP codes: Use error categories (BadRequest, NotFound, etc.)
- Log errors: Always log errors at service boundaries
- Don't panic: Use error returns instead of panics
Testing
- Test services independently: Mock dependencies for unit tests
- Test handlers with contexts: Use echo test utilities
- Integration tests: Test full request flow end-to-end
- Coverage: Aim for >80% coverage on critical paths
Performance
- Use connection pooling: Configure DB connection limits
- Enable throttling: Set rate limits per endpoint or server-wide
- Profile regularly: Use pprof to identify bottlenecks
- Cache when appropriate: Add caching layer for expensive operations
Common Patterns
Adding Database Support
// In service
type manager struct {
db db.Manager
}
func (m *manager) GetUser(ctx context.Context, id string) (*entity.User, error) {
var user entity.User
err := m.db.DB().WithContext(ctx).
Table("users").
Where("id = ?", id).
First(&user).Error
return &user, errors.Wrap(err)
}
Adding Authentication Middleware
// middlewares/auth/middleware.go
func (m *middleware) Func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
token := c.Request().Header.Get("Authorization")
user, err := m.validateToken(token)
if err != nil {
return errors.Unauthorized.New("invalid token")
}
c.Set("user", user)
return next(c)
}
}
// In handler
func (r *router) GetProfile(c echo.Context) error {
user := c.Get("user").(*entity.User)
return c.JSON(200, user)
}
Multiple API Servers
# config.yaml
api:
public:
host: 0.0.0.0
port: 8080
prefix: /api/v1
admin:
host: 127.0.0.1
port: 8081
prefix: /admin
# routers/public/router.yaml
server: public # Target public server
# routers/admin/router.yaml
server: admin # Target admin server
Troubleshooting
Service won't start
- Check config file path:
./myapp daemon -c /correct/path/config.yaml - Check configuration syntax:
cat config.yaml | yaml-lint - Check logs for initialization errors
- Verify database connectivity
Handler not found (404)
- Check router YAML configuration (server, prefix, path)
- Verify handler function name matches YAML
- Check router registration in api.go
- Enable debug logging to see registered routes
Service dependencies error
- Check Dependencies() method returns correct services
- Ensure services are registered before TopoSort()
- Look for circular dependencies
- Verify all required services are initialized
Rate limiting issues
- Check throttle configuration in config.yaml
- Adjust RPS and burst_size for your load
- Consider per-handler throttle overrides
- Monitor rate limit errors in logs
Resources
Framework Documentation
External Libraries
- Echo Framework - HTTP router
- Cobra - CLI framework
- Viper - Configuration management
- GORM - ORM (if using DB manager)
Example Components
- Services - Business logic layer
- Routers - HTTP routing layer
- Middlewares - Request processing
- Server Component - Orchestration
- CMD Component - CLI interface
Next Steps
- Explore the examples: Review each component's README and implementation
- Create a simple service: Start with a basic service following the example pattern
- Add an API endpoint: Create a router with handlers for your service
- Test locally: Build and run your application
- Add features incrementally: Middleware, database integration, etc.
- Deploy: Follow production deployment guidelines
Getting Help
- Check component-specific README files for detailed documentation
- Review example implementations in each directory
- Examine the framework source code in
pkg/directory - Look at integration patterns in
components/server/example/
Happy Building with Framingo!