Skip to content

Ticket Masala: Architecture Review & Improvement Recommendations

Date: January 2025
Reviewer: Architecture Analysis
Purpose: Current state assessment and recommendations for architectural improvements


Executive Summary

Ticket Masala is a well-structured modular monolith with strong domain-agnostic foundations. The architecture demonstrates good separation of concerns, extensibility patterns, and multi-tenancy support. This review identifies areas for improvement to enhance maintainability, flexibility, and integration capabilities.

Key Findings: - Strong modular structure with clear boundaries - Good use of dependency injection and extension methods - Multi-tenant support via Docker volumes - Inconsistent naming conventions (Ticket vs WorkItem) - Tight coupling to SQLite-specific features - Missing standardized plugin interface alignment - Configuration management could be more flexible


1. Current Architecture Analysis

1.1 Architecture Pattern

Current: Modular Monolith with Plugin System

┌─────────────────────────────────────────┐
│         TicketMasala.Web                │
│  ┌───────────────────────────────────┐  │
│  │  Controllers (MVC + API)          │  │
│  └──────────────┬────────────────────┘  │
│                 │                        │
│  ┌──────────────▼────────────────────┐  │
│  │  Engine/ (Business Logic)        │  │
│  │  ├── Core/                        │  │
│  │  ├── GERDA/ (AI Services)         │  │
│  │  ├── Compiler/ (Rule Engine)      │  │
│  │  └── Ingestion/                   │  │
│  └──────────────┬────────────────────┘  │
│                 │                        │
│  ┌──────────────▼────────────────────┐  │
│  │  Repositories/ (Data Access)      │  │
│  └──────────────┬────────────────────┘  │
│                 │                        │
│  ┌──────────────▼────────────────────┐  │
│  │  Data/ (EF Core DbContext)        │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Strengths: - Clear separation between presentation, business logic, and data access - Well-organized Engine modules with single responsibilities - Repository pattern provides testability and abstraction

Weaknesses: - No clear domain boundaries (everything in one project) - Missing shared domain models project (domain models embedded in Web project) - Controllers mix MVC and API concerns

1.2 Dependency Injection & Service Registration

Current Pattern:

// Extension methods organize service registration
builder.Services.AddMasalaDatabase(...);
builder.Services.AddCoreServices();
builder.Services.AddGerdaServices(...);

Assessment: Excellent - Clean, discoverable extension methods - Follows ASP.NET Core best practices - Easy to test and mock

Recommendation: Consider consolidating into fewer extension methods for consistency, following established ASP.NET Core patterns.

1.3 Multi-Tenancy Architecture

Current Implementation: - Tenant Isolation: Docker volumes per tenant (tenants/{tenant}/config, tenants/{tenant}/data) - Configuration: Environment variable MASALA_CONFIG_PATH + TenantConnectionResolver - Plugin System: TenantPluginLoader loads external DLLs

Strengths: - Clear tenant separation via volumes - Each tenant has isolated database - Plugin system allows tenant-specific extensions

Weaknesses: - No runtime tenant resolution (requires separate containers) - Plugin interface could be standardized for better interoperability - No shared tenant registry/metadata

Recommendation: Consider supporting both patterns: 1. Current (Container-per-tenant): Good for strict isolation 2. Runtime resolution: Better for shared infrastructure and resource efficiency

1.4 Data Model & Persistence

Current: - EF Core with SQLite (primary) or SQL Server - Repository + Unit of Work pattern - Specification pattern for queries

Strengths: - Repository abstraction enables testing - Specification pattern provides reusable queries - JSON columns (CustomFieldsJson) for flexibility

Critical Issues:

Issue 1: SQLite-Specific Computed Columns

// MasalaDbContext.cs - Line 55-66
entity.Property(e => e.ComputedPriority)
      .HasComputedColumnSql("json_extract(CustomFieldsJson, '$.priority_score')", stored: true);

