refactor: Align Epic 0 & Epic 1 with true microservices architecture

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.
This commit is contained in:
2025-11-06 09:23:36 +01:00
parent 38a251968c
commit 16731fc1d1
25 changed files with 1826 additions and 21 deletions

View File

@@ -22,13 +22,15 @@ type Client struct {
// Config holds database configuration.
type Config struct {
DSN string
Schema string // Schema name for schema isolation (e.g., "identity", "auth", "authz", "audit")
MaxConnections int
MaxIdleConns int
ConnMaxLifetime time.Duration
ConnMaxIdleTime time.Duration
}
// NewClient creates a new Ent client with connection pooling.
// NewClient creates a new Ent client with connection pooling and schema isolation support.
// If schema is provided, it will be created if it doesn't exist and set as the search path.
func NewClient(cfg Config) (*Client, error) {
// Open database connection
db, err := sql.Open("postgres", cfg.DSN)
@@ -51,6 +53,19 @@ func NewClient(cfg Config) (*Client, error) {
return nil, fmt.Errorf("failed to ping database: %w", err)
}
// Create schema if provided
if cfg.Schema != "" {
if err := createSchemaIfNotExists(ctx, db, cfg.Schema); err != nil {
_ = db.Close()
return nil, fmt.Errorf("failed to create schema %s: %w", cfg.Schema, err)
}
// Set search path to the schema
if _, err := db.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", cfg.Schema)); err != nil {
_ = db.Close()
return nil, fmt.Errorf("failed to set search path to schema %s: %w", cfg.Schema, err)
}
}
// Create Ent driver
drv := entsql.OpenDB(dialect.Postgres, db)
@@ -63,6 +78,49 @@ func NewClient(cfg Config) (*Client, error) {
}, nil
}
// NewClientWithSchema is a convenience function that creates a client with a specific schema.
func NewClientWithSchema(dsn string, schema string) (*Client, error) {
return NewClient(Config{
DSN: dsn,
Schema: schema,
MaxConnections: 25,
MaxIdleConns: 5,
ConnMaxLifetime: 5 * time.Minute,
ConnMaxIdleTime: 10 * time.Minute,
})
}
// createSchemaIfNotExists creates a PostgreSQL schema if it doesn't exist.
func createSchemaIfNotExists(ctx context.Context, db *sql.DB, schemaName string) error {
// Use a transaction to ensure atomicity
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
// Check if schema exists
var exists bool
err = tx.QueryRowContext(ctx,
"SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name = $1)",
schemaName,
).Scan(&exists)
if err != nil {
return err
}
// Create schema if it doesn't exist
if !exists {
// Use fmt.Sprintf for schema name since it's a configuration value, not user input
_, err = tx.ExecContext(ctx, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schemaName))
if err != nil {
return err
}
}
return tx.Commit()
}
// Close closes the database connection.
func (c *Client) Close() error {
if err := c.Client.Close(); err != nil {