6dccf494f8
cleanup permit policies and correct documentation * docs * policy cleanup * refactor * Merge remote-tracking branch 'origin/feature/permit-policy-cleanup' into feature/permit-policy-cleanup * docs and edits
400 lines
11 KiB
Markdown
400 lines
11 KiB
Markdown
# 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](https://github.com/golang-standards/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 scheduling
|
|
- **`internal/document/batch/`**: Batch upload processing and ZIP archive handling
|
|
- **`internal/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
|
|
├── 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:
|
|
|
|
```go
|
|
// 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:
|
|
|
|
```go
|
|
// 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:
|
|
|
|
```go
|
|
// 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:
|
|
|
|
```go
|
|
// 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 (`queryAPI` from `queryAPI.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**: `New` or `New[Type]` (`New`, `NewService`)
|
|
- **Getters**: No `Get` prefix for simple accessors (`ID()`, not `GetID()`)
|
|
|
|
### Variable Names
|
|
- **Short scope**: Single letters (`i`, `j`, `c` for 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
|
|
```go
|
|
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
|
|
```go
|
|
// 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
|
|
```go
|
|
// 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
|
|
```go
|
|
// 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
|
|
```go
|
|
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
|
|
```go
|
|
// 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
|
|
```go
|
|
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.
|