Architecture Layers

┌─────────────────────────────────────────────────────┐ │ Arthix.API (Presentation) │ │ Controllers → HandleResult() → HTTP Response │ ├─────────────────────────────────────────────────────┤ │ Arthix.Application (Business Logic) │ │ Service Interfaces │ DTOs │ Validators │ ├─────────────────────────────────────────────────────┤ │ Arthix.Infrastructure (Implementations) │ │ EF Core │ Azure │ RabbitMQ │ SignalR │ Meta API │ ├─────────────────────────────────────────────────────┤ │ Arthix.Domain (Core) │ │ BaseEntity │ IRepository │ IUnitOfWork │ Entities │ ├─────────────────────────────────────────────────────┤ │ Arthix.Shared (Cross-Cutting) │ │ Result Pattern │ Enums │ DTOs │ Configuration │ ├─────────────────────────────────────────────────────┤ │ Arthix.Worker (Background) │ │ RabbitMQ Consumer │ Scheduled Actions │ SignalR │ └─────────────────────────────────────────────────────┘

Dependency Flow: API → Application → Domain ← Infrastructure ← Worker, Shared used by all.

BaseEntity

C#
public abstract class BaseEntity : ISoftDeletable
{
    [Key] public Guid Id { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime? UpdatedAt { get; set; }
    public Guid? CreatedBy { get; set; }
    public Guid? UpdatedBy { get; set; }
    public bool IsDeleted { get; set; } = false;
}

Note: EF Core global query filters auto-exclude IsDeleted = true rows.

Result Pattern

ResultTypeHTTPWhen To Use
Success200Operation succeeded
Failure500Unexpected internal error
ValidationError400Input validation failed
NotFound404Entity not in database
Unauthorized401Auth/permission failure
BadRequest400Malformed request data
NotModified304No changes applied
C#
// Service usage:
return Result.Success(myDto, "Created successfully");
return Result.NotFound<MyDto>("Contact not found");
return Result.ValidationError<MyDto>(new List<string> { "Email is required" });

// Controller:
var result = await _service.DoSomethingAsync(request, ct);
return HandleResult(result);

IRepository & IUnitOfWork Interfaces

C#
public interface IRepository<T> where T : BaseEntity
{
    Task<T?> GetByIdAsync(Guid id, CancellationToken ct);
    Task<IEnumerable<T>> GetAllAsync(CancellationToken ct);
    Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate, CancellationToken ct);
    Task<T> AddAsync(T entity, CancellationToken ct);
    Task UpdateAsync(T entity, CancellationToken ct);
    Task DeleteAsync(Guid id, CancellationToken ct); // Soft delete
    Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate, CancellationToken ct);
}

public interface IUnitOfWork : IDisposable
{
    Task<int> SaveChangesAsync(CancellationToken ct);
    Task BeginTransactionAsync(CancellationToken ct);
    Task CommitTransactionAsync(CancellationToken ct);
    Task RollbackTransactionAsync(CancellationToken ct);
}

DbContext — DbSets

DbSetEntity GroupModule
Firms, FirmAddresses, FirmSettingsFirm entitiesFirm
FirmUsers, FirmUserRelations, FirmUserRefreshTokensAuth entitiesAuth/User
FirmContacts, FirmContactPhones, FirmContactEmails...Contact entitiesContact
MetaWhatsappConfigurations, MetaWebhooks, MetaObjectLogsMeta configurationWhatsApp
WhatsappTemplates, WhatsappTemplateComponents...TemplatesWhatsApp
WhatsappGroups, WhatsappMessages, WhatsappMessageObjsMessagingWhatsApp
Workflows, WorkflowNodes, WorkflowEdges...Workflow entitiesWorkflow
NotificationsNotificationsNotifications

JSONB Columns (PostgreSQL):

  • WorkflowNode.Configuration — Node-specific settings
  • WorkflowExecution.WorkflowJson — Snapshot at execution time
  • WorkflowNodeExecution.InputData, OutputData, ErrorData — Node I/O
  • Notification.Metadata — Per-module extra data

Migration Commands

Bash
# Create migration
dotnet ef migrations add MigrationName --project src/Arthix.Infrastructure --startup-project src/Arthix.API

# Apply migration
dotnet ef database update --project src/Arthix.Infrastructure --startup-project src/Arthix.API