Oryn

Oryn

An exercise in building multi-tenant architecture—how I learned to isolate data, manage subscriptions, and think in layers rather than features.

Swagger UI ReDoc GitHub

Context

I set out to understand how modern SaaS platforms (Linear, Notion, Figma) serve thousands of companies from a single codebase while keeping data strictly separated. Not by reading about it—by building the infrastructure myself.

The result is Oryn: a backend API where companies sign up, get provisioned an isolated workspace, and manage projects internally. Two levels—Tenant and User—with boundaries enforced at every layer.

This is the largest project I've built not by LOC count, but by discipline. I used Claude Code and Copilot to plan infrastructure before writing imports, challenging assumptions rather than generating code. Also built this interface with Kimi to document the journey.

How It Was Built

I started with the database. Multi-tenancy offers three paths: shared schema, isolated schema, or isolated databases. I chose shared database with row-level security—enforcing tenant isolation at the application layer through strict query scoping.

Authentication & Isolation JWT tokens carry tenant context. FastAPI dependencies inject this into sessions, automatically scoping every query with tenant_id filters.
PyJWT • FastAPI Depends • SQLAlchemy
Subscription Infrastructure Full Stripe integration with webhook handling for subscription events, usage tracking, and tier enforcement at the middleware level.
stripe-python • Webhooks • Async handlers
Audit Logging Immutable records of who did what, when. Built into the base repository class so every operation is tracked by default, not as an afterthought.
PostgreSQL triggers • Append-only tables
Async Architecture Fully async database operations using asyncpg and SQLAlchemy 2.0. No synchronous blocking in the hot path.
asyncpg • asyncio • Connection pooling

What I Learned

Planning is cheaper than refactoring. Two days whiteboarding database relationships saved weeks of migration headaches. The upfront cost of thinking through foreign key constraints and cascade behaviors paid for itself immediately.

Audit logs are infrastructure, not features. When built into the base class, tracking is automatic and comprehensive. When added later, it's patchy and unreliable.

Multi-tenancy is a business logic concern. Deciding where to enforce boundaries—database, application, or both—affects every query you write. I chose application layer enforcement with database constraints as backup.

I also learnt how to manage and build a real working subscription system with Strip integration which to me seemed complex, until I built it myself

On testing: I abandoned automated testing for this project, relying solely on Postman. Not proud of this—automated testing would have caught edge cases earlier. Next project, testing comes first, not last.

I learned how to implement rate limiting in my application using SlowAPI. This technique allows me to control how many requests a client can make within a specific time window. By enforcing these limits, I can protect the system from abuse and ensure fair access for all users.

This is a learning project. It runs real payment logic in Stripe test mode and follows production patterns, but includes no database security hardening. A reference implementation, not a production service.

Built with FastAPI, PostgreSQL, and patience.