Technical Design Document

Table of Contents

  1. System Design Overview
  2. Core Design Patterns
  3. Module Specifications
  4. Data Model Design
  5. API Design
  6. Event System Design
  7. Security Design
  8. Performance Design
  9. Error Handling Design
  10. Testing Strategy

System Design Overview

Design Goals

  1. Scalability: Support 100,000+ concurrent operations
  2. Maintainability: Clean, modular code structure
  3. Reliability: 99.9% uptime with graceful degradation
  4. Flexibility: Adapt to changing business requirements
  5. Performance: Sub-second response times

Design Constraints

  • Must integrate with existing authentication service
  • PostgreSQL as primary datastore
  • Go language for backend implementation
  • RESTful API design
  • JWT-based authentication

Core Design Patterns

1. Repository Pattern

Abstracts data access logic and provides a more object-oriented view of the persistence layer.

// Repository Interface
type ProjectRepository interface {
    Create(project *models.Project) error
    FindByID(id string) (*models.Project, error)
    FindByUniqueCode(code string) (*models.Project, error)
    Update(project *models.Project) error
    Delete(id string) error
    List(filters ProjectFilters) ([]*models.Project, error)
}

// Implementation
type projectRepository struct {
    db *gorm.DB
}

func (r *projectRepository) Create(project *models.Project) error {
    return r.db.Create(project).Error
}

2. Service Layer Pattern

Encapsulates business logic and coordinates between repositories.

// Service Interface
type ProjectService interface {
    CreateProject(dto CreateProjectDTO) (*models.Project, error)
    GetProject(uniqueCode string) (*models.Project, error)
    UpdateProject(uniqueCode string, dto UpdateProjectDTO) (*models.Project, error)
    ApproveProject(uniqueCode string, userID string) error
    GetProjectMetrics(uniqueCode string) (*ProjectMetrics, error)
}

// Implementation with business logic
type projectService struct {
    repo      ProjectRepository
    eventBus  EventBus
    validator Validator
}

func (s *projectService) CreateProject(dto CreateProjectDTO) (*models.Project, error) {
    // Validate input
    if err := s.validator.Validate(dto); err != nil {
        return nil, err
    }

    // Apply business rules
    project := &models.Project{
        Name:        dto.Name,
        UniqueCode:  generateUniqueCode(),
        Status:      enums.ProjectStatusPending,
        Progress:    enums.ProjectProgressTechnicalNegotiating,
    }

    // Persist
    if err := s.repo.Create(project); err != nil {
        return nil, err
    }

    // Emit event
    s.eventBus.Publish(ProjectCreatedEvent{
        ProjectID: project.ID,
        CreatedBy: dto.CreatorID,
    })

    return project, nil
}

3. Factory Pattern

Creates complex objects with proper initialization.

// Task Factory
type TaskFactory struct {
    idGenerator IDGenerator
    validator   Validator
}

func (f *TaskFactory) CreateTask(dto CreateTaskDTO) (*models.Task, error) {
    task := &models.Task{
        ID:         f.idGenerator.Generate(),
        Name:       dto.Name,
        UniqueCode: generateTaskCode(),
        Status:     enums.TaskStatusPending,
        Priority:   dto.Priority,
        Tags:       dto.Tags,
    }

    if err := f.validator.Validate(task); err != nil {
        return nil, err
    }

    return task, nil
}

4. Observer Pattern (Event-Driven)

Implements loose coupling between components through events.

// Event System
type EventBus interface {
    Subscribe(eventType string, handler EventHandler)
    Publish(event Event)
}

type EventHandler func(event Event)

type Event interface {
    Type() string
    Payload() interface{}
}

// Usage
eventBus.Subscribe("task.created", func(event Event) {
    // Send notification
    // Update analytics
    // Trigger workflows
})

5. Builder Pattern

Constructs complex queries with fluent interface.

// Query Builder
type TaskQueryBuilder struct {
    query *gorm.DB
}

func NewTaskQueryBuilder(db *gorm.DB) *TaskQueryBuilder {
    return &TaskQueryBuilder{query: db.Model(&models.Task{})}
}

func (b *TaskQueryBuilder) WithProject(projectID string) *TaskQueryBuilder {
    b.query = b.query.Where("projectId = ?", projectID)
    return b
}

