12 - How-To Guides

Recipes for common backend dev tasks.

How to Add a New Controller

  1. Create a file in backend/src/MO.Ekyc.Api/Controllers/StageN/ (or Common/, Admin/)
  2. Decorate with [ApiController], [Route(...)] and optionally [RequiresStage(N)]
  3. Inject the service you need via constructor
  4. Delegate to the service - keep the action thin
  5. Wrap the result in ApiResponse<T> with a JourneyStatus
  6. Add a route constant to MO.Ekyc.Shared/Constants/ApiRoutes.cs
backend/src/MO.Ekyc.Api/Controllers/StageN/MyController.csC#
[ApiController]
[RequiresStage(5)]
public class MyController : ControllerBase
{
    private readonly MyService _service;
    public MyController(MyService service) { _service = service; }

    [HttpPost(ApiRoutes.MyEndpoint)]
    public async Task<IActionResult> DoSomething([FromBody] MyCommand cmd, CancellationToken ct)
    {
        var result = await _service.ExecuteAsync(cmd, ct);
        return Ok(ApiResponse<MyResult>.Ok(result));
    }
}

How to Add a New Service

  1. Create a file in backend/src/MO.Ekyc.Infrastructure/Services/StageN/
  2. Inject dependencies: EkycDbContext, ILogger<T>, IJourneyTracker, configuration
  3. Write methods that take Command DTOs and return Result DTOs
  4. Use _db.SaveChangesAsync(ct) to persist
  5. Call _journeyTracker.RecordStateTransitionAsync for state changes
  6. Register in Program.cs: builder.Services.AddScoped<MyService>();

How to Add a New External Provider

  1. Define or locate the interface (e.g., IPanProvider) in Infrastructure/ExternalProviders/Pan/
  2. Create the provider class implementing that interface + IExternalProvider
  3. Inject IHttpClientFactory, IConfiguration, ILogger<T>, ApiAuditLogger
  4. Add a named HttpClient in Program.cs: builder.Services.AddHttpClient("NewProvider", c => { c.BaseAddress = ...; c.Timeout = ...; });
  5. Register the provider: builder.Services.AddScoped<IPanProvider, NewProvider>();
  6. Insert a row into provider_configurations with category=PAN, priority, enabled=true, mock_mode
  7. Log every call via _auditLogger.LogAsync(...) for audit trail

How to Add a New Domain Entity

  1. Create a POCO in backend/src/MO.Ekyc.Domain/Entities/MyEntity.cs - no dependencies, just fields
  2. Add a DbSet<MyEntity> to EkycDbContext
  3. Create backend/src/MO.Ekyc.Infrastructure/Persistence/Configurations/MyEntityConfiguration.cs implementing IEntityTypeConfiguration<MyEntity> for indexes, foreign keys, column types
  4. Run a migration: dotnet ef migrations add AddMyEntity -p MO.Ekyc.Infrastructure -s MO.Ekyc.Api
  5. Apply: dotnet ef database update

How to Create an EF Core Migration

BashMigration commands
# Install EF tools if not already
dotnet tool install --global dotnet-ef --version 8.0.*

# Add a new migration
cd D:\MO_Project\ekyc\backend
dotnet ef migrations add InitialCreate \
    --project src\MO.Ekyc.Infrastructure \
    --startup-project src\MO.Ekyc.Api \
    --output-dir Persistence\Migrations

# Apply migrations to DB
dotnet ef database update \
    --project src\MO.Ekyc.Infrastructure \
    --startup-project src\MO.Ekyc.Api

# Generate SQL script (for prod DBA review)
dotnet ef migrations script \
    --project src\MO.Ekyc.Infrastructure \
    --startup-project src\MO.Ekyc.Api \
    --output schema.sql

How to Add a Hangfire Background Job

C#Enqueue a fire-and-forget job
using Hangfire;

public class MyController : ControllerBase
{
    private readonly IBackgroundJobClient _jobClient;
    public MyController(IBackgroundJobClient jobClient) { _jobClient = jobClient; }

    [HttpPost("do-async")]
    public IActionResult DoAsync(MyCommand cmd)
    {
        // Fire and forget
        _jobClient.Enqueue<MyBackgroundJob>(job => job.RunAsync(cmd.LeadId));

        // Scheduled
        _jobClient.Schedule<MyBackgroundJob>(
            job => job.RunAsync(cmd.LeadId),
            TimeSpan.FromMinutes(5));

        return Ok();
    }
}

public class MyBackgroundJob
{
    private readonly EkycDbContext _db;
    public MyBackgroundJob(EkycDbContext db) { _db = db; }

    public async Task RunAsync(Guid leadId)
    {
        // work happens here, can use injected services
    }
}

How to Add a New Configuration Section

  1. Add the JSON section to appsettings.json and/or appsettings.Development.json
  2. Create a strongly-typed options class: public class MyOptions { public string BaseUrl { get; set; } = ""; }
  3. Bind in Program.cs: builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("MySection"));
  4. Inject IOptions<MyOptions> where needed

How to Publish a Downstream Event

C#Publish to Kafka / downstream consumers
public class MyService
{
    private readonly IDownstreamEventPublisher _publisher;
    public MyService(IDownstreamEventPublisher publisher) { _publisher = publisher; }

    public async Task SomethingHappenedAsync(Guid leadId, ...)
    {
        // Publish event to configured targets (CLEVERTAP, CDP, DATALAKE, ZOHO_CRM)
        await _publisher.PublishAsync(
            eventType: "SOMETHING_HAPPENED",
            leadId: leadId,
            targets: new[] { "CLEVERTAP", "CDP" },
            ct: ct);
    }
}

How to Populate a Sync Table Locally

Some tests require reference data. Use the SQL scripts from the BRD Analysis folder as seed data.