# Database Architecture Refactoring - Technical Debt ## Current Problem The current database layer violates several software architecture principles and creates maintenance issues: ### Issues with Current Implementation 1. **No Interface Abstraction**: - `Database` struct is concrete, not interface-based - Hard to mock for testing - Tight coupling between handlers and database implementation 2. **Type Switching Everywhere**: ```go switch h.database.GetDBType() { case "sqlite3": err = h.database.GetSQLiteQueries().SomeMethod(...) case "postgres": err = h.database.GetPostgreSQLQueries().SomeMethod(...) } ``` - Repeated in every handler method - Violates DRY principle - Makes adding new database types difficult 3. **Mixed Responsibilities**: - Single struct holds both SQLite and PostgreSQL queries - Database type detection mixed with query execution - Leaky abstraction (handlers know about database internals) 4. **Poor Testability**: - Cannot easily mock database operations - Hard to test database-specific edge cases - Difficult to test transaction behavior 5. **Violates Open/Closed Principle**: - Adding new database support requires modifying existing handlers - Cannot extend database support without changing core business logic ## Proposed Solution ### 1. Repository Pattern with Interface ```go // Domain-focused interface type ContentRepository interface { // Content operations GetContent(ctx context.Context, siteID, contentID string) (*Content, error) CreateContent(ctx context.Context, content *Content) (*Content, error) UpdateContent(ctx context.Context, content *Content) (*Content, error) DeleteContent(ctx context.Context, siteID, contentID string) error // Collection operations GetCollection(ctx context.Context, siteID, collectionID string) (*Collection, error) GetCollectionItems(ctx context.Context, siteID, collectionID string) ([]*CollectionItem, error) CreateCollectionItem(ctx context.Context, item *CollectionItem) (*CollectionItem, error) ReorderCollectionItems(ctx context.Context, siteID, collectionID string, positions []ItemPosition) error // Transaction support WithTransaction(ctx context.Context, fn func(ContentRepository) error) error } ``` ### 2. Database-Specific Implementations ```go type SQLiteRepository struct { queries *sqlite.Queries db *sql.DB } func (r *SQLiteRepository) GetContent(ctx context.Context, siteID, contentID string) (*Content, error) { result, err := r.queries.GetContent(ctx, sqlite.GetContentParams{ ID: contentID, SiteID: siteID, }) if err != nil { return nil, err } return r.convertSQLiteContent(result), nil } type PostgreSQLRepository struct { queries *postgresql.Queries db *sql.DB } func (r *PostgreSQLRepository) GetContent(ctx context.Context, siteID, contentID string) (*Content, error) { result, err := r.queries.GetContent(ctx, postgresql.GetContentParams{ ID: contentID, SiteID: siteID, }) if err != nil { return nil, err } return r.convertPostgreSQLContent(result), nil } ``` ### 3. Clean Handler Implementation ```go type ContentHandler struct { repository ContentRepository // Interface, not concrete type authService *auth.AuthService } func (h *ContentHandler) GetContent(w http.ResponseWriter, r *http.Request) { contentID := chi.URLParam(r, "id") siteID := r.URL.Query().Get("site_id") // No more type switching! content, err := h.repository.GetContent(r.Context(), siteID, contentID) if err != nil { http.Error(w, "Content not found", http.StatusNotFound) return } json.NewEncoder(w).Encode(content) } ``` ### 4. Dependency Injection ```go func NewContentHandler(repo ContentRepository, authService *auth.AuthService) *ContentHandler { return &ContentHandler{ repository: repo, authService: authService, } } // In main.go or wherever handlers are initialized var repo ContentRepository switch dbType { case "sqlite3": repo = NewSQLiteRepository(db) case "postgres": repo = NewPostgreSQLRepository(db) } contentHandler := NewContentHandler(repo, authService) ``` ## Benefits of Refactoring 1. **Single Responsibility**: Each repository handles one database type 2. **Open/Closed**: Easy to add new database types without modifying existing code 3. **Testability**: Can easily mock `ContentRepository` interface 4. **Clean Code**: Handlers focus on HTTP concerns, not database specifics 5. **Type Safety**: Interface ensures all database types implement same methods 6. **Performance**: No runtime type switching overhead ## Migration Strategy ### Phase 1: Create Interface and Base Implementations - [ ] Define `ContentRepository` interface - [ ] Create `SQLiteRepository` implementation - [ ] Create `PostgreSQLRepository` implementation - [ ] Add factory function for repository creation ### Phase 2: Migrate Handlers One by One - [ ] Start with simple handlers (GetContent, etc.) - [ ] Update handler constructors to use interface - [ ] Remove type switching logic - [ ] Add unit tests with mocked repository ### Phase 3: Advanced Features - [ ] Add transaction support to interface - [ ] Implement batch operations efficiently - [ ] Add connection pooling and retry logic - [ ] Performance optimization for each database type ### Phase 4: Cleanup - [ ] Remove old `Database` struct - [ ] Remove database type detection logic - [ ] Update documentation - [ ] Add integration tests ## Estimated Effort - **Phase 1**: 1-2 days (interface design and base implementations) - **Phase 2**: 2-3 days (handler migration and testing) - **Phase 3**: 1-2 days (advanced features) - **Phase 4**: 1 day (cleanup) **Total**: ~5-8 days for complete refactoring ## Priority **Medium Priority** - This is architectural debt that should be addressed when: 1. Adding support for new database types 2. Significant new database operations are needed 3. Testing infrastructure needs improvement 4. Performance optimization becomes critical The current implementation works correctly but is not maintainable long-term.