cat lanka/README.md

Lanka

A backend platform connecting influencers with brands to run marketing campaigns — account linking, audience analytics, campaign lifecycle, and search-based matching. Built as my BSc diploma project to take modern .NET architecture end to end.

.NET 10Modular MonolithDDDCQRSRabbitMQAspire
view source on github
ls modules/

Four bounded contexts.

Each module owns its domain, its persistence, and its integration events — boundaries enforced in code rather than by convention.

Users
Authentication, profiles, Instagram account linking
Analytics
Instagram insights, audience demographics, engagement metrics
Campaigns
Campaign creation, applications, contracts, tracking
Matching
Search and discovery via Elasticsearch
cat adr/003-modular-monolith.md

The decision that shaped it.

A modular monolith instead of microservices — and I wrote down why, along with what it would cost me.

Why
I wanted a clear separation of concerns while keeping the benefits of a single, unified codebase. Each module encapsulates its own domain logic, data, and interfaces; modules talk through well-defined contracts and events — so boundaries are enforced in code, not just on a diagram.
Payoff
Maintainability and isolated testing, without the operational cost of a distributed system — and the option to split a module out into a service later if it ever earns it.
Cost
Real overhead in establishing clean module boundaries, and a risk of complexity creeping in if inter-module communication isn't managed carefully. Both went in the decision record as accepted risks rather than being discovered later.

This is one of 16 architecture decision records in the repo — the habit of writing decisions down forced me to think them through before implementing. Read ADR-003 in full ↗

ls ~/stack --why

How the pieces fit.

Messaging

RabbitMQ with MassTransit for integration events between modules, Outbox/Inbox for reliable delivery, and saga orchestration for multi-step flows like Instagram account linking.

Persistence, per job

PostgreSQL as the relational store (one schema per module), MongoDB for analytics time-series, Redis for caching and distributed locking, Elasticsearch for search and discovery.

Reads vs writes

CQRS through MediatR pipelines — EF Core on the command side, Dapper for read-optimised queries.

Edge

YARP as a reverse-proxy gateway handling rate limiting and auth forwarding, with Keycloak as the OAuth2/OIDC provider.

Running it

.NET Aspire for local orchestration and OpenTelemetry for distributed tracing across modules.

cat lessons-learned.md

What I'd do differently.

The genuinely useful output of a learning project. These are my own notes from building it.

01

Design integration event contracts upfront, as part of the module specification — mine evolved during implementation instead.

02

Build test helpers, factories, and fixtures as the first implementation step. Treating tests as an afterthought made them harder to add later.

03

Start with the simplest domain model that works and refactor when real requirements demand it, rather than anticipating them.

04

Document complex flows before implementing them — writing them up afterwards meant reverse-engineering my own work.

Full write-up in the repo ↗