group events
This commit is contained in:
@@ -1,98 +1,194 @@
|
||||
<p align="center">
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||
</p>
|
||||
# Costly – Architektur & Entscheidungsgrundlagen
|
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||
## Ziel des Projekts
|
||||
Costly ist eine **Offline-fähige PWA zur Aufteilung von Gruppenausgaben**.
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
Kernanforderungen:
|
||||
- Gruppen ohne Accounts (Join per Invite)
|
||||
- Offline Änderungen möglich
|
||||
- Späterer Sync ohne Datenverlust
|
||||
- Erweiterbar für Konflikte, Undo, Mehrgeräte-Nutzung
|
||||
|
||||
## Description
|
||||
---
|
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||
## Architektur – Überblick
|
||||
|
||||
## Project setup
|
||||
Costly nutzt eine **Hybrid Event-Driven Architektur**:
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
- **Events sind die Quelle der Wahrheit**
|
||||
- **Relationale Tabellen sind Read-Models**
|
||||
- Offline-Sync basiert auf **append-only Events**
|
||||
|
||||
### Überblick
|
||||
Client
|
||||
├─ Offline Outbox (Events)
|
||||
├─ UI arbeitet mit aktuellem State
|
||||
└─ Sync: sendet Events + holt neue Events
|
||||
|
||||
Server
|
||||
├─ Event Store (group_events, expense_events, ...)
|
||||
├─ Read Models (groups, expenses, ...)
|
||||
└─ Projektion: Events → State
|
||||
|
||||
yaml
|
||||
Code kopieren
|
||||
|
||||
---
|
||||
|
||||
## Warum Events?
|
||||
### Nicht: „Wie sieht der Zustand aus?“
|
||||
### Sondern: „Was ist passiert?“
|
||||
|
||||
Beispiel:
|
||||
- ❌ `Group.name = "Trip"`
|
||||
- ✅ `GROUP_RENAMED { from: "Urlaub", to: "Trip" }`
|
||||
|
||||
### Vorteile
|
||||
- Offline-Änderungen lassen sich sicher synchronisieren
|
||||
- Events sind idempotent (keine Doppeländerungen)
|
||||
- Konflikte sind sichtbar und erklärbar
|
||||
- Undo / Replay später möglich
|
||||
- Debugging & Audit inklusive
|
||||
|
||||
---
|
||||
|
||||
## Event-Log vs. History
|
||||
- `group_events` **ersetzt** klassische `group_history`
|
||||
- Keine doppelte Speicherung
|
||||
- Events *sind* die History
|
||||
|
||||
Merksatz:
|
||||
> **History sagt, was ist.
|
||||
> Events sagen, was passiert ist.**
|
||||
|
||||
---
|
||||
|
||||
## Hybrid-Ansatz (bewusst gewählt)
|
||||
Costly ist **kein dogmatisches Event Sourcing**.
|
||||
|
||||
Wir nutzen:
|
||||
- ✅ Events als Quelle der Wahrheit
|
||||
- ✅ Materialisierte Tabellen (`groups`, `expenses`) für schnelle Queries
|
||||
|
||||
➡️ Best of both worlds.
|
||||
|
||||
---
|
||||
|
||||
## Aktueller Konflikt-Ansatz
|
||||
**Last-write-wins**, basierend auf Server-Reihenfolge der Events.
|
||||
|
||||
- Client, der später synchronisiert, kann frühere Änderungen überschreiben
|
||||
- Das ist **bewusst akzeptiert** fürs MVP
|
||||
|
||||
Spätere Erweiterung möglich (siehe unten).
|
||||
|
||||
---
|
||||
|
||||
## Event-Grundstruktur
|
||||
```ts
|
||||
GroupEvent {
|
||||
id: string; // vom Client generiert (UUID/ULID)
|
||||
groupId: string;
|
||||
type: string; // z.B. GROUP_CREATED, GROUP_RENAMED
|
||||
payload: object; // fachliche Daten
|
||||
actorId?: string; // deviceId / memberId
|
||||
createdAt: Date; // Server-Zeit
|
||||
}
|
||||
```
|
||||
## Warum Client-generierte IDs?
|
||||
### Ermöglicht Idempotenz
|
||||
|
||||
## Compile and run the project
|
||||
- Events können gefahrlos erneut gesendet werden
|
||||
- Wichtig für Offline-Retry
|
||||
### Warum es weiterhin REST-Endpunkte gibt
|
||||
Es gibt z. B.:
|
||||
- PATCH /groups/:id/name
|
||||
Diese Endpunkte:
|
||||
- validieren Input
|
||||
- prüfen Regeln
|
||||
- erzeugen intern Events
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ npm run start
|
||||
Der Client muss Events nicht kennen, solange er online ist.
|
||||
|
||||
# watch mode
|
||||
$ npm run start:dev
|
||||
➡️ Später können Offline-Events direkt an /events/batch geschickt werden.
|
||||
|
||||
# production mode
|
||||
$ npm run start:prod
|
||||
## Transaktionen – aktueller Stand
|
||||
Jede fachliche Aktion läuft atomar:
|
||||
- Event wird gespeichert
|
||||
- Read Model wird aktualisiert
|
||||
- Beides in einer DB-Transaktion
|
||||
|
||||
Beispiel:
|
||||
|
||||
```sql
|
||||
Code kopieren
|
||||
renameGroup()
|
||||
├─ UPDATE groups
|
||||
└─ INSERT group_events
|
||||
```
|
||||
### Erweiterung: Optimistic Concurrency (später)
|
||||
Problem:
|
||||
- Zwei Geräte ändern offline denselben Wert
|
||||
|
||||
## Run tests
|
||||
Lösung:
|
||||
- Client sendet zusätzlich expectedLastEventId
|
||||
- Server prüft:
|
||||
- passt → akzeptieren
|
||||
- passt nicht → 409 Conflict
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ npm run test
|
||||
Das ist ohne Architekturwechsel möglich, weil Events genutzt werden.
|
||||
|
||||
# e2e tests
|
||||
$ npm run test:e2e
|
||||
## Erweiterung: Transaktionen als Event-Gruppe
|
||||
Wenn später komplexe Änderungen kommen (z. B. mehrere Expenses auf einmal):
|
||||
|
||||
# test coverage
|
||||
$ npm run test:cov
|
||||
Idee
|
||||
Mehrere Events gehören logisch zusammen.
|
||||
|
||||
```ts
|
||||
{
|
||||
transactionId: "tx-123",
|
||||
type: "EXPENSE_CREATED"
|
||||
}
|
||||
{
|
||||
transactionId: "tx-123",
|
||||
type: "EXPENSE_SPLIT_UPDATED"
|
||||
}
|
||||
```
|
||||
### Möglichkeiten:
|
||||
|
||||
## Deployment
|
||||
Server behandelt Events mit gleicher transactionId atomar
|
||||
- Undo / Rollback pro Transaktion möglich
|
||||
- UI kann „eine Aktion“ anzeigen statt viele Events
|
||||
|
||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||
### Erweiterung: Undo / Replay
|
||||
Da Events append-only sind:
|
||||
- Undo = Gegen-Event
|
||||
- Replay = Events neu anwenden → State neu aufbauen
|
||||
|
||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||
Beispiel:
|
||||
- GROUP_RENAMED (A → B)
|
||||
- Undo → GROUP_RENAMED (B → A)
|
||||
|
||||
```bash
|
||||
$ npm install -g @nestjs/mau
|
||||
$ mau deploy
|
||||
```
|
||||
Wichtige Designregeln
|
||||
- Repositories kennen keine Events
|
||||
- Services orchestrieren State + Event
|
||||
- Events sind append-only
|
||||
- Read Models dürfen neu aufgebaut werden
|
||||
- Keine Logik im Controller
|
||||
|
||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||
Warum das alles?
|
||||
Weil Costly:
|
||||
- offline funktionieren soll
|
||||
- ohne Accounts auskommen soll
|
||||
- später wachsen können soll
|
||||
- aber jetzt schon stabil sein muss
|
||||
|
||||
## Resources
|
||||
Diese Architektur ist der kleinste sinnvolle Schritt, um das zu erreichen.
|
||||
|
||||
Check out a few resources that may come in handy when working with NestJS:
|
||||
|
||||
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
## Stay in touch
|
||||
|
||||
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||
|
||||
## License
|
||||
|
||||
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||
## Status
|
||||
- [x]Groups als Events
|
||||
- [x] Rename als Event
|
||||
- [ ] Expenses als Events
|
||||
- [ ] Client-Outbox (IndexedDB)
|
||||
- [ ] Sync Pull (GET /events?since=...)
|
||||
- [ ] Conflict Handling (optional)
|
||||
Reference in New Issue
Block a user