Skip to content

Architecture.md

Codewriter90x edited this page Jan 24, 2026 · 1 revision

Architecture

This document describes the high-level architecture, design philosophy, and structural decisions of OpenCashFlow.

Design Philosophy

OpenCashFlow follows Clean Architecture principles with these goals:

  1. Separation of Concerns - Each layer has a single responsibility
  2. Dependency Inversion - High-level modules don't depend on low-level modules
  3. Testability - Business logic is isolated from infrastructure
  4. Maintainability - Changes in one layer don't cascade to others

System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Presentation Layer                        │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │  OpenCashFlow   │  │  OpenCashFlow   │  │  OpenCashFlow   │  │
│  │      .App       │  │      .API       │  │     .Admin      │  │
│  │   (MVC + UI)    │  │   (REST API)    │  │  (Admin Panel)  │  │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘  │
└───────────┼────────────────────┼────────────────────┼───────────┘
            │                    │                    │
            └────────────────────┼────────────────────┘
                                 │
┌────────────────────────────────┼────────────────────────────────┐
│                         Shared Layer                             │
│  ┌─────────────────────────────┴─────────────────────────────┐  │
│  │                   OpenCashFlow.Shared                      │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐  │  │
│  │  │  Models  │ │   DTOs   │ │ Services │ │ Repositories │  │  │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────────┘  │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐                   │  │
│  │  │  Enums   │ │ Mappings │ │ DbContext│                   │  │
│  │  └──────────┘ └──────────┘ └──────────┘                   │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────┬───────────────────────────────┘
                                  │
┌─────────────────────────────────┼───────────────────────────────┐
│                         Data Layer                               │
│  ┌──────────────────────────────┴────────────────────────────┐  │
│  │                      PostgreSQL 16                         │  │
│  └────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Layer Responsibilities

Presentation Layer

The presentation layer contains three separate applications:

Project Responsibility
OpenCashFlow.App User-facing MVC application with Razor views, handles UI rendering and user authentication flows
OpenCashFlow.API RESTful API for data operations, consumed by the App via AJAX and by external integrations
OpenCashFlow.Admin Administrative dashboard for system management and monitoring

Shared Layer

The shared library (OpenCashFlow.Shared) contains:

  • Models (Entities) - Database entity definitions with EF Core mappings
  • DTOs - Data Transfer Objects for API contracts
  • Services - Business logic implementations
  • Repositories - Data access abstractions
  • Enums - Shared enumerations (permissions, statuses, types)
  • Mappings - AutoMapper profiles for entity-DTO conversion
  • DbContext - Entity Framework Core database context

Data Layer

PostgreSQL database with:

  • Multi-tenant data isolation via TenantID
  • Audit trail fields on all entities
  • Soft delete support
  • Optimistic concurrency where needed

Key Architectural Patterns

Multi-Tenancy

Every business entity includes a TenantID field that represents the owning company:

public class Payment
{
    public Guid PaymentID { get; set; }
    public Guid TenantID { get; set; }  // Company identifier
    public decimal Amount { get; set; }
    // ... other fields
}

All queries filter by TenantID to ensure data isolation between companies.

Repository Pattern

Data access is abstracted through repository interfaces:

// Interface definition
public interface IPaymentRepository
{
    Task<IEnumerable<Payment>> GetAllAsync(Guid tenantId);
    Task<Payment?> GetByIdAsync(Guid paymentId);
    Task<Payment> CreateAsync(Payment payment);
    Task UpdateAsync(Payment payment);
    Task DeleteAsync(Guid paymentId);
}

// Controller usage
public class PaymentController : ControllerBase
{
    private readonly IPaymentRepository _paymentRepository;

    public PaymentController(IPaymentRepository paymentRepository)
    {
        _paymentRepository = paymentRepository;
    }
}

DTO Pattern

DTOs separate API contracts from internal entities:

// Entity (internal)
public class Payment
{
    public Guid PaymentID { get; set; }
    public Guid TenantID { get; set; }
    public decimal Amount { get; set; }
    public string CreatedBy { get; set; }
    public DateTime DateIns { get; set; }
    // ... audit fields
}

// DTO (external contract)
public class Payment_List_DTO
{
    public Guid PaymentID { get; set; }
    public decimal Amount { get; set; }
    public string EntryType { get; set; }
    public DateTime PaymentDate { get; set; }
}

Partial Classes

Controllers and repositories are split using partial classes for maintainability:

Controllers/
  PaymentController.cs           # Main controller definition
  PaymentController.Create.cs    # Create payment logic
  PaymentController.Update.cs    # Update payment logic
  PaymentController.Delete.cs    # Delete payment logic

AppStart Extension Methods

Startup configuration is organized into chainable extension methods:

// Program.cs
builder.AppStartConfigureLogging();
builder.AppStartConfigureServices();
builder.AppStartConfigureAuthentication();

// Extension method files
// AppStart/00_Logging.cs
// AppStart/01_Configuration.cs
// AppStart/02_Authentication.cs

Request Flow

API Request Flow

HTTP Request
    │
    ▼
┌─────────────────────┐
│    Middleware       │
│  - Authentication   │
│  - Authorization    │
│  - Subscription     │
│  - Error Handling   │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│    Controller       │
│  - Route handling   │
│  - Input validation │
│  - Response mapping │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│    Service          │
│  - Business logic   │
│  - Validation rules │
│  - Orchestration    │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│    Repository       │
│  - Data access      │
│  - Query building   │
│  - CRUD operations  │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│    DbContext        │
│  - EF Core          │
│  - Change tracking  │
│  - Migrations       │
└──────────┬──────────┘
           │
           ▼
      PostgreSQL

MVC App Request Flow

HTTP Request
    │
    ▼
┌─────────────────────┐
│    MVC Controller   │
│  - Authentication   │
│  - View selection   │
└──────────┬──────────┘
           │
           ├─────────────────┐
           │                 │
           ▼                 ▼
    ┌─────────────┐   ┌─────────────┐
    │ Razor View  │   │ AJAX Call   │
    │ (server)    │   │ to API      │
    └─────────────┘   └──────┬──────┘
                             │
                             ▼
                      ┌─────────────┐
                      │ OpenCash    │
                      │ Flow.API    │
                      └─────────────┘

Audit Trail

All entities inherit audit fields for compliance and debugging:

Field Purpose
CreatedBy User who created the record
DateIns Creation timestamp
EditedBy User who last modified the record
DateEdit Last modification timestamp
IsDeleted Soft delete flag
IsDeletedBy User who deleted the record
IsDeletedWhy Reason for deletion
DateDeleted Deletion timestamp

API Response Wrapper

All API responses use a consistent wrapper:

public class ApiResponse<T>
{
    public bool Success { get; set; }
    public string Message { get; set; }
    public T Data { get; set; }
    public List<string> Errors { get; set; }
}

Example responses:

// Success
{
  "success": true,
  "message": "Payment created successfully",
  "data": { "paymentId": "..." },
  "errors": []
}

// Error
{
  "success": false,
  "message": "Validation failed",
  "data": null,
  "errors": ["Amount must be greater than zero"]
}

Dependency Injection

Services are registered in Program.cs with appropriate lifetimes:

// Scoped (per-request)
builder.Services.AddScoped<IPaymentRepository, PaymentRepository>();
builder.Services.AddScoped<IPaymentService, PaymentService>();

// Singleton (application lifetime)
builder.Services.AddSingleton<IEmailService, EmailService>();

// Transient (new instance each time)
builder.Services.AddTransient<IValidator<PaymentDto>, PaymentValidator>();

External Integrations

Service Purpose
Stripe Subscription billing, payment processing, webhooks
Sentry Error tracking and monitoring
Slack Log notifications (via Serilog)
SMTP Email delivery

OpenCashFlow

Preview Status

  • Developer Preview
  • Not production-ready
  • First-run setup included

Clone this wiki locally