9.0 KiB
HemHubs arkitektur
Detta dokument beskriver den arkitektur som kan verifieras i repositoryts kod,
tester och konfiguration. Historiska implementationssteg finns under
features/ och övergripande beslut under
decisions/.
Aktuell implementation
Monorepo
HemHub ligger i ett Git-repository med två separata applikationer:
hemhub/
├── backend/
├── frontend/
└── docs/
Applikationerna har egna byggverktyg och beroenden. De delar inte källkod eller byggprocess.
Frontend
Frontend finns i frontend/ och använder React 19, TypeScript, Vite, pnpm och
dnd-kit-ekosystemets aktuella React-adapter. Den ansvarar för:
- hämtning och presentation av användare och uppgifter;
- lokalt val av aktiv användare;
- formulär för att skapa användare och uppgifter;
- val och visning av ansvarig användare på uppgifter;
- serverbekräftade statusändringar genom knappar på uppgiftskorten;
- optimistiska statusflyttar genom drag-and-drop mellan brädans kolumner;
- bekräftad och serverbekräftad permanent radering av uppgifter;
- klientnära validering och begripliga felmeddelanden;
- uppgiftsbrädan med kolumnerna Väntande, Pågående och Klart.
Tillståndet hanteras lokalt i React-komponenter. Ingen router eller separat global state-lösning används.
Backend
Backend finns i backend/ och använder Java 21, Spring Boot 4.1.0, Maven,
Spring Web, Spring Data JPA och Flyway. Maven Wrapper ingår i repositoryt.
Backend ansvarar för API, slutlig validering, skapande av UUID och tidsstämplar, persistens samt sortering av returnerade användare och uppgifter.
Kommunikation
Alla applikationsendpoints ligger under /api. Frontend använder enbart
relativa adresser, exempelvis /api/users och /api/tasks.
Vid lokal utveckling kör Vite normalt på port 5173 och proxar /api till
http://localhost:8080, där Spring Boot körs. Ingen generell
CORS-konfiguration finns i backend. Webbläsaren anropar därmed Vites origin,
och utvecklingsservern vidarebefordrar API-anropen.
Aktuella endpoints:
GET /api/healthGET /api/usersPOST /api/usersGET /api/tasksPOST /api/tasksPUT /api/tasks/{taskId}/assigneePUT /api/tasks/{taskId}/statusDELETE /api/tasks/{taskId}
Databas och migreringar
Lokal körning använder en H2-databas i minnet. Databasen finns under backendprocessens livstid och lokal utvecklingsdata återställs när backend startas om. Automatiska backendtester använder en separat H2-databas i minnet.
Båda anslutningarna använder H2:s MODE=PostgreSQL,
DATABASE_TO_LOWER=TRUE och DEFAULT_NULL_ORDERING=HIGH. Det är en verifierbar
kompatibilitetsinställning, inte samma sak som att applikationen har verifierats
mot PostgreSQL.
Flyway kör migreringarna:
V1__create_users.sqlV2__create_tasks.sqlV3__add_task_points.sqlV4__add_task_assignee.sql
Hibernate är konfigurerat med ddl-auto=validate; Flyway skapar schemat och
Hibernate validerar entiteterna mot det.
Domänmodell
Användare
En användare lagras i tabellen app_user med:
id: UUID;name: visningsnamn, högst 50 tecken;normalized_name: trimmat namn i gemener, internt och unikt;created_at: enInstant, lagrad somTIMESTAMP WITH TIME ZONE.
normalized_name exponeras inte via API. Användare returneras alfabetiskt efter
visningsnamn med deterministiska sekundära jämförelser.
Uppgift
En uppgift lagras i tabellen task med:
id: UUID;title: obligatorisk titel, högst 100 tecken;description: valfri beskrivning, högst 500 tecken;status:WAITING,IN_PROGRESSellerCOMPLETED;points: obligatoriskt heltal mellan 1 och 99;assignee_id: nullable främmande nyckel tillapp_user;created_at: enInstant, lagrad somTIMESTAMP WITH TIME ZONE.
Status lagras som enumens textvärde genom EnumType.STRING. Nya uppgifter får
alltid status WAITING. Poängintervallet skyddas i backend och med en
databasconstraint. En uppgift kan vara otilldelad eller referera till exakt en
ansvarig användare. Relationen hämtas tillsammans med uppgifterna när de listas,
så API-responsen kan innehålla ansvarigs id och name utan separata
frontend-anrop. Alla aktiva användare ser samma uppgiftslista.
Ansvarig är valfri vid skapande. Tilldelnings-API:t kan tilldela eller byta
ansvarig i samtliga statusar. Ansvarig kan tas bort i WAITING och COMPLETED,
men inte i IN_PROGRESS. Tilldelning ändrar aldrig uppgiftens status.
Alla direkta statusövergångar är tillåtna och samma målstatus är idempotent.
IN_PROGRESS kräver en ansvarig. När en otilldelad uppgift påbörjas skickar
frontend aktiv användares id, och backend tilldelar användaren och ändrar status
i samma transaktion. En befintlig ansvarig byts aldrig av statusoperationen.
Uppgifter raderas fysiskt genom task-repositoryt. Det finns ingen mjukraderingsflagga, papperskorg eller återställningsmodell. Radering av en uppgift påverkar inte dess ansvariga användare.
Aktiv användare
Användarlistan hämtas från backend. Frontend lagrar endast den valda
användarens UUID i webbläsarens localStorage under nyckeln
hemhub.activeUserId.
Vid start verifieras det lagrade id:t mot backendens aktuella användarlista. Ett giltigt val återanvänds i samma browser. Ett ogiltigt val tas bort. Valet är lokalt per browser och utgör inte autentisering eller behörighetskontroll.
Felhantering
Backend använder ett litet gemensamt JSON-format med code och message.
ApiExceptionHandler översätter kända valideringsfel till 400 Bad Request,
saknade uppgifter eller användare till 404 Not Found och dubbletter eller
otillåtna tilldelningsändringar till 409 Conflict.
Frontend skiljer mellan fel vid hämtning och skapande. Hämtfel kan återförsökas. Formulärfel visas nära formuläret och inmatningen behålls vid misslyckade API-anrop. Status- och tilldelningsfel visas lokalt på berört kort; statusknappar och tilldelning uppdateras först med backendens bekräftade respons. Drag-and-drop flyttar kortet optimistiskt men återställer hela den tidigare uppgiften vid fel. Vid framgång ersätts alltid det lokala värdet med backendens fullständiga respons. Status- och tilldelningsanrop delar låsning per task-id, så det berörda kortet blockeras utan att resten av brädan låses. Drag-and-drop återanvänder backendens befintliga status-API oförändrat.
Radering är serverbekräftad och använder samma låsning per task-id. Kortet och
bekräftelsedialogen ligger kvar tills backend svarar. Vid 204 No Content
tas kortet bort lokalt. Ett 404-svar tas endast som bekräftelse på att kortet
redan saknas när felkoden är TASK_NOT_FOUND; övriga fel behåller kortet och
dialogen för ett nytt försök.
Teststrategi
Backend har JUnit 5-tester:
- ett fristående MockMvc-test för health-endpointen;
- Spring Boot-integrationstester via MockMvc mot H2 in-memory för användar- och uppgifts-API.
Frontend använder Vitest, jsdom och React Testing Library. fetch och
localStorage ersätts i testerna, så frontendtesterna kräver inte en körande
backend. Drag-and-drop-adaptern översätter bibliotekshändelser till task-id och
status, så stateflöden kan testas utan att simulera fysisk layout i jsdom.
Produktionsbygget kör TypeScript-kompilering följt av Vite.
Produktionsdeployment
Ingen produktionsdeployment är implementerad i repositoryt. Det finns inga Dockerfiler, pipelinefiler eller produktionsspecifika Nginx-, Watchtower- eller databaskonfigurationer. H2 används både lokalt och i automatiska tester; någon PostgreSQL-konfiguration finns ännu inte.
Beslutad planerad riktning
Repositoryt anger att affärsregler även framöver ska säkerställas i backend och att större arkitekturella beslut ska diskuteras innan de införs.
Följande produktionsriktning är beslutad men ännu inte implementerad:
- PostgreSQL ska användas som produktionsdatabas.
- Frontend och backend ska paketeras som separata Docker-images.
- Källkoden ligger i Gitea.
- Drone ska bygga och publicera images till ett privat registry.
- Watchtower ska uppdatera de körande tjänsterna när nya images publiceras.
- Nginx kan användas som reverse proxy framför tjänsterna.
- Produktionsmiljön ska köras på Ubuntu-servern Biff.
Den planerade riktningen beskrivs även i
005-production-deployment-direction.md.
Punkterna ovan beskriver målbilden och ska inte tolkas som att motsvarande
konfiguration redan finns eller har verifierats.
Fortfarande öppna detaljer
Följande har inte fastställts i dokumentationen och ska beslutas i samband med att produktionslösningen implementeras:
- exakt containerstruktur och tjänsteindelning;
- image-namn och taggningsstrategi;
- produktions-URL;
- hantering och distribution av secrets;
- exakt Nginx-konfiguration;
- exakt Drone-, registry-, Watchtower- och deploymentkonfiguration.
Miljöspecifika adresser, credentials och secrets ska inte lagras i dessa arkitekturdokument.