Problem: This SQLite-specific syntax won't work with SQL Server or PostgreSQL.

Recommendation: Use EF Core's database-agnostic approach:

// Option 1: Database-agnostic computed column
entity.Property(e => e.ComputedPriority)
      .HasComputedColumnSql(
          "CASE " +
          "WHEN 'SQLite' THEN json_extract(CustomFieldsJson, '$.priority_score') " +
          "WHEN 'SqlServer' THEN JSON_VALUE(CustomFieldsJson, '$.priority_score') " +
          "ELSE NULL END",
          stored: true);

// Option 2: Use EF Core's HasComputedColumnSql with provider-specific SQL
// Better: Use a database provider factory pattern

Issue 2: Missing Domain Abstraction

The Ticket model mixes domain concepts: - Core fields (Title, Description, Status) - Domain-specific fields (DomainId, WorkItemTypeCode) - JSON blobs (CustomFieldsJson, DomainCustomFieldsJson)

Recommendation: Extract to shared domain project:

src/
├── TicketMasala.Domain/          # NEW: Shared domain models
│   ├── Entities/
│   │   ├── WorkItem.cs           # Rename from Ticket
│   │   ├── WorkContainer.cs      # Rename from Project
│   │   └── WorkHandler.cs        # Rename from ApplicationUser
│   └── ValueObjects/
└── TicketMasala.Web/             # Presentation & Infrastructure

1.5 Configuration Management

Current: - YAML-based domain configuration (masala_domains.yaml) - JSON feature flags (masala_config.json) - Environment variable resolution (MASALA_CONFIG_PATH)

Strengths: - Flexible YAML-based domain rules - Clear configuration path resolution

Weaknesses: - No configuration validation on startup - No hot-reload support (requires app restart) - Configuration scattered across multiple files

Recommendation: Use strongly-typed configuration options:

// Strongly-typed options pattern
public class MasalaOptions
{
    public string ConfigPath { get; set; }
    public DatabaseOptions Database { get; set; }
    public GerdaOptions Gerda { get; set; }
}

// In Program.cs
builder.Services.Configure<MasalaOptions>(builder.Configuration.GetSection("Masala"));

1.6 GERDA AI Architecture

Current Structure:

Engine/GERDA/
├── Dispatching/      # Agent assignment
├── Estimating/       # Effort estimation
├── Ranking/          # Priority scoring
├── Grouping/         # Spam detection
└── Anticipation/     # Forecasting

Strengths: - Clear separation of AI concerns - Strategy pattern for swappable algorithms - Background processing via GerdaBackgroundService

Weaknesses: - ML.NET models embedded in application - No model versioning or A/B testing - No metrics/observability for AI decisions

Recommendation: Consider extracting AI services to a separate project for better separation of concerns:

src/
├── TicketMasala.Web/           # Main application (Operations)
└── TicketMasala.AI/            # AI/ML services (Optional)
    └── Gerda/


2. Integration Challenges & Solutions

2.1 Naming Convention Mismatch

Problem: Ticket Masala uses "Ticket" internally but documents "WorkItem" in API.

Current State: - Internal code: Ticket, Project, ApplicationUser - API documentation: WorkItem, WorkContainer, WorkHandler - Configuration: Mixed terminology

Impact: Confusing for developers and API consumers due to inconsistent terminology.

Recommendation: 1. Phase 1: Add aliases in API layer

[Route("api/v1/[controller]")]
[ApiController]
public class WorkItemsController : ControllerBase
{
    private readonly ITicketService _ticketService;

    [HttpGet]
    public async Task<IActionResult> GetWorkItems()
    {
        var tickets = await _ticketService.GetAllTicketsAsync();
        return Ok(tickets.Select(t => MapToWorkItemDto(t)));
    }
}

  1. Phase 2: Gradually migrate internal code to domain-agnostic terms
  2. Create WorkItem as alias for Ticket (via inheritance or composition)
  3. Update new code to use WorkItem
  4. Deprecate Ticket gradually

