Technical Design Document
Table of Contents
- System Design Overview
- Core Design Patterns
- Module Specifications
- Data Model Design
- API Design
- Event System Design
- Security Design
- Performance Design
- Error Handling Design
- Testing Strategy
System Design Overview
Design Goals
- Scalability: Support 100,000+ concurrent operations
- Maintainability: Clean, modular code structure
- Reliability: 99.9% uptime with graceful degradation
- Flexibility: Adapt to changing business requirements
- 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 accessInterfaces
// 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 accessTask 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
- Resource-based URLs:
/api/v1/projects/{uniqueCode} - HTTP Methods: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)
- Status Codes: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 404 (Not Found)
- 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:
- Modularity: Clear separation of concerns
- Scalability: Patterns that support growth
- Maintainability: Clean, testable code
- Security: Defense in depth approach
- 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.