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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user