cognito and permit shared prod environment support * code complete * user creattion tool test harness
11 KiB
Code Organization
Project Structure
The project follows Go Standard Project Layout with enhanced organization for batch processing, observability, and background task management.
The codebase follows the Standard Go Project Layout with additional conventions specific to the domain requirements.
Directory Overview
query-orchestration.2/
├── cmd/ # Main applications and entry points
├── internal/ # Private application code
├── api/ # Generated API server implementations
├── pkg/ # Public library code
├── deployments/ # Deployment configurations
├── docs/ # Documentation
├── scripts/ # Build and automation scripts
├── test/ # Integration and e2e tests
├── assets/ # Test data and sample files
└── vendor/ # Vendored dependencies
Package Architecture
Command Layer (cmd/)
Entry points for all deployable services, following the pattern:
cmd/
├── [serviceName]/
│ └── main.go # Service bootstrap and configuration
Naming Conventions:
- APIs:
[functionality]API(e.g.,queryAPI) - Runners:
[functionality]Runner(e.g.,docInitRunner) - Utilities: Descriptive names (e.g.,
healthcheck)
Internal Packages (internal/)
Core business logic and infrastructure components, organized by domain:
New Components (Since June 2025):
internal/backgroundtask/: Background task execution framework with periodic schedulinginternal/document/batch/: Batch upload processing and ZIP archive handlinginternal/serviceconfig/observability/prometheus/: Comprehensive Prometheus metrics framework
Internal Packages (internal/)
Core business logic organized by domain boundaries:
Domain Services
internal/
├── client/ # Client management domain
├── document/ # Document processing domain
├── fieldextraction/ # Field extraction domain
├── collector/ # Client-specific configurations
└── export/ # Data export operations
Infrastructure Services
internal/
├── database/ # Data persistence layer
├── server/ # HTTP and queue server infrastructure
├── serviceconfig/ # Configuration management
├── cognitoauth/ # Authentication middleware (includes environment access control)
├── usermanagement/ # Cognito and Permit.io user operations, environment filtering
├── eula/ # EULA version management and compliance
├── test/ # Testing utilities
└── validation/ # Input validation
API Layer (api/)
Generated server implementations from OpenAPI specifications:
api/
├── queryAPI/ # REST API implementation
│ ├── api.gen.go # Generated types and interfaces
│ ├── controllers.go # Business logic implementation
│ ├── authHandlers.go # Authentication endpoints
│ └── *.go # Domain-specific handlers
└── [runnerName]/ # Queue-based service implementations
└── runner.go # Queue message processing
Design Patterns and Principles
Service Layer Pattern
Each domain follows a consistent service layer structure:
// Service struct with injected dependencies
type Service struct {
cfg ConfigProvider // Configuration interface
}
// Constructor with interface-based dependency injection
func New(cfg ConfigProvider) *Service {
return &Service{cfg: cfg}
}
// Business operations
func (s *Service) CreateEntity(ctx context.Context, req CreateRequest) (*Entity, error) {
// Business logic implementation
}
Repository Pattern
Database access is abstracted through repositories:
// Generated by SQLC from SQL queries
type Repository interface {
CreateClient(ctx context.Context, arg CreateClientParams) (Client, error)
GetClient(ctx context.Context, clientID string) (Client, error)
// ... other operations
}
// Service uses repository interface
type Service struct {
repo Repository
}
Configuration Provider Pattern
Services receive configuration through composed interfaces:
// Domain-specific configuration interface
type ConfigProvider interface {
serviceconfig.ConfigProvider // Base configuration
database.ConfigProvider // Database access
objectstore.ConfigProvider // S3 access
// ... other provider interfaces
}
// Concrete configuration embeds all providers
type Config struct {
serviceconfig.BaseConfig
database.Config
objectstore.Config
}
Dependency Injection
Services are composed through constructor injection:
// In main.go
cfg := &Config{
BaseConfig: baseConfig,
DatabaseConfig: dbConfig,
// ... other configs
}
// Create service with injected dependencies
svc := service.New(cfg)
// Create controller with service dependency
controller := api.NewController(svc)
Naming Conventions
Package Names
- Domain packages: Singular nouns (
client,document,fieldextraction) - Utility packages: Descriptive (
serviceconfig,cognitoauth) - Generated packages: Match source (
queryAPIfromqueryAPI.yaml)
File Names
- Service files:
service.go(main business logic) - Test files:
*_test.go(alongside source) - Private tests:
*private_test.go(package-private testing) - Interface files: Match domain (
client.go,document.go)
Function and Method Names
- Public functions: PascalCase (
CreateClient,GetDocument) - Private functions: camelCase (
validateInput,processResult) - Constructors:
NeworNew[Type](New,NewService) - Getters: No
Getprefix for simple accessors (ID(), notGetID())
Variable Names
- Short scope: Single letters (
i,j,cfor client) - Medium scope: Abbreviations (
ctx,req,resp) - Long scope: Descriptive names (
clientRepository,configProvider) - Constants: PascalCase (
MaxRetryAttempts,DefaultTimeout)
Type Names
- Structs: PascalCase nouns (
Client,DocumentProcessor) - Interfaces: PascalCase with optional "er" suffix (
ConfigProvider,Processor) - Enums: PascalCase with type suffix (
QueryType,ProcessingStatus)
Code Style Guidelines
Import Organization
package main
import (
// Standard library
"context"
"fmt"
// Third-party packages
"github.com/labstack/echo/v4"
"github.com/google/uuid"
// Local packages
"queryorchestration/internal/client"
"queryorchestration/internal/database"
)
Error Handling
// Wrap errors with context
func (s *Service) ProcessDocument(ctx context.Context, id uuid.UUID) error {
doc, err := s.repo.GetDocument(ctx, id)
if err != nil {
return fmt.Errorf("failed to get document %s: %w", id, err)
}
if err := s.processContent(doc); err != nil {
return fmt.Errorf("failed to process document content: %w", err)
}
return nil
}
Interface Design
// Prefer small, focused interfaces
type ClientReader interface {
GetClient(ctx context.Context, id string) (*Client, error)
}
type ClientWriter interface {
CreateClient(ctx context.Context, client *Client) error
UpdateClient(ctx context.Context, client *Client) error
}
// Compose when needed
type ClientRepository interface {
ClientReader
ClientWriter
}
Struct Design
// Group related fields
type Client struct {
// Identity
ID string
Name string
// Configuration
CanSync bool
// Metadata
CreatedAt time.Time
UpdatedAt time.Time
}
// Use embedding for composition
type APIClient struct {
Client
AuthToken string
}
Testing Patterns
Table-Driven Tests
func TestService_CreateClient(t *testing.T) {
tests := []struct {
name string
input CreateClientRequest
want *Client
wantErr bool
}{
{
name: "valid client",
input: CreateClientRequest{
ID: "test-client",
Name: "Test Client",
},
want: &Client{
ID: "test-client",
Name: "Test Client",
},
wantErr: false,
},
{
name: "empty ID",
input: CreateClientRequest{
Name: "Test Client",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
}
Mock Usage
// Generate mocks for interfaces
//go:generate mockery --name=ClientRepository --output=mocks
func TestService_WithMocks(t *testing.T) {
// Create mock
mockRepo := &mocks.ClientRepository{}
// Set expectations
mockRepo.On("GetClient", mock.Anything, "test-id").
Return(&Client{ID: "test-id"}, nil)
// Create service with mock
svc := &Service{repo: mockRepo}
// Test and verify
result, err := svc.GetClient(context.Background(), "test-id")
assert.NoError(t, err)
assert.Equal(t, "test-id", result.ID)
mockRepo.AssertExpectations(t)
}
Integration Test Structure
func TestIntegration_DocumentProcessing(t *testing.T) {
// Setup test environment
ctx := context.Background()
testDB := test.NewDatabase(t)
testS3 := test.NewObjectStore(t)
// Create service with real dependencies
cfg := &Config{
Database: testDB,
ObjectStore: testS3,
}
svc := New(cfg)
// Run test scenario
client := test.CreateTestClient(t, svc)
document := test.UploadTestDocument(t, svc, client.ID)
// Verify results
result := test.ProcessDocument(t, svc, document.ID)
assert.NotNil(t, result)
// Cleanup handled by test utilities
}
Architecture Decisions
Configuration Management
- Environment-first: All configuration from environment variables
- Type safety: Strong typing with validation
- Composition: Interface-based configuration providers
- Testing: Override-friendly for test scenarios
Error Handling
- Wrap errors: Add context at each layer
- Structured logging: Use slog for consistent error logging
- Graceful degradation: Continue operation when possible
- Client-friendly: Translate internal errors to appropriate HTTP status
Database Access
- Type safety: SQLC generates type-safe database code
- Migrations: Version-controlled schema evolution
- Connection pooling: Shared connections across services
- Transaction management: Explicit transaction boundaries
API Design
- Contract-first: OpenAPI specification drives implementation
- Version compatibility: Backward-compatible changes
- Validation: Automatic request/response validation
- Documentation: Auto-generated from specification
This organization enables maintainable, testable, and scalable code while following Go best practices and domain-driven design principles.