Domain-aligned service ownership
The 16 services follow product domains such as identity, voting, messaging, moderation, and reporting. Each service owns its schema and communicates through typed gRPC contracts or domain events.
A polling and discussion platform designed to keep participant identities separate from voting and reporting data. Organizations receive live results, moderation tools, and province-level insights without access to individual voting histories.
The core product problem was supporting registered and guest participation while enforcing one vote per question and exposing only aggregate reporting to organizations.
I designed the system architecture and built the Flutter app, backend services, data model, event-driven workflows, recommendation features, and AWS deployment.
Private pilot with an institutional launch partner.
Flutter and React clients supported by 16 NestJS services running on AWS EKS, with PostgreSQL, Redis, Kafka, and RabbitMQ.
Keeping participant identity separate from voting data while coordinating live reporting, notifications, and recommendations across multiple services.



Consensus has three interfaces: a Flutter app for participants, a React operations panel for staff, and a React reporting panel for organizations. Polls can be scheduled or published as breaking questions, with threaded discussions and moderation workflows around them.
Registered and guest votes use different identifiers and PostgreSQL uniqueness constraints. Reporting services consume aggregate results, while organization users cannot access individual voting histories.
The 16 NestJS services fall into four domains: identity and access; content and voting; social and messaging; and governance. The corporate BFF shapes organization-facing reads without exposing internal service boundaries.
Clients use REST and WebSocket through the Application Load Balancer. Direct internal dependencies use typed gRPC contracts; aggregation and delivery work leaves the request path through brokers, keeping vote submission independent from downstream effects.
Each service writes only to its PostgreSQL schema; other services use its interface or consume its events instead of joining across schemas. This preserves ownership while avoiding database-per-service overhead at beta scale.
App, staff, and corporate users have separate JWT scopes. Guest identity becomes an HMAC-derived identity_hash, and question-scoped vote_hash values support database constraints for registered and guest votes. After a poll closes, the vote hash can be purged while aggregate results remain.
Kafka carries domain events for fan-out, replay, aggregation, and read-model updates. Independent consumers process votes.cast, consensus.closed, user.registered, and message events without extending the originating request.
RabbitMQ carries point-to-point delivery jobs for push and email workers. These jobs use acknowledgements, bounded retries, and dead-letter queues so failed deliveries can be retried or inspected independently.
NestJS remains the API and orchestration layer. Python owns offline ingestion, feature engineering, training, and candidate generation; Rust handles request-time scoring and compute-heavy aggregation, where predictable latency matters more than iteration speed.
Route 53 and CloudFront serve the public edge; Kubernetes Ingress provisions the ALB, and EKS runs service Deployments, scheduled workers, readiness probes, and HPA. RDS, ElastiCache, MSK, and Amazon MQ carry the stateful dependencies; GitHub Actions, ECR, and Helm handle delivery.
Prometheus scrapes RED metrics from each service, with Grafana and Alertmanager covering SLOs and alerts. Product signals such as votes per minute, active WebSocket connections, and push success rate sit beside infrastructure metrics.
The 16 services follow product domains such as identity, voting, messaging, moderation, and reporting. Each service owns its schema and communicates through typed gRPC contracts or domain events.
Kafka provides replayable fan-out for domain events and read-model updates. RabbitMQ handles push and email jobs that need per-message acknowledgements, retries, and dead-letter queues.
Schema ownership prevents cross-service writes and joins while one PostgreSQL instance keeps beta-stage operations manageable. A service can move to its own database later without changing the ownership contract.
NestJS remains the default. Python is limited to the offline ML lifecycle, while Rust handles latency-sensitive scoring and aggregation; each additional runtime has a specific workload and interface.
Running 16 services on EKS was not required for pilot scale. It was an intentional operational learning exercise around Kubernetes deployment, service ownership, observability, and event-driven workflows.