func (b *TaskQueryBuilder) WithStatus(status string) *TaskQueryBuilder {
    b.query = b.query.Where("status = ?", status)
    return b
}

func (b *TaskQueryBuilder) WithPriority(priority string) *TaskQueryBuilder {
    b.query = b.query.Where("priority = ?", priority)
    return b
}

func (b *TaskQueryBuilder) Build() *gorm.DB {
    return b.query
}

// Usage
tasks := NewTaskQueryBuilder(db).
    WithProject("project123").
    WithStatus("in_progress").
    WithPriority("high").
    Build().
    Find(&results)

Module Specifications

Project Module

Components

/internal/
├── models/
│   ├── project.go         # Domain model
│   └── project_history.go # Audit model
├── dto/
│   └── project.go         # Data transfer objects
├── services/
│   └── project_service.go # Business logic
├── handlers/
│   └── project_handler.go # HTTP handlers
└── repositories/
    └── project_repo.go    # Data access

Interfaces

// Core Interfaces
type ProjectService interface {
    CreateProject(ctx context.Context, dto CreateProjectDTO) (*models.Project, error)
    GetProject(ctx context.Context, uniqueCode string) (*models.Project, error)
    UpdateProject(ctx context.Context, uniqueCode string, dto UpdateProjectDTO) (*models.Project, error)
    DeleteProject(ctx context.Context, uniqueCode string) error
    ListProjects(ctx context.Context, filters ListProjectsFilter) ([]*models.Project, int64, error)
    ApproveProject(ctx context.Context, uniqueCode string, approverID string) error
    UpdateProgress(ctx context.Context, uniqueCode string, progress string) error
    GetProjectMetrics(ctx context.Context, uniqueCode string) (*ProjectMetrics, error)
}

Task Module

Components

/internal/
├── models/
│   ├── task.go            # Domain model
│   ├── task_comment.go    # Comment model
│   └── task_activity.go   # Activity tracking
├── services/
│   ├── task_service.go    # Task logic
│   └── comment_service.go # Comment logic
├── handlers/
│   └── task_handler.go    # HTTP handlers
└── repositories/
    └── task_repo.go       # Data access

Task State Machine

type TaskStateMachine struct {
    transitions map[string][]string
}

func NewTaskStateMachine() *TaskStateMachine {
    return &TaskStateMachine{
        transitions: map[string][]string{
            "pending":        {"in_progress", "canceled", "deferred"},
            "in_progress":    {"on_hold", "blocked", "pending_review", "canceled"},
            "on_hold":        {"in_progress", "canceled"},
            "blocked":        {"in_progress", "canceled"},
            "pending_review": {"completed", "in_progress"},
            "completed":      {"archived"},
            "canceled":       {"archived"},
            "deferred":       {"pending", "canceled"},
            "archived":       {},
        },
    }
}

func (sm *TaskStateMachine) CanTransition(from, to string) bool {
    validTransitions, exists := sm.transitions[from]
    if !exists {
        return false
    }

    for _, valid := range validTransitions {
        if valid == to {
            return true
        }
    }
    return false
}

Product Module (EAV Implementation)

Entity-Attribute-Value Design

// Core Product Entity
type Product struct {
    ID          string
    Name        string
    Type        string // physical, software, service, document
    Status      string
    ProjectID   string

    // Dynamic attributes via EAV
    Attributes  []ProductAttribute `gorm:"foreignKey:ProductID"`
    AttributeMap map[string]interface{} `gorm:"-"`
}

// Attribute Storage
type ProductAttribute struct {
    ID            string
    ProductID     string
    AttributeKey  string
    AttributeValue string
    AttributeType string // string, number, boolean, date, json
}

// Type-safe attribute access
func (p *ProductAttribute) GetValue() interface{} {
    switch p.AttributeType {
    case "string":
        return p.AttributeValue
    case "number":
        val, _ := strconv.ParseFloat(p.AttributeValue, 64)
        return val
    case "boolean":
        return p.AttributeValue == "true"
    case "date":
        t, _ := time.Parse(time.RFC3339, p.AttributeValue)
        return t
    case "json":
        var result interface{}
        json.Unmarshal([]byte(p.AttributeValue), &result)
        return result
    default:
        return p.AttributeValue
    }
}

Data Model Design

Base Model Pattern

