← Back to projects
07Social · Mobile2025

Stoodi

A verified-student social network combining real-time messaging, matching, groups and events. I designed and built the backend domain architecture, identity verification, trust-and-safety pipeline and the CI-enforced boundaries that keep it maintainable as it grows.

Source code is private. The case study focuses on domain boundaries, identity trust and the CI-enforced architecture contract rather than user-specific data.

Product model
Verified-student social network
Audience
University students
Timeline
2025
My role
Backend architect & platform engineer
Context
Private commercial product
Delivery scope
Domain architecture · realtime · trust & safety · CI/CD
11domain modules
85%enforced test coverage
12stage CI pipeline

01

Product surfaces

Product surfaces

Distinct product surfaces made school, association and campus responsibilities visible before getting into the backend architecture.

Private workflow

Student verification flow

Enrollment claim, registry matching and reminder path before an account gains full access.

Screenshot coming

Discovery & real-time messaging

Matching and direct/group conversations delivered over authenticated WebSocket connections.

Private product UI

Trust & safety operations

Report review, sanctions and appeals operating on the same verified identity as the social graph.

02

Problem and constraints

Problem and constraints

A student social product cannot rely on self-reported enrollment, and cannot let 11 growing feature areas — identity, discovery, messaging, groups, events, moderation and billing — collapse into a tangle of direct imports. Trust and architecture had to be enforced, not assumed.

Unverifiable identity claims

A self-reported school claim gives no real trust signal for a product built around verified students.

Real-time messaging needs its own auth boundary

A long-lived HTTP credential is not a safe fit for a persistent socket connection.

Domain growth pressure

Eleven feature areas sharing one codebase tend to erode isolation under delivery pressure unless boundaries are enforced mechanically.

Safety cannot be purely reactive

Some risks, like underage users, need automatic detection and containment rather than waiting for a manual report.

03

My exact ownership

My exact ownership

I designed

  • The full domain architecture: 11 bounded contexts, each behind services, selectors and adapters, composed through explicit ports rather than direct imports.
  • The trust model linking claimed enrollment, registry verification and moderation into one coherent identity lifecycle.
  • The deterministic matching and ranking approach behind discovery, and the ticket-based real-time authentication model.

I implemented

  • All 11 Django domains, their APIs, background tasks, and the ADR-driven adapter pattern wired at the composition root.
  • The verification pipeline against the ESR open dataset, the moderation and sanctions pipeline, and automated underage detection.
  • The 12-stage CI pipeline: linting, strict typing, import-linter, security scanning, coverage gates, migration checks and OpenAPI breaking-change detection.

I collaborated on

  • Product behavior on the Flutter client consuming these APIs, and how real-time and verification states surface to users.
  • Trust-and-safety policy decisions translated into the moderation domain’s rules.

03

Objectives

Objectives

Trust

Tie product access to enrollment verified against an external, authoritative registry.

Boundaries

Keep 11 domains independently testable and enforce isolation as a CI-gated contract.

Realtime

Deliver messaging over persistent sockets without exposing long-lived credentials to the connection.

Safety

Combine manual moderation with automated detection paths for the highest-risk cases.

04

System architecture

System architecture

01Client

Flutter · iOS · Android

02API & realtime

DRF · Channels/WebSockets · ticket auth

03Domain core

11 bounded domains · services · ports

04Runtime & external

PostgreSQL/PostGIS · Redis · Celery · ESR · RevenueCat

The Flutter client, realtime and request APIs, the 11-domain core and external services stay separated so no layer needs to understand another’s internals.

Technology stack

Application

Django 5 · Django REST Framework · drf-spectacular · ASGI/Daphne

Realtime & async

Channels · channels-redis · Celery · Redis

Data & storage

PostgreSQL · PostGIS · S3-compatible storage

Integrations & operations

RevenueCat · Sentry · Prometheus · structlog · ESR open dataset

05

Core domain model

Core domain model

The data model keeps verified identity, the social graph and the safety layer distinct. A school is validated against an external registry before it can anchor a verification, connections and conversations depend on that verified identity, and moderation acts on the same identity without folding into product tables.

01

School (registry-verified)

Synced from the ESR open dataset and matched by normalized name similarity.

02

Account & verification

Local identity plus an independent enrollment-verification record and state.

03

Profile & discovery readiness

Media, coarse location and visibility gates before a profile enters matching.

04

Connection

Friend requests and blocks scoped to verified accounts.

05

Conversation & message

Direct and group messaging delivered over authenticated realtime sockets.

06

Community & event

Groups with roles, and events with participation tied back to membership.

07

Moderation case

Reports, evidence, sanctions and appeals attach to the verified identity, not to a generic user row.

06

State model

State model

Verification and trust are one lifecycle: an enrollment claim only becomes access after external evidence, and later safety events act on that same verified identity rather than a separate account concept.

01

Enrollment claimed

The user submits a school and identity claim.

02

Registry matched

The claim is normalized and matched against the ESR school catalog.

03

Verified

The account gains full access once enrollment is confirmed.

04

Trust maintained

Reports and sanctions reference this same verified identity going forward.

Alternate transitions

Verification mismatch

A registry mismatch is routed to review instead of silent accept or reject.

Underage detected

The account is auto-suspended and sessions revoked pending review, independent of manual moderation.

Verification and trust form one lifecycle: access follows external evidence, and safety events act on that same verified identity rather than a parallel concept.

07

Engineering challenges

Engineering challenges

01

Verifying real students without becoming a registry authority

Problem

Self-reported enrollment gives no defensible trust signal, but the platform cannot own an authoritative student registry itself.

Solution

Sync and normalize the French Ministry of Higher Education open dataset, match claims by name similarity, and route mismatches to a review and reminder path instead of an automatic accept or reject.

Tradeoff

Verification depends on external data freshness and occasional manual review, but access is grounded in defensible evidence instead of trust in the user alone.

Failure if handled poorly

If enrollment is only self-reported, the verified-student premise collapses and bad actors can pose as students freely.

02

Domain boundaries that survive growth

Problem

Eleven domains sharing one codebase are one convenient import away from becoming entangled as features accumulate.

Solution

Enforce import-linter contracts at CI, document every legitimate cross-domain exception as an ADR-backed adapter registered at a single composition root, and move cross-domain facts through a transactional outbox.

Tradeoff

Every cross-domain interaction costs more ceremony than a direct call, but domains stay independently testable and replaceable.

Failure if handled poorly

Without enforcement, delivery pressure erodes boundaries import by import until domains can no longer change independently.

03

Real-time messaging without a long-lived credential on the socket

Problem

Exposing a standard access token directly to a persistent WebSocket connection extends its exposure window well beyond a normal request.

Solution

Exchange a short-lived realtime ticket for the socket handshake, and track device-level sessions independently from HTTP authentication.

Tradeoff

The client needs an extra round trip to obtain a ticket before connecting, in exchange for a bounded credential on the socket.

Failure if handled poorly

A leaked long-lived token would otherwise grant sustained access to a live conversation stream instead of a short, revocable window.

08

Failure modes & mitigations

Failure modes & mitigations

Registry mismatch during verification

A normalized-name mismatch is routed to a review and reminder path instead of being silently accepted or rejected.

Underage user detected

The account is automatically suspended and its sessions revoked pending review, independent of the manual moderation queue.

Cross-domain event not yet delivered

The transactional outbox persists the fact durably and supports replay instead of losing it on a downstream failure.

Invalid or expired realtime ticket

The socket connection is rejected explicitly, forcing a fresh ticket exchange instead of degrading silently.

09

Technical decisions

Technical decisions

01

Ports, not imports, across domains

Cross-domain reads go through adapters registered at a composition root, with import-linter enforcing the boundary as a CI-gated contract rather than a convention.

02

A transactional outbox for cross-domain facts

Domain events are persisted durably and replayed rather than fired and forgotten, so a downstream domain never silently misses a fact.

03

Trust built on external evidence

Enrollment is checked against the ESR school registry instead of accepting self-reported claims, with mismatches routed to a review path instead of being silently accepted or rejected.

10

Security & operations

Security & operations

CI-enforced architecture boundaries

import-linter contracts and ADR-documented exceptions gate every merge, not just code review discipline.

Fail-fast production configuration

Startup validation refuses debug mode, wildcard hosts, insecure cookies and placeholder secrets instead of accepting them silently.

Key rotation and encrypted secrets

JWT signing keys rotate with multiple accepted verification keys, and OTP secrets are Fernet-encrypted at rest.

Verified billing webhooks

RevenueCat webhooks are validated by HMAC signature before any entitlement is granted.

  • 85% line and 80% branch coverage gates enforced in CI.
  • A 12-stage pipeline covering security scanning, migration freshness and OpenAPI breaking-change detection.
  • Structured logs, Sentry and Prometheus metrics for operational visibility.

11

Delivered system

Delivered system

Implementation outcomes — no unverified commercial metrics.

A defensible trust boundary

Access is grounded in registry-checked enrollment rather than a self-reported claim.

Isolation enforced, not assumed

11 domains stay independently testable because CI, not convention, enforces their boundaries.

An auditable safety pipeline

Reports, sanctions and appeals operate on the same verified identity with both manual and automatic paths.

Realtime without losing session control

Messaging runs over persistent sockets while credentials stay short-lived and revocable.

What I learned

“Architecture boundaries only hold if CI enforces them mechanically — conventions alone erode under delivery pressure. And trust in a social product has to be built on external evidence, not self-reported claims.”
Next case studyAssaliz