2.2 Plugin Interface Alignment

Problem: Plugin interface could be standardized for better interoperability with other plugin-based systems.

Current:

public interface ITenantPlugin
{
    string TenantId { get; }
    string DisplayName { get; }
    void ConfigureServices(IServiceCollection services, IConfiguration configuration);
    void ConfigureMiddleware(IApplicationBuilder app, IWebHostEnvironment env);
}

Recommendation: Consider creating a standardized plugin interface that can be adapted to various plugin ecosystems:

public interface IStandardPlugin
{
    string PluginId { get; }
    string DisplayName { get; }
    void ConfigureServices(IServiceCollection services, IConfiguration configuration);
}

Create adapter pattern to bridge between interfaces as needed.

2.3 Database Provider Abstraction

Problem: SQLite-specific features prevent easy migration to SQL Server/PostgreSQL.

Current Issues: 1. Computed columns use SQLite syntax 2. JSON extraction functions are SQLite-specific 3. WAL mode configuration hardcoded

Recommendation: Use EF Core's database provider abstraction:

public static class DatabaseExtensions
{
    public static void ConfigureComputedColumns(this ModelBuilder modelBuilder, string provider)
    {
        var ticketEntity = modelBuilder.Entity<Ticket>();

        if (provider == "SQLite")
        {
            ticketEntity.Property(e => e.ComputedPriority)
                .HasComputedColumnSql("json_extract(CustomFieldsJson, '$.priority_score')", stored: true);
        }
        else if (provider == "SqlServer")
        {
            ticketEntity.Property(e => e.ComputedPriority)
                .HasComputedColumnSql("JSON_VALUE(CustomFieldsJson, '$.priority_score')", stored: true);
        }
        else if (provider == "PostgreSQL")
        {
            ticketEntity.Property(e => e.ComputedPriority)
                .HasComputedColumnSql("(CustomFieldsJson->>'priority_score')::float", stored: true);
        }
    }
}

2.4 Shared Domain Models

Problem: No shared domain project for cross-module integration.

Current: All models in TicketMasala.Web/Models/

Recommendation: Extract to TicketMasala.Domain/:

src/
├── TicketMasala.Domain/              # NEW
│   ├── Entities/
│   │   ├── WorkItem.cs
│   │   ├── WorkContainer.cs
│   │   └── WorkHandler.cs
│   ├── ValueObjects/
│   │   ├── Status.cs
│   │   └── Priority.cs
│   ├── Interfaces/
│   │   ├── IWorkItemRepository.cs
│   │   └── IWorkItemService.cs
│   └── MasalaDbContext.cs           # Move from Web project
├── TicketMasala.Web/                 # Presentation only
│   ├── Controllers/
│   ├── Views/
│   └── ViewModels/                   # DTOs for presentation
└── TicketMasala.Infrastructure/      # NEW: Data access
    ├── Repositories/
    └── Data/

Benefits: - Domain models reusable across modules - Clear separation of concerns - Easier integration with other systems and modules


3. Specific Improvement Recommendations

3.1 High Priority (Integration Blockers)

3.1.1 Extract Domain Models

Action: Create TicketMasala.Domain project - Move Models/ to Domain/Entities/ - Move MasalaDbContext to Domain/ - Update namespaces

Impact: Enables shared domain models across modules and systems

3.1.2 Standardize Plugin Interface

Action: Create standardized plugin interface - Create compatibility layer/adapter if needed - Update plugin loader

Impact: Enables plugin interoperability with other plugin-based systems

3.1.3 Database Provider Abstraction

Action: Remove SQLite-specific code - Abstract computed column SQL - Use EF Core provider detection - Test with SQL Server

Impact: Enables deployment flexibility

3.2 Medium Priority (Architecture Improvements)

