Skip to content
bitzorcas
中EN

Concept

Ticketing Module Architecture: Collaboration, SLA & Events

Explore the BitzOrcas.Modern Ticketing Subsystem. Learn Ticket aggregate roots, SLA escalation state machines, optimistic concurrency assignments, and read model projections.

Last updated

In enterprise customer support and internal IT operations, the Ticketing Subsystem addresses intricate lifecycle management and contractual SLA agreements:

  • SLA Breach Prevention: Critical P0 incidents must trigger automated escalation workflows and multi-channel alarms if unacknowledged within 15 minutes;
  • Concurrent Assignment Conflicts: Multiple agents clicking “Claim Ticket” concurrently must not corrupt ticket ownership;
  • Strict Multi-Tenant Boundaries: Tenant A’s internal diagnostic attachments must never be accessible across tenant perimeters.

BitzOrcas.Modern Ticketing Module combines “Unified Aggregate Golden Standard + SLA State Machines + Event-Driven Alerts”:

Ticket Lifecycle and SLA Escalation Topology

Unacknowledged Timeout

1. Customer Submits Ticket (CreateTicketCommand)

2. Vertical Slice Handler

3. Ticket Aggregate Root (State: Open, Computes SLA Deadline)

4. IAggregateRepository (Transactional Persistence)

5. CAP Domain Events (ticket.created)

6. SLA Monitor Scheduler (Quartz Registers Timeout Check)

7. NotificationConsumer (Dispatches Agent Task)

8. EscalateTicketCommand (Auto-Escalates to P0 & Alerts Manager)


Step 1: Core Domain Entity Ticket

Ticket.cs: Business Ticket Aggregate Root
using System;
using System.ComponentModel;
using BitzOrcas.Domain.Entities;
using BitzOrcas.Domain.Results;
using BitzOrcas.Persistence.Metadata;
namespace BitzOrcas.Ticket.Domain;
public static class TicketErrors
{
public static readonly Error AlreadyClosed =
Error.Conflict("Ticket.AlreadyClosed", "Closed tickets cannot be assigned.");
public static readonly Error NotFound =
Error.NotFound("Ticket.NotFound", "Ticket not found.");
}
[BitzTable("SysTicket", IsTenant = true, IsSoftDelete = true, Description = "Business Tickets Table")]
[BitzIndex("IX_SysTicket_Status_Tenant", nameof(Status), nameof(TenantId))]
public sealed class Ticket : TenantAggregateRoot<string>
{
[BitzColumn(Length = 200, IsRequired = true)]
public string Title { get; private set; } = string.Empty;
[BitzColumn(Length = 4000, IsRequired = true)]
public string Description { get; private set; } = string.Empty;
[BitzColumn(IsRequired = true)]
public TicketPriority Priority { get; private set; } = TicketPriority.Medium;
[BitzColumn(IsRequired = true)]
public TicketStatus Status { get; private set; } = TicketStatus.Open;
[BitzColumn(Length = 64)]
public string? AssigneeUserId { get; private set; }
[BitzColumn(IsRequired = true)]
public DateTimeOffset SlaResponseDueAt { get; private set; }
[Obsolete("For ORM materialization only. Use Create.", error: true)]
[EditorBrowsable(EditorBrowsableState.Never)]
public Ticket()
: base("0")
{
}
// Domain method: assigns ticket to engineer and sets in-progress state
public Result AssignTo(string engineerUserId, string operatorId, DateTimeOffset now)
{
if (Status == TicketStatus.Closed || Status == TicketStatus.Resolved)
{
return Result.Failure(TicketErrors.AlreadyClosed);
}
AssigneeUserId = engineerUserId;
Status = TicketStatus.InProgress;
// Emit domain event
AddDomainEvent(new TicketAssignedDomainEvent(Id, TenantId, engineerUserId, operatorId, now));
return Result.Success();
}
}

Step 2: Assign Ticket Command Slice

AssignTicketCommandHandler.cs: Assignment Slice
using System.Threading;
using System.Threading.Tasks;
using BitzOrcas.Application.Abstractions.Security;
using BitzOrcas.Domain.Abstractions;
using BitzOrcas.Domain.Results;
using BitzOrcas.Ticket.Domain;
public sealed class AssignTicketCommandHandler(
ICommandRepository<Ticket, string> ticketRepo,
ICurrentUser currentUser,
IAppClock clock)
{
public async ValueTask<Result> Handle(AssignTicketCommand command, CancellationToken ct)
{
// 1. Load target ticket aggregate
var ticketResult = await ticketRepo.FindAsync(command.TicketId, ct);
if (ticketResult.IsFailure)
{
return Result.Failure(TicketErrors.NotFound);
}
var ticket = ticketResult.Value;
// 2. Execute domain assignment
var assignResult = ticket.AssignTo(command.AssigneeUserId, currentUser.UserId, clock.UtcNow);
if (assignResult.IsFailure) return assignResult;
// 3. Persist aggregate (auto-validates optimistic concurrency version)
await ticketRepo.UpdateAsync(ticket, ct);
return Result.Success();
}
}

Summary

The Ticketing Module supercharges operations:

  • Optimistic Concurrency: Eliminates assignment race conditions;
  • Automated SLA Lifecycle: Point-in-time clocks trigger automated escalations;
  • Event-Driven Integration: Real-time notifications keep agents and customers aligned.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%