All entities inherit from a base model for consistency:

type BaseModel struct {
    ID        string       `gorm:"type:text;primaryKey"`
    CreatedAt time.Time    `gorm:"not null;default:CURRENT_TIMESTAMP"`
    UpdatedAt time.Time    `gorm:"not null;default:CURRENT_TIMESTAMP"`
    DeletedAt sql.NullTime `gorm:"index"`
}

func (b *BaseModel) BeforeCreate(tx *gorm.DB) error {
    if b.ID == "" {
        b.ID = GenerateULID()
    }
    return nil
}

Relationship Design

// One-to-Many
type Project struct {
    BaseModel
    Tasks []Task `gorm:"foreignKey:ProjectID"`
}

// Many-to-Many
type Task struct {
    BaseModel
    Participants []Employee `gorm:"many2many:task_participants;"`
}

// Self-Referencing
type TaskComment struct {
    BaseModel
    ParentID *string       `gorm:"type:text"`
    Parent   *TaskComment  `gorm:"foreignKey:ParentID"`
    Replies  []TaskComment `gorm:"foreignKey:ParentID"`
}

Index Strategy

-- Performance indexes
CREATE INDEX idx_projects_status ON projects(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_projects_unique_code ON projects(unique_code);
CREATE INDEX idx_tasks_project_id ON tasks(project_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_status_priority ON tasks(status, priority) WHERE deleted_at IS NULL;
CREATE INDEX idx_product_attributes_product_id ON product_attributes(product_id);
CREATE INDEX idx_product_attributes_key_value ON product_attributes(attribute_key, attribute_value);

-- Full-text search
CREATE INDEX idx_projects_search ON projects USING gin(to_tsvector('english', name || ' ' || COALESCE(description, '')));
CREATE INDEX idx_tasks_search ON tasks USING gin(to_tsvector('english', name || ' ' || COALESCE(description, '')));

API Design

RESTful Principles

  1. Resource-based URLs: /api/v1/projects/{uniqueCode}
  2. HTTP Methods: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)
  3. Status Codes: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 404 (Not Found)
  4. Consistent Response Format

Request/Response Design

// Standard Response Wrapper
type Response struct {
    Data    interface{} `json:"data,omitempty"`
    Message string      `json:"message,omitempty"`
    Error   *ErrorInfo  `json:"error,omitempty"`
    Meta    *Meta       `json:"meta,omitempty"`
}

type ErrorInfo struct {
    Code       string            `json:"code"`
    Message    string            `json:"message"`
    Details    map[string]string `json:"details,omitempty"`
    StatusCode int               `json:"statusCode"`
}

type Meta struct {
    Page       int   `json:"page"`
    Limit      int   `json:"limit"`
    Total      int64 `json:"total"`
    TotalPages int   `json:"totalPages"`
}

// Pagination
type PaginationParams struct {
    Page    int    `query:"page" default:"1"`
    Limit   int    `query:"limit" default:"10"`
    SortBy  string `query:"sortBy" default:"created_at"`
    SortDir string `query:"sortDir" default:"desc"`
}

API Versioning Strategy

// Version in URL path
app.Group("/api/v1", v1Routes)
app.Group("/api/v2", v2Routes)

// Version negotiation via headers
func VersionMiddleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        version := c.Get("API-Version", "v1")
        c.Locals("apiVersion", version)
        return c.Next()
    }
}

Event System Design

Event Architecture

// Event Interface
type Event interface {
    Type() string
    Timestamp() time.Time
    Payload() interface{}
    UserID() string
}

// Event Implementation
type TaskCreatedEvent struct {
    TaskID    string
    ProjectID string
    CreatorID string
    CreatedAt time.Time
}

func (e TaskCreatedEvent) Type() string { return "task.created" }
func (e TaskCreatedEvent) Timestamp() time.Time { return e.CreatedAt }
func (e TaskCreatedEvent) Payload() interface{} { return e }
func (e TaskCreatedEvent) UserID() string { return e.CreatorID }

// Event Bus
type EventBus struct {
    handlers map[string][]EventHandler
    mu       sync.RWMutex
}

func (eb *EventBus) Publish(event Event) {
    eb.mu.RLock()
    handlers := eb.handlers[event.Type()]
    eb.mu.RUnlock()

    for _, handler := range handlers {
        go handler(event) // Async processing
    }
}

Event Handlers

// Notification Handler
func NotificationHandler(event Event) {
    switch e := event.(type) {
    case TaskCreatedEvent:
        // Send notification to assignees
        notifyTaskAssignees(e.TaskID)
    case CommentAddedEvent:
        // Notify mentioned users
        notifyMentionedUsers(e.CommentID)
    }
}

// Audit Handler
func AuditHandler(event Event) {
    audit := &AuditLog{
        EventType: event.Type(),
        UserID:    event.UserID(),
        Timestamp: event.Timestamp(),
        Payload:   event.Payload(),
    }
    db.Create(audit)
}

Security Design

Authentication & Authorization

// JWT Claims
type Claims struct {
    UserID      string   `json:"userId"`
    Email       string   `json:"email"`
    Roles       []string `json:"roles"`
    Permissions []string `json:"permissions"`
    jwt.StandardClaims
}

// Permission Check
func RequirePermissions(permissions ...string) fiber.Handler {
    return func(c *fiber.Ctx) error {
        claims := c.Locals("claims").(*Claims)

        for _, required := range permissions {
            hasPermission := false
            for _, userPerm := range claims.Permissions {
                if userPerm == required {
                    hasPermission = true
                    break
                }
            }
            if !hasPermission {
                return fiber.NewError(fiber.StatusForbidden, "Insufficient permissions")
            }
        }

        return c.Next()
    }
}

Input Validation

// Validation Layer
type Validator struct {
    validate *validator.Validate
}

func (v *Validator) ValidateStruct(s interface{}) error {
    if err := v.validate.Struct(s); err != nil {
        return v.formatValidationError(err)
    }
    return nil
}

// DTO with Validation Tags
type CreateProjectDTO struct {
    Name          string    `json:"name" validate:"required,min=3,max=255"`
    CustomerID    string    `json:"customerId" validate:"required,ulid"`
    ContractValue int64     `json:"contractValue" validate:"min=0"`
    Currency      string    `json:"currency" validate:"required,iso4217"`
    ReplyDate     time.Time `json:"replyDate" validate:"required,gtefield=Now"`
}

SQL Injection Prevention

// Safe Query Building
func (r *projectRepository) FindByFilters(filters ProjectFilters) ([]*Project, error) {
    query := r.db.Model(&Project{})

    // Use parameterized queries
    if filters.Status != "" {
        query = query.Where("status = ?", filters.Status)
    }

    if filters.CustomerID != "" {
        query = query.Where("customer_id = ?", filters.CustomerID)
    }

    // Never concatenate user input
    // BAD: query.Where(fmt.Sprintf("status = '%s'", filters.Status))

    var projects []*Project
    return projects, query.Find(&projects).Error
}

Performance Design

Database Optimization

// Query Optimization
func (s *projectService) GetProjectWithDetails(uniqueCode string) (*Project, error) {
    var project Project

    // Preload associations efficiently
    err := s.db.
        Preload("Tasks", func(db *gorm.DB) *gorm.DB {
            return db.Where("deleted_at IS NULL").Order("priority DESC, created_at DESC")
        }).
        Preload("Products.Attributes").
        Preload("Participants.Employee").
        Where("unique_code = ?", uniqueCode).
        First(&project).Error

    return &project, err
}

// Batch Operations
func (s *taskService) CreateBulkTasks(tasks []*Task) error {
    return s.db.CreateInBatches(tasks, 100).Error
}

Caching Strategy

// Cache Layer
type CacheService interface {
    Get(key string, dest interface{}) error
    Set(key string, value interface{}, ttl time.Duration) error
    Delete(key string) error
}

// Cached Service
type CachedProjectService struct {
    service ProjectService
    cache   CacheService
}

func (s *CachedProjectService) GetProject(uniqueCode string) (*Project, error) {
    key := fmt.Sprintf("project:%s", uniqueCode)

    var project Project
    if err := s.cache.Get(key, &project); err == nil {
        return &project, nil
    }

    // Cache miss - fetch from database
    p, err := s.service.GetProject(uniqueCode)
    if err != nil {
        return nil, err
    }

    // Update cache
    s.cache.Set(key, p, 5*time.Minute)
    return p, nil
}

Connection Pooling

// Database Configuration
func ConfigureDatabase(cfg *Config) (*gorm.DB, error) {
    db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })

    if err != nil {
        return nil, err
    }

    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }

    // Connection pool settings
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)

    return db, nil
}

Error Handling Design

Error Types

// Custom Error Types
type AppError struct {
    Code       string
    Message    string
    StatusCode int
    Details    map[string]string
}

func (e AppError) Error() string {
    return e.Message
}

// Error Factory
var (
    ErrProjectNotFound = AppError{
        Code:       "PROJECT_NOT_FOUND",
        Message:    "Project not found",
        StatusCode: 404,
    }

    ErrInvalidInput = AppError{
        Code:       "INVALID_INPUT",
        Message:    "Invalid input provided",
        StatusCode: 400,
    }

    ErrUnauthorized = AppError{
        Code:       "UNAUTHORIZED",
        Message:    "Unauthorized access",
        StatusCode: 401,
    }
)

Error Handling Middleware

func ErrorHandler() fiber.Handler {
    return func(c *fiber.Ctx) error {
        err := c.Next()

        if err != nil {
            // Check if it's our custom error
            if appErr, ok := err.(AppError); ok {
                return c.Status(appErr.StatusCode).JSON(Response{
                    Error: &ErrorInfo{
                        Code:    appErr.Code,
                        Message: appErr.Message,
                        Details: appErr.Details,
                    },
                })
            }

            // Handle Fiber errors
            if fiberErr, ok := err.(*fiber.Error); ok {
                return c.Status(fiberErr.Code).JSON(Response{
                    Error: &ErrorInfo{
                        Code:    "FIBER_ERROR",
                        Message: fiberErr.Message,
                    },
                })
            }

            // Generic error
            return c.Status(500).JSON(Response{
                Error: &ErrorInfo{
                    Code:    "INTERNAL_ERROR",
                    Message: "An internal error occurred",
                },
            })
        }

        return nil
    }
}

Testing Strategy

Unit Testing

// Service Test
func TestProjectService_CreateProject(t *testing.T) {
    // Setup
    mockRepo := &MockProjectRepository{}
    mockEventBus := &MockEventBus{}
    service := NewProjectService(mockRepo, mockEventBus)

    // Test case
    dto := CreateProjectDTO{
        Name:       "Test Project",
        CustomerID: "customer123",
    }

    mockRepo.On("Create", mock.AnythingOfType("*models.Project")).Return(nil)
    mockEventBus.On("Publish", mock.AnythingOfType("ProjectCreatedEvent")).Return()

    // Execute
    project, err := service.CreateProject(dto)

    // Assert
    assert.NoError(t, err)
    assert.NotNil(t, project)
    assert.Equal(t, "Test Project", project.Name)
    mockRepo.AssertExpectations(t)
    mockEventBus.AssertExpectations(t)
}

Integration Testing

// API Integration Test
func TestProjectAPI_CreateProject(t *testing.T) {
    // Setup test database
    db := setupTestDB(t)
    defer cleanupTestDB(db)

    // Create test server
    app := setupTestApp(db)

    // Prepare request
    payload := map[string]interface{}{
        "name":          "Integration Test Project",
        "customerId":    "customer123",
        "contractValue": 1000000,
    }

    // Execute request
    req := httptest.NewRequest("POST", "/api/v1/projects", jsonBody(payload))
    req.Header.Set("Authorization", "Bearer " + testToken)
    resp, _ := app.Test(req)

    // Assert
    assert.Equal(t, 201, resp.StatusCode)

    var result Response
    json.NewDecoder(resp.Body).Decode(&result)
    assert.NotNil(t, result.Data)
}

Test Coverage Requirements

  • Unit Tests: > 80% coverage
  • Integration Tests: Critical paths covered
  • E2E Tests: Main user workflows
  • Performance Tests: Load testing for APIs
  • Security Tests: Penetration testing

Conclusion

This technical design document provides a comprehensive blueprint for implementing the MS-Project system. The design emphasizes:

  1. Modularity: Clear separation of concerns
  2. Scalability: Patterns that support growth
  3. Maintainability: Clean, testable code
  4. Security: Defense in depth approach
  5. Performance: Optimized at every layer

The implementation follows industry best practices and proven design patterns, ensuring a robust and extensible system that can evolve with business needs.