3.2.1 Configuration Validation

Action: Add startup validation

public class MasalaConfigurationValidator
{
    public void Validate(MasalaOptions options)
    {
        if (!Directory.Exists(options.ConfigPath))
            throw new InvalidOperationException($"Config path not found: {options.ConfigPath}");

        // Validate YAML syntax
        // Validate required domains exist
    }
}

3.2.2 Hot Reload Support

Action: Add configuration change detection

public class ConfigurationWatcher : IHostedService
{
    private readonly FileSystemWatcher _watcher;
    private readonly IDomainConfigurationService _configService;

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _watcher.Changed += async (sender, e) =>
        {
            await _configService.ReloadConfigurationAsync();
        };
    }
}

3.2.3 API Versioning

Action: Add API versioning support

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
});

3.3 Low Priority (Nice to Have)

3.3.1 Observability

  • Add OpenTelemetry instrumentation
  • Add structured logging with Serilog
  • Add metrics endpoints

3.3.2 Testing Infrastructure

  • Add integration test base classes
  • Add test data builders
  • Add API contract testing

3.3.3 Documentation

  • Generate API documentation (Swagger/OpenAPI)
  • Add architecture decision records (ADRs)
  • Add developer onboarding guide

4. Integration Roadmap

Phase 1: Foundation (Weeks 1-2)

  • Extract TicketMasala.Domain project
  • Standardize plugin interface (IStandardPlugin)
  • Abstract database provider code
  • Add configuration validation

Phase 2: Alignment (Weeks 3-4)

  • Align naming conventions (WorkItem terminology)
  • Add API versioning
  • Create shared domain abstractions
  • Update documentation

Phase 3: Integration & Testing (Weeks 5-6)

  • Create integration examples and documentation
  • Test multi-tenant scenarios
  • Performance testing
  • Security audit

Phase 4: Enhancement (Ongoing)

  • Add observability
  • Improve test coverage
  • Optimize performance
  • Add advanced features

5. Code Quality Assessment

5.1 Strengths

  • Clean Architecture: Clear separation of concerns
  • Dependency Injection: Well-structured service registration
  • Repository Pattern: Good data access abstraction
  • Extension Methods: Discoverable configuration
  • Observer Pattern: Decoupled event handling

5.2 Areas for Improvement

  • Test Coverage: Limited integration tests
  • Error Handling: Inconsistent exception handling
  • Logging: Basic logging, needs structured logging
  • Documentation: API documentation incomplete
  • Performance: No caching strategy documented

5.3 Technical Debt

  1. SQLite Dependency: Computed columns need abstraction
  2. Naming Inconsistency: Ticket vs WorkItem confusion
  3. Configuration: No validation or hot-reload
  4. Plugin System: Could benefit from standardization
  5. Domain Models: Embedded in Web project

6. Recommendations Summary

Critical (Must Fix)

  1. Extract domain models to separate project
  2. Abstract database provider code
  3. Standardize plugin interface

Important (Should Fix)

  1. Add configuration validation
  2. Align naming conventions
  3. Add API versioning

Nice to Have

  1. Add observability
  2. Improve test coverage
  3. Add hot-reload support

7. Conclusion

Ticket Masala has a solid architectural foundation with good separation of concerns and extensibility patterns. The main integration challenges are:

  1. Naming inconsistencies (Ticket vs WorkItem)
  2. Database provider coupling (SQLite-specific code)
  3. Plugin interface standardization (for better interoperability)
  4. Missing domain abstraction (models embedded in Web project)

With the recommended improvements, Ticket Masala will be more maintainable, flexible, and ready for integration with various systems while maintaining its domain-agnostic, multi-tenant architecture.

Next Steps: 1. Review this document with the team 2. Prioritize improvements based on integration timeline 3. Create GitHub issues for each recommendation 4. Begin Phase 1 implementation


Document Version: 1.0
Last Updated: January 2025