Farewell Wishes

System Design Proposal

Farewill Engineering — System Design Review

Agenda

Today's session covers the four things promised in the design document:

  1. Technology choices — what we're using and why
  2. Build vs. modify — which existing systems are touched
  3. Communication protocols — how components talk to each other
  4. Database adaptations — what changes in MongoDB

Starting Point: Existing Infrastructure

Before any decisions, here's what Farewill already has:

Layer What exists
Frontend React SPAs on Cloudflare CDN (Will Writing, Account Mgmt)
Backend Node.js Will Writing API on AWS
Database MongoDB with users, wills, gifts, people, possessions
Auth Account Management API issuing JWTs

All four of our decisions are anchored to this stack.

1. Technology Choices

Component Choice Status
Frontend React SPA on Cloudflare 🆕 New
Backend Will Writing API (Node.js) ✏️ Extended
Database MongoDB (existing DB) ✏️ Extended
File storage AWS S3 🆕 New bucket

Guiding principle: align with the existing stack. No new languages, no new cloud providers.

Why These Choices

React SPA on Cloudflare — matches the existing frontend paradigm; reuses component libraries for design consistency; Cloudflare handles 0.016 avg RPS in Year 1 trivially.

Extend the Will Writing API, not a new microservice — peak load is ~0.3 RPS in Year 1, ~200 RPS in Year 5. A new service adds maintenance overhead without benefit. Shared codebase also makes the conversion-to-will flow straightforward.

Reuse MongoDB — existing collections already model users, people, possessions and gifts. 20 MB structured data in Year 1, 12.5 GB by Year 5 — well within standard MongoDB capacity.

New S3 bucket — photo uploads aren't in the current architecture. At 40 GB Year 1 → 25 TB Year 5, dedicated object storage is required. S3 is the natural choice within AWS.

2. What Gets Built vs. Modified

System Action Detail
Wishes React SPA Build new New Cloudflare-hosted SPA
Will Writing API Extend Add /v1/wishes/* endpoint namespace
MongoDB Extend Add four new wish_* draft collections
AWS S3 New bucket Photo storage with lifecycle policy
Account Mgmt API No change Consumed as-is for JWT auth
Will Writing SPA No change Receives user at conversion via redirect
Funerals/Probate No change Entirely separate system — out of scope

One new service. Two extensions. Everything else untouched.

3. Communication Protocols — API Contract

New endpoints namespaced under /v1/wishes/ — isolated from existing will-writing routes.

Action Method Endpoint
Fetch session state GET /v1/wishes/session
Upsert loved one PUT /v1/wishes/people/:id
Upsert possession PUT /v1/wishes/possessions/:id
Upsert gift / message PUT /v1/wishes/gifts/:id
Request upload URL POST /v1/wishes/upload-url
Trigger conversion POST /v1/wishes/convert

All PUT endpoints are idempotent — safe to call repeatedly on autosave. All errors return a machine-readable error.code field.

3. Communication Protocols — Authentication

Problem: forcing sign-up at entry kills conversion.

Solution: deferred authentication

User starts journey → anonymous session in localStorage
       ↓
User reaches first save point
       ↓
Auth prompt (Account Management API → JWT)
       ↓
Server merges anonymous session into authenticated account

Token handling: short-lived JWTs (15 min) + silent refresh via HttpOnly cookie. The Will Writing API validates tokens locally using the shared public key — no round-trip to Account Management per request.

3. Communication Protocols — Image Upload

Images bypass the Node.js API to avoid blocking its event loop.

1.  SPA → API         POST /v1/wishes/upload-url
                       { contentType, fileSizeBytes }

2.  API validates      size ≤ 5 MB, MIME type = image/*
    API → SPA          short-lived presigned S3 PUT URL (5 min TTL)

3.  SPA → S3           PUT photo directly (HTTPS)

4.  SPA → API          PUT /v1/wishes/people/:id  { photoUrl: "s3://..." }

Security note: client-side compression is a bandwidth optimisation only. The 5 MB limit is enforced server-side in step 2, and S3's content-length-range condition prevents bypass via direct upload.

3. Communication Protocols — Conversion Flow

When a user clicks "Create my legal will":

SPA → POST /v1/wishes/convert
          ↓
  API atomically promotes wish_* draft data
  into the canonical will schema
          ↓
  Returns redirect URL to Will Writing SPA
  (pre-populated with user's data)

This is a synchronous, server-side operation — no event bus needed at current scale.

If the architecture evolves toward microservices, POST /v1/wishes/convert is the natural integration point for an async SNS/SQS event.

4. Database Adaptations — The Decision

Option A: reuse existing collections (add source: "wishes" flag)

  • ✅ No migration
  • ❌ Wish data is partial — will fail existing will schema validation
  • ❌ Pollutes production collections; harder to clean up abandoned sessions

Option B: new wish_* draft collections ← recommended

  • ✅ Clean separation; no risk to existing data integrity
  • ✅ Independent lifecycle (retention, deletion, GDPR)
  • ✅ Single explicit boundary at conversion time
  • ❌ Requires a promotion step at POST /v1/wishes/convert

POST /v1/wishes/convert is the one controlled point where partial draft data is validated and promoted into the canonical schema.

4. Database Adaptations — New Collections

Four new collections, all prefixed wish_:

Collection Purpose Key fields
wish_sessions Top-level progress tracker userId, status
wish_people Loved ones (Q1) userId, name, photoUrl
wish_possessions Possessions (Q2) userId, name, description, photoUrl
wish_gifts Gift/message assignments (Q3) userId, recipientId, possessionId, message

status on wish_sessions: in_progresscompletedconverted

Storage impact: ~20 MB in Year 1, ~12.5 GB by Year 5 — negligible within existing MongoDB deployment.

4. Database Adaptations — Indexes & Lifecycle

Indexes — every collection indexed on userId (the primary lookup key):

Collection Index
wish_sessions { userId: 1 }, { userId: 1, status: 1 }
wish_people { userId: 1 }
wish_possessions { userId: 1 }
wish_gifts { userId: 1 }

Data lifecycle:

  • Active sessions: retained indefinitely
  • Converted sessions: retained 12 months post-conversion, then soft-deleted
  • Abandoned sessions (no update in 24 months): flagged for deletion — GDPR data minimisation
  • Right to erasure: all wish_* collections covered by the existing user-deletion workflow

5. Security Controls

Control What it enforces
Authorisation Every endpoint asserts JWT userId matches the resource — no cross-user access
Data in transit HTTPS everywhere; Cloudflare redirects HTTP; S3 rejects HTTP PUT
Data at rest MongoDB EBS encryption (confirm active); S3 SSE-S3/KMS on photos bucket
S3 bucket access Private — no public ACL; photos served only via time-limited presigned GET URLs
Upload hardening MIME type allowlist, 5 MB limit enforced server-side + S3 content-length-range
Rate limiting 20 presigned URLs per user per hour on POST /v1/wishes/upload-url
Input validation Max field lengths enforced at API (names: 100 chars, messages: 2,000 chars)
Secrets management JWT keys + AWS credentials in AWS Secrets Manager — not in env vars

GDPR controls (right to erasure, retention, data minimisation) are covered in the Database Adaptations section.

Summary

Deliverable Decision
Tech choices React SPA + extend Will Writing API + MongoDB + S3
Build vs. modify 1 new service, 2 extensions, 4 systems untouched
Communication REST /v1/wishes/*, deferred JWT auth, presigned S3 uploads, sync conversion
Database 4 new wish_* draft collections; promoted at conversion
Security Authorisation per request, HTTPS+SSE at rest, upload hardening, secrets in AWS SM

Design principle throughout: prefer the simplest solution that meets the requirement and aligns with what Farewill already operates.

Questions?

Made with Slides.com