Refactor core kernel and infrastructure to support true microservices architecture where services are independently deployable. Phase 1: Core Kernel Cleanup - Remove database provider from CoreModule (services create their own) - Update ProvideHealthRegistry to not depend on database - Add schema support to database client (NewClientWithSchema) - Update main entry point to remove database dependency - Core kernel now provides only: config, logger, error bus, health, metrics, tracer, service registry Phase 2: Service Registry Implementation - Create ServiceRegistry interface (pkg/registry/registry.go) - Implement Consul registry (internal/registry/consul/consul.go) - Add Consul dependency (github.com/hashicorp/consul/api) - Add registry configuration to config/default.yaml - Add ProvideServiceRegistry() to DI container Phase 3: Service Client Interfaces - Create service client interfaces: - pkg/services/auth.go - AuthServiceClient - pkg/services/identity.go - IdentityServiceClient - pkg/services/authz.go - AuthzServiceClient - pkg/services/audit.go - AuditServiceClient - Create ServiceClientFactory (internal/client/factory.go) - Create stub gRPC client implementations (internal/client/grpc/) - Add ProvideServiceClientFactory() to DI container Phase 4: gRPC Service Definitions - Create proto files for all core services: - api/proto/auth.proto - api/proto/identity.proto - api/proto/authz.proto - api/proto/audit.proto - Add generate-proto target to Makefile Phase 5: API Gateway Implementation - Create API Gateway service entry point (cmd/api-gateway/main.go) - Create Gateway implementation (services/gateway/gateway.go) - Add gateway configuration to config/default.yaml - Gateway registers with Consul and routes requests to backend services All code compiles successfully. Core services (Auth, Identity, Authz, Audit) will be implemented in Epic 2 using these foundations.
128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
// Package gateway provides API Gateway implementation.
|
|
package gateway
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
|
|
"git.dcentral.systems/toolz/goplt/internal/client"
|
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Gateway handles routing requests to backend services.
|
|
type Gateway struct {
|
|
config config.ConfigProvider
|
|
log logger.Logger
|
|
clientFactory *client.ServiceClientFactory
|
|
registry registry.ServiceRegistry
|
|
routes []RouteConfig
|
|
}
|
|
|
|
// RouteConfig defines a route configuration.
|
|
type RouteConfig struct {
|
|
Path string
|
|
Service string
|
|
AuthRequired bool
|
|
}
|
|
|
|
// NewGateway creates a new API Gateway instance.
|
|
func NewGateway(
|
|
cfg config.ConfigProvider,
|
|
log logger.Logger,
|
|
clientFactory *client.ServiceClientFactory,
|
|
reg registry.ServiceRegistry,
|
|
) (*Gateway, error) {
|
|
// Load route configurations
|
|
routes := loadRoutes(cfg)
|
|
|
|
return &Gateway{
|
|
config: cfg,
|
|
log: log,
|
|
clientFactory: clientFactory,
|
|
registry: reg,
|
|
routes: routes,
|
|
}, nil
|
|
}
|
|
|
|
// SetupRoutes configures routes on the Gin router.
|
|
func (g *Gateway) SetupRoutes(router *gin.Engine) {
|
|
// Setup route handlers
|
|
for _, route := range g.routes {
|
|
route := route // Capture for closure
|
|
router.Any(route.Path, g.handleRoute(route))
|
|
}
|
|
|
|
// Default handler for unmatched routes
|
|
router.NoRoute(func(c *gin.Context) {
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "Route not found",
|
|
"path": c.Request.URL.Path,
|
|
})
|
|
})
|
|
}
|
|
|
|
// handleRoute returns a handler function for a route.
|
|
func (g *Gateway) handleRoute(route RouteConfig) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// TODO: Add authentication middleware if auth_required is true
|
|
// TODO: Add rate limiting middleware
|
|
// TODO: Add CORS middleware
|
|
|
|
// Discover service instances
|
|
ctx := c.Request.Context()
|
|
instances, err := g.registry.Discover(ctx, route.Service)
|
|
if err != nil {
|
|
g.log.Error("Failed to discover service",
|
|
logger.String("service", route.Service),
|
|
logger.Error(err),
|
|
)
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
"error": "Service unavailable",
|
|
})
|
|
return
|
|
}
|
|
|
|
if len(instances) == 0 {
|
|
g.log.Warn("No instances found for service",
|
|
logger.String("service", route.Service),
|
|
)
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
"error": "Service unavailable",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Use first healthy instance (load balancing can be added later)
|
|
instance := instances[0]
|
|
targetURL := fmt.Sprintf("http://%s:%d", instance.Address, instance.Port)
|
|
|
|
// Create reverse proxy
|
|
target, err := url.Parse(targetURL)
|
|
if err != nil {
|
|
g.log.Error("Failed to parse target URL",
|
|
logger.String("url", targetURL),
|
|
logger.Error(err),
|
|
)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Internal server error",
|
|
})
|
|
return
|
|
}
|
|
|
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
|
proxy.ServeHTTP(c.Writer, c.Request)
|
|
}
|
|
}
|
|
|
|
// loadRoutes loads route configurations from config.
|
|
func loadRoutes(cfg config.ConfigProvider) []RouteConfig {
|
|
// For now, return empty routes - will be loaded from config in future
|
|
// This is a placeholder implementation
|
|
return []RouteConfig{}
|
|
}
|