﻿# ADR-002 — A service layer shared by the GUI and the API

**Status:** accepted, implemented from day one · **Date:** 2026-10-13 · **Deciders:** RG

*Ported from YoutubeManager, where this was a migration; here it is the birth shape.*

## Context

The sibling YoutubeManager project started with the GUI routes and the API routes each holding
their own copy of the use-case logic. The two copies of "update a video" drifted (different
`publishAt` parsing, different defaults) and every fix had to be applied twice. Its ADR-002
introduced a service layer after the fact, one domain per change.

TikTok Manager is written after that lesson, so the question is not whether to migrate but
whether to keep the two adapters over one use case — and the answer is yes.

## Decision

`app/services/` holds one module per domain (`posts.py`, `uploads.py`, …). A service function:

- takes plain Python values (dicts, str, `Account`), never `request`, `session`, `flash` or a Flask
  response;
- takes the account/token it needs as a parameter (dependency injection, testable with a fake);
- validates its input against the rules in `SECURITY.md` §2.7 and raises typed exceptions
  (`ValidationError`, `NotFound`, `Forbidden`, `UpstreamError`);
- returns plain dicts shaped like the API `data` payload.

Routes are adapters:

```
GUI route:  form → service(...) → flash + redirect / render
API route:  JSON → service(...) → api_success(...)
```

Adapters own only: parsing the transport format, choosing the account (session vs header), the
CSRF/auth guard, and rendering. A shared error mapper turns the typed exceptions into the API
envelope codes and into GUI flash messages.

Layering (dependencies point down only):

```
routes/ (GUI adapters, API adapters)
   ↓
services/           use cases, validation
   ↓
tiktok_api.py     facade over the TikTok API (no DB, no Flask)
models.py, auth.py  persistence and OAuth
```

## Consequences

- Parity is a property of the structure, not of discipline (`API-PARITY.md` §2.1) — the project
  is born with zero drift.
- Unit tests target services with fakes; adapter tests are thin.
- Read-only features (list posts, insights) may call the facade directly from both adapters;
  anything with validation or multi-step logic goes through a service function.


