Pamietnik — Architekturdokumentation (arc42)
1. Einführung und Ziele
Pamietnik ist ein persönliches Journal bestehend aus drei Komponenten: einer Android-App, einer Web-App und einem Go-Backend-Server.
Fachliches Zielbild
- Android-App loggt GPS-Standortdaten im Hintergrund, cached offline, lädt bevorzugt per HTTP zum Server hoch
- Manuelles Hinzufügen von Punkten (ohne GPS) mit optionaler Notiz und Hashtags
- Server erkennt aus Logdaten „Stops" (längere Aufenthalte) und leitet Vorschläge ab
- Standortinformationen (Reverse-Geocoding) über kostenlose API (Nominatim/OSM)
- Web-Ansicht nach Login: Tagesübersicht und Tagesdetail mit Karte, Stops, Vorschlägen
- Datei-Export aus der App (Android SAF)
- Nutzung erfordert Account und Login
Qualitätsziele
| Priorität |
Ziel |
Maßnahme |
| 1 |
Datensicherheit |
Argon2id Passwort-Hashing, Session-Auth, HTTPS |
| 2 |
Offline-Fähigkeit |
Room + WorkManager auf Android, kein RAM-only Queue |
| 3 |
Idempotenz |
event_id (UUID) als Unique Key pro Device |
| 4 |
Erweiterbarkeit |
Geocoding-Provider über Config austauschbar |
| 5 |
Datenschutz |
Background-Location Disclosure (Google Play) |
Stakeholder
| Rolle |
Erwartung |
| Mobiler Nutzer |
Automatisches Tracking, Offline-Betrieb, Export |
| Web-Nutzer |
Login, Tagesübersicht mit Karte, Stops und Vorschläge |
2. Randbedingungen
| Art |
Bedingung |
| Technisch |
Android: Kotlin + Jetpack Compose (DEC-CLIENT-01) |
| Technisch |
Backend: Go, PostgreSQL (DEC-DB-01) |
| Technisch |
Webapp: Vanilla TypeScript, keine Frameworks |
| Technisch |
Server-Schnittstelle: HTTP/REST, kein MQTT (DEC-API-01) |
| Technisch |
Karten: OpenStreetMap + MapLibre, konfigurierbare Tile-Quelle (DEC-MAP-01) |
| Technisch |
API-Spec: OpenAPI 3.1 als openapi.yaml (DEC-OPENAPI-01) |
| Organisatorisch |
Keine kommerziell lizenzierten Bibliotheken oder Schriften |
| Organisatorisch |
Geocoding über kostenlose API, Policy-konform (DEC-GEO-01) |
| Organisatorisch |
Alle Anforderungen in Markdown; Diagramme in Mermaid (DEC-DOC-01) |
3. Kontextsicht
Fachlicher Kontext
| Kommunikationspartner |
Interaktion |
| Benutzer mobil |
Start/Stop Trip, Status, Export, manuelle Punkte, Hashtags |
| Benutzer web |
Login, Tagesübersicht, Tagesdetail, Stops, Vorschläge, Tag-Filter |
| Android OS / Location Services |
Liefert GPS-Standortupdates |
| Webserver |
Nimmt Trackpoints entgegen, berechnet Stops, stellt Website bereit |
| Geocoding-Dienst (Nominatim) |
Liefert Adress-/Place-Infos zu Koordinaten |
| Dateisystem via SAF |
Export-Ziel für Logs/Trips |
Technischer Kontext
| Schnittstelle |
Protokoll |
Auth |
| Android → Backend |
HTTPS REST JSON |
Session Cookie (TBD: API-Key/JWT) |
| Browser → Backend |
HTTPS |
Session Cookie |
| Backend → Nominatim |
HTTPS |
— (Rate-Limit, gecached) |
| App → Dateisystem |
Android SAF |
OS-Permission |
4. Lösungsstrategie
| Entscheidung |
ID |
Begründung |
| Android: Kotlin + Jetpack Compose |
DEC-CLIENT-01 |
Android „Kotlin-first", moderne UI |
| Go Single Binary als Backend |
— |
Geringe Ressourcen, einfaches Deployment |
| Distroless Docker Image |
— |
Minimale Angriffsfläche |
Schema via go:embed + CREATE TABLE IF NOT EXISTS |
DEC-SCHEMA-01 |
Keine Migrations-Infrastruktur, idempotenter Start |
| Room (SQLite) auf Android |
DEC-DB-01 |
Offline-First Persistenz |
| PostgreSQL im Backend |
DEC-DB-01 |
JSONB, UUID-Funktionen, zuverlässig |
| Session Cookie statt JWT (Web) |
DEC-AUTH-01 |
Serverseitige Invalidierung, einfacheres Threat Model |
| Argon2id für Passwort-Hashing |
REQ-AUTH-01 |
GPU-resistent, State-of-the-art |
| MapLibre + OpenStreetMap |
DEC-MAP-01 |
Open Source, keine Lizenzkosten, konfigurierbare Tiles |
| Nominatim als Geocoding-Provider |
DEC-GEO-01 |
Kostenlos, OSM-basiert, Provider austauschbar |
| Vanilla Web Components |
— |
Kein Framework-Overhead, langlebig, keine Lizenzkonflikte |
| OpenAPI 3.1 als API-Spec |
DEC-OPENAPI-01 |
Maschinenlesbar, Validierbar in CI |
5. Bausteinsicht
Ebene 1 — Systeme
Ebene 2 — Backend-Pakete
Ebene 2 — Android-Pakete
6. Laufzeitsicht
R1 — Trip starten und Trackpoints senden
R2 — Offline → Online Retry (REQ-SYNC-02, REQ-SYNC-04)
R3 — Web Login und Tagesansicht (REQ-WEB-01, REQ-WEB-02)
R4 — Manuellen Punkt hinzufügen (REQ-MAN-01 bis REQ-MAN-05)
R5 — Stop-Erkennung und Geocoding (REQ-SUG-01, REQ-GEO-01)
R6 — Schema-Initialisierung beim Server-Start
7. Verteilungssicht
Docker Compose Services:
| Service |
Image |
Port extern |
Funktion |
postgres |
postgres:16-alpine |
— (intern) |
Datenhaltung |
api |
golang:1.25 → distroless |
— (intern) |
REST API + Web UI |
webapp |
node:22 → nginx:alpine |
9050 |
SPA + API-Proxy |
Start:
Schema wird beim API-Start automatisch initialisiert (keine separate Migration nötig).
8. Querschnittskonzepte
Authentifizierung & Sessions (REQ-AUTH-01, REQ-AUTH-02, DEC-AUTH-01)
- Passwörter: Argon2id (
$argon2id$<saltHex>$<hashHex>)
- Sessions in PostgreSQL (
sessions-Tabelle): sofortige Invalidierung bei Logout
- Cookie:
HttpOnly, SameSite=Lax, 24h Lebensdauer
- Android Upload-Auth: aktuell Session Cookie — TBD: API-Key oder JWT (DEC-AUTH-01)
Idempotenz (REQ-SYNC-04)
- Jeder Trackpoint trägt eine client-generierte
event_id (UUID empfohlen)
- Unique Constraint
(device_id, event_id) in PostgreSQL
ON CONFLICT DO NOTHING → doppelter Upload = 200 OK, kein Fehler
- Batch-Response enthält
accepted_ids und rejected[] für Queue-Steuerung
Offline-First (REQ-SYNC-01 bis REQ-SYNC-03)
- Jeder Punkt wird zuerst in Room (SQLite) persistiert
- Upload via WorkManager:
NetworkConnected-Constraint, Exponential Backoff
source-Feld unterscheidet "gps" und "manual"
Device-Registrierung
EnsureDevice() beim ersten Ingest: INSERT ... ON CONFLICT DO NOTHING
- Verknüpft
device_id mit authentifiziertem user_id
- Query-Endpoints filtern über
devices.user_id
Hashtags (REQ-TAG-01 bis REQ-TAG-06)
- Feld
tags: string[] im Trackpoint-Schema
- Eingabe-Vorschläge aus vorhandenen Tags des Nutzers
- Server:
GET /v1/tags (Häufigkeit), GET /v1/trackpoints?tag=...
- Webapp: Tag-Filter in Tagesdetail, Tag-Übersicht
Geocoding (REQ-GEO-01 bis REQ-GEO-03)
- Nur ereignisbasiert (pro Stop), niemals bulk/periodisch
- Cache vor jedem Provider-Call
- Provider über Config austauschbar (kein Hardcode)
- Nominatim: User-Agent korrekt setzen (Policy-Pflicht)
Karten (DEC-MAP-01, REQ-MAP-01, REQ-MAP-02)
- Android: MapLibre SDK
- Webapp: MapLibre GL JS (open source, lizenzfrei)
- Tile-Quelle konfigurierbar (
tileUrl-Property auf <track-map>)
9. Produktanforderungen (aus README)
3.1 Background-Logging
| ID |
Anforderung |
| REQ-LOC-01 |
App MUSS Standort im Vordergrund und Hintergrund erfassen |
| REQ-LOC-02 |
COARSE (optional FINE) im Manifest + Runtime; App funktioniert mit „approximate" |
| REQ-LOC-03 |
Background-Location korrekt angefordert |
| REQ-LOC-04 |
foregroundServiceType="location" wenn Foreground Service |
3.2 Offline-First
| ID |
Anforderung |
| REQ-SYNC-01 |
Trackpoints zuerst lokal persistieren (kein RAM-Queue) |
| REQ-SYNC-02 |
Upload bevorzugt; bei Fehler cachen und automatisch nachladen |
| REQ-SYNC-03 |
WorkManager mit NetworkConnected + Retry/Backoff |
| REQ-SYNC-04 |
Keine Duplikate bei Retries; Idempotenz via event_id |
3.3 Manuelle Punkte
| ID |
Anforderung |
| REQ-MAN-01 |
Manueller Punkt: lat, lon, timestamp (Pflicht); Name/Notiz (optional) |
| REQ-MAN-02 |
Lokal in Room speichern |
| REQ-MAN-03 |
In Upload-Flow integriert (inkl. event_id) |
| REQ-MAN-04 |
Validierung as-the-user-types; verständliche Fehlermeldungen |
| REQ-MAN-05 |
Vorschlag „aktueller Zeitpunkt / aktuelle Position" (editierbar) |
3.4 Export
| ID |
Anforderung |
| REQ-EXP-01 |
Export in Datei möglich |
| REQ-EXP-02 |
Via SAF ACTION_CREATE_DOCUMENT; Nutzer wählt Speicherort |
3.5 Server Website
| ID |
Anforderung |
| REQ-WEB-01 |
Website nur nach Login zugänglich |
| REQ-WEB-02 |
Pro Tag Punkte anzeigen (Liste + Detailansicht) |
| REQ-WEB-03 |
Daten nutzerspezifisch (ein Nutzer sieht nur eigene Daten) |
| REQ-WEB-04 |
Kartenansicht (Punkte/Stops pro Tag) |
3.6 Security / Auth
| ID |
Anforderung |
| REQ-AUTH-01 |
Passwörter NICHT im Klartext; Argon2id |
| REQ-AUTH-02 |
Session Cookie; serverseitig verwaltbar (Logout/Expire) |
| REQ-AUTH-03 |
Auth auch für Web-Query-Endpoints |
3.7 Stops / Vorschläge
| ID |
Anforderung |
| REQ-SUG-01 |
Stop-Erkennung (Mindestdauer, Radius konfigurierbar) |
| REQ-SUG-02 |
Vorschläge aus Stops ableiten |
| REQ-SUG-03 |
Pro Nutzer/Device/Trip zuordenbar; in Website sichtbar |
3.8 Geocoding
| ID |
Anforderung |
| REQ-GEO-01 |
Kostenloser Dienst (Nominatim); Caching + Rate-Limit |
| REQ-GEO-02 |
Provider austauschbar via Config (ohne App-Update) |
| REQ-GEO-03 |
Kein Bulk/periodisches Geocoding; ereignisbasiert (pro Stop) |
3.9 Karten
| ID |
Anforderung |
| REQ-MAP-01 |
OpenStreetMap-Basis; MapLibre als Renderer |
| REQ-MAP-02 |
Tile-Quelle konfigurierbar |
3.10 OpenAPI
| ID |
Anforderung |
| REQ-OPENAPI-01 |
API als OpenAPI 3.1 dokumentiert |
| REQ-OPENAPI-02 |
openapi.yaml im Repository versioniert |
| REQ-OPENAPI-03 |
Alle Endpoints, Schemas, Fehler, Security-Schemes |
| REQ-OPENAPI-04 |
CookieAuth via type: apiKey, in: cookie |
3.11 Google Play / Datenschutz
| ID |
Anforderung |
| REQ-PRIV-01 |
Background-Location Disclosure im UI; korrekte Deklaration |
3.13 Hashtags
| ID |
Anforderung |
| REQ-TAG-01 |
Trackpoints mit Hashtags versehen (#restaurant, ...) |
| REQ-TAG-02 |
Tags lokal in Room + beim Upload übertragen (tags: string[]) |
| REQ-TAG-03 |
Server persistiert Tags, abfragbar pro Trackpoint |
| REQ-TAG-04 |
Webapp: Filter nach Hashtag |
| REQ-TAG-05 |
Eingabe-Vorschläge aus vorhandenen Tags des Nutzers |
| REQ-TAG-06 |
Webapp: Tag-Übersicht mit Häufigkeit |
10. Architekturentscheidungen
| ID |
Entscheidung |
Alternativen |
Status |
| DEC-CLIENT-01 |
Android (Kotlin + Compose) |
iOS, Flutter |
Entschieden |
| DEC-LOC-01 |
Background Location Logging |
Nur Vordergrund |
Entschieden |
| DEC-API-01 |
HTTP/REST + JSON |
MQTT, gRPC, Protobuf |
Entschieden (Protobuf offen) |
| DEC-DB-01 |
Room (Android) + PostgreSQL (Server) |
SQLite Server, MySQL |
Entschieden |
| DEC-WEB-01 |
Website mit Login (SSR + SPA) |
nur API |
Entschieden |
| DEC-AUTH-01 |
Session Cookie für Web; JWT optional |
JWT only |
Session entschieden |
| DEC-MAP-01 |
MapLibre + OSM, konfigurierbare Tiles |
Google Maps |
Entschieden |
| DEC-GEO-01 |
Nominatim, Provider austauschbar |
Google Places API |
Entschieden |
| DEC-OPENAPI-01 |
OpenAPI 3.1 YAML |
kein Spec |
Entschieden |
| DEC-SCHEMA-01 |
go:embed schema.sql, IF NOT EXISTS |
golang-migrate |
Entschieden |
11. Risiken und offene Punkte
Implementierungsstatus
Security-Risiken (Architekt-Review)
Produktionsreife
Offene Entscheidungen
| Thema |
Optionen |
Auswirkung |
timestamp-Format |
epochMillis vs RFC3339 |
Android + API Kompatibilität |
| Android Upload Auth |
Session Cookie / API-Key / JWT |
Security-Architektur |
| Payload |
JSON vs Protobuf |
Bandbreite auf Mobile |
| Batch-Limits |
max Items / max Bytes |
Denial-of-Service Schutz |
| Retention Policy |
Löschen nach X Tagen |
Storage-Kosten |
| Stop-Parameter |
Mindestdauer, Radius |
Qualität der Vorschläge |
| Geocoding Provider |
Nominatim public / self-hosted |
Datenschutz, Verfügbarkeit |