3. Development Workflow
Author: System Architecture Team
Status: Active
Last Updated: 2025-06-14
Goals
- Document the complete development workflow from schema changes to deployment
- Provide clear guidance for common development tasks
- Establish best practices for testing and validation
- Define the feedback loop for rapid development iteration
Non-Goals
- CI/CD pipeline configuration (separate infrastructure concern)
- Production deployment strategies
- Performance profiling and optimization workflows
- Security testing and penetration testing procedures
Overview
The development workflow is designed around the principle of schema-driven development, where changes to either the API specification (TypeSpec) or database schema (SQL) trigger a cascade of code regeneration, testing, and validation.
The workflow supports several common development scenarios:
- Adding new API endpoints
- Modifying database schema
- Adding business logic
- Bug fixes and maintenance
Detailed Design
Complete Development Cycle
graph TB
subgraph "Development Setup"
Setup[make db-setup<br/>Initial environment]
Deps[make dev-deps<br/>Install tools]
end
subgraph "Schema Changes"
APIChange[Modify spec/main.tsp]
DBChange[Modify _db/schema.sql]
QueryChange[Modify _db/query_*.sql]
end
subgraph "Code Generation"
Generate[make build_dev<br/>Full regeneration]
APIGen[make ogen<br/>API code only]
DBGen[make sqlc-generate<br/>DB code only]
end
subgraph "Implementation"
Business[Implement business logic<br/>internal/api_service.go]
Tests[Write/update tests<br/>*_test.go]
end
subgraph "Validation"
UnitTest[make test-short<br/>Unit tests]
IntegrationTest[make test-integration<br/>Integration tests]
ManualTest[Manual API testing]
end
subgraph "Commit"
Review[Code review]
Commit[git commit]
end
Setup --> Deps
Deps --> APIChange
Deps --> DBChange
Deps --> QueryChange
APIChange --> Generate
DBChange --> Generate
QueryChange --> Generate
Generate --> Business
Business --> Tests
Tests --> UnitTest
UnitTest --> IntegrationTest
IntegrationTest --> ManualTest
ManualTest --> Review
Review --> Commit
Common Development Scenarios
Scenario 1: Adding a New API Endpoint
Step 1: Define API Contract
// spec/main.tsp
@tag("facilities")
@route("/api/v1/facilities/{id}/bookings/")
@get
@summary("List facility bookings")
op facilities_bookings_list(
@path id: integer,
@query date?: string
): FacilityBooking[] | ErrorResponse;
Step 2: Generate Code
make ogen # Regenerate API handlers
Step 3: Implement Business Logic
// internal/api_service.go
func (s *Service) FacilitiesBookingsList(ctx context.Context, req api.FacilitiesBookingsListRequest) (api.FacilitiesBookingsListResponse, error) {
// Implementation using generated database queries
bookings, err := s.db.Queries().ListFacilityBookings(ctx, s.db.Pool(), req.ID)
if err != nil {
return &api.FacilitiesBookingsListInternalServerError{}, err
}
return &api.FacilitiesBookingsListOK{
Data: convertBookingsToAPI(bookings),
}, nil
}
Step 4: Test and Validate
make test-short # Unit tests
make test-integration # Integration tests with real DB
Scenario 2: Database Schema Evolution
Step 1: Update Schema
-- _db/schema.sql
CREATE TABLE bookings (
id SERIAL PRIMARY KEY,
facility_id INTEGER NOT NULL REFERENCES facilities(id),
user_id INTEGER NOT NULL REFERENCES users(id),
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
Step 2: Add Queries
-- _db/query_bookings.sql
-- name: ListFacilityBookings :many
SELECT id, facility_id, user_id, start_time, end_time, created_at, updated_at
FROM bookings
WHERE facility_id = $1
ORDER BY start_time ASC;
-- name: CreateBooking :one
INSERT INTO bookings (facility_id, user_id, start_time, end_time)
VALUES ($1, $2, $3, $4)
RETURNING id, facility_id, user_id, start_time, end_time, created_at, updated_at;
Step 3: Apply Schema and Generate Code
make atlas-apply # Apply schema to database
make sqlc-generate # Generate new Go database code
Step 4: Update API Contract (if needed)
// spec/main.tsp
model FacilityBooking {
@visibility(Lifecycle.Read) id: integer;
facility_id: integer;
user_id: integer;
start_time: utcDateTime;
end_time: utcDateTime;
@visibility(Lifecycle.Read) created_at: utcDateTime;
@visibility(Lifecycle.Read) updated_at: utcDateTime;
}
Step 5: Full Build and Test
make build_dev # Complete pipeline: format, lint, generate, test, build
Scenario 3: Bug Fix Development
Step 1: Reproduce Issue
# Start local environment
make db-up
go run cmd/api-server/main.go
# Test the problematic endpoint
curl -X GET "http://localhost:8080/api/v1/facilities/999"
Step 2: Write Failing Test
// internal/service_test.go
func TestFacilitiesRetrieve_NotFound(t *testing.T) {
svc := internal.NewService(nil)
resp, err := svc.FacilitiesRetrieve(context.Background(), api.FacilitiesRetrieveRequest{
ID: 999, // Non-existent facility
})
// This test should initially fail, demonstrating the bug
assert.Error(t, err)
assert.IsType(t, &api.FacilitiesRetrieveNotFound{}, resp)
}
Step 3: Fix Implementation
// internal/api_service.go
func (s *Service) FacilitiesRetrieve(ctx context.Context, req api.FacilitiesRetrieveRequest) (api.FacilitiesRetrieveResponse, error) {
facility, err := s.db.Queries().GetFacilityByID(ctx, s.db.Pool(), req.ID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return &api.FacilitiesRetrieveNotFound{}, nil // Fix: return proper 404
}
return &api.FacilitiesRetrieveInternalServerError{}, err
}
return &api.FacilitiesRetrieveOK{
Data: convertFacilityToAPI(facility),
}, nil
}
Step 4: Validate Fix
make test-short # Ensure test now passes
make build_dev # Full validation
Development Environment Management
Initial Setup
# First-time setup
git clone <repository>
cd facility_reservation_go
# Install development dependencies
make dev-deps
# Setup database and generate initial code
make db-setup
Daily Development Loop
# Start development session
make db-up # Start PostgreSQL
# Make changes to code/schema...
# Validate changes
make build_dev # Full build and test
# Optional: quick iteration
make ogen # Just regenerate API code
make sqlc-generate # Just regenerate DB code
make test-short # Just unit tests
Database Management
# View current schema
make atlas-status
# Apply schema changes
make atlas-apply
# Reset database (destructive)
make db-clean
make db-setup
Testing Strategy Integration
Test-Driven Development Flow
- Write failing test for new functionality
- Run test to confirm it fails (
make test-short
) - Implement minimal code to make test pass
- Refactor while keeping tests green
- Run full test suite (
make build_dev
)
Integration Testing Workflow
# Dedicated test database
make db-test-up # Start test PostgreSQL on port 5433
make atlas-apply-test # Apply schema to test DB
# Run integration tests
make test-integration # Uses TEST_DATABASE_URL
# Clean up
docker-compose down postgres-test # Stop test database
Alternatives Considered
Alternative 1: Manual Code Management
Pros: Complete control over generated code
Cons: Consistency issues, manual synchronization, prone to errors
Rejected: Benefits of code generation outweigh control trade-offs
Alternative 2: Watch-Mode Development
Pros: Automatic regeneration on file changes
Cons: Complex setup, potential infinite loops, resource intensive
Rejected: Manual control provides better debugging experience
Alternative 3: Separate API and Database Workflows
Pros: Independent development of API and database concerns
Cons: Integration complexity, potential for drift between layers
Rejected: Unified workflow ensures consistency and reduces cognitive load
Testing Strategy
Development Testing Levels
- Unit Tests (
make test-short
): Fast feedback on business logic - Integration Tests (
make test-integration
): Database interaction validation - Build Tests (
make build_dev
): Complete system validation - Manual Testing: API endpoint validation during development
Continuous Validation
# Pre-commit validation
make build_dev # Must pass before committing
# Quick iteration during development
make test-short && echo "✅ Unit tests passed"
# Database schema validation
make atlas-apply && echo "✅ Schema applied successfully"
Future Considerations
Potential Improvements
- Watch Mode: File watching for automatic regeneration during development
- Development Containers: Docker-based development environment
- IDE Integration: Better integration with editors for generated code
- Performance Profiling: Integration with profiling tools during development
Workflow Optimization
- Parallel Testing: Run unit and integration tests in parallel
- Incremental Generation: Only regenerate changed components
- Caching: Cache generated artifacts for faster iteration
- Hot Reload: Server restart on code changes during development
Known Limitations
- Build Time: Full regeneration can be slow for large schemas
- Learning Curve: New developers need to understand entire workflow
- Tool Dependencies: Requires specific versions of external tools
- Error Correlation: Errors may span multiple generation steps
Scaling Considerations
As the project grows:
- Modular Schemas: Split large schemas into focused modules
- Parallel Development: Support for multiple developers working on different APIs
- Integration Environments: Staging environments for testing schema changes
- Automated Testing: More comprehensive test automation for complex workflows