taskly · board și pontaj
Board-ul Kanban din Taskly, cu tema întunecată: carduri pe coloanele Open, To-Do, In Progress, Testing și Done.

Privire de ansamblu

Management de proiecte și issue-uri, cu board Kanban, epic-uri, pontaj, rapoarte și notificări în timp real, pe .NET 10 și Angular 22.

Cineva triază cardurile adunate peste noapte, mută trei pe board și pornește cronometrul. Tot ce e dedesubt există pentru ca doi oameni care editează același task să nu piardă niciodată o modificare fără să știe.

  • Issue-uri cu prioritate, status, label-uri, epic-uri și o poziție pe board
  • Board, backlog, epic-uri și issue-uri rezolvate, pentru fiecare proiect
  • Pontaj, calendar și grafice de raportare
  • Notificări live, remindere pentru termene și un rezumat săptămânal
Patru actori, grupați după ce vin să facă.
Pe scurt
RolSingurul autor: backend, frontend, infrastructură și tooling
StructurăDouăsprezece proiecte .NET, un client Angular 22, o aplicație de admin și un CLI de deploy
DateEF Core pe SQL Server, soft delete, concurență controlată prin row version
Timp realSignalR, cu grupuri per utilizator și opt servicii care rulează în fundal
Teste3.666 de metode de test pe backend, în 13 proiecte, 2.241 de spec-uri pe frontend
Repository publicO variantă demo: fără autentificare, cu localStorage în locul API-ului

Arhitectură

Șase straturi, o singură direcție și controller-e care doar trimit mai departe.

Toate săgețile merg în jos; aplicația de admin e un client al API-ului, nu o ușă din spate.
  • Un proiect poate referenția doar stratul de sub el, așa că orice handler se poate testa cu un fake.
  • Un controller construiește o comandă sau o interogare și o trimite mai departe. Permisiunile vin din atribute, nu din verificări în handler-e.
  • Clientul Angular, randat pe server, e format din componente standalone pe signal store-uri, câte unul pentru fiecare zonă din domeniu.
  • Am scris singur stratul CQRS, tipul Result și un mapper mic: e mai ieftin să le întrețin decât să configurez altele.

Proiectarea domeniului

Două agregate și două reguli impuse de baza de date, nu lăsate la nivel de convenție.

Tabelele din spatele board-ului. SD marchează soft delete, RV un row version.
  • Project și TaskItem sunt rădăcinile de agregat. Handler-ele încarcă și salvează doar entitățile acestea.
  • Fiecare salvare trimite row version-ul pe care l-a citit clientul, așa că o modificare făcută pe o copie învechită e refuzată cu 409.
  • Un query filter global transformă ștergerea într-un flag. Ștergerea definitivă e doar pentru administratori.
  • O specificație per entitate ține interogările în afara handler-elor.

Fluxul de date și de control

Un card mutat pe board, urmărit până în browserul celuilalt utilizator care îl vede.

Verificat față de ce a văzut cel care l-a mutat, apoi afișat tuturor celor care au board-ul deschis.

O coloană de pe board își încarcă toate cardurile și subtask-urile într-o singură cerere, nu câte una pentru fiecare card. Opt servicii din fundal trimit aceleași comenzi ca și controller-ele, așa că o perioadă de trial expiră fără să apese cineva pe ceva.

Strategia de testare

3.666 de metode de test pe backend și 2.241 de spec-uri pe frontend, cu handler-ul ca unitate.

  • Un handler e testat cu un repository fake și un principal, iar rezultatul se verifică prin Result-ul lui.
  • Rutarea, autentificarea sau baza de date îl fac, prin definiție, test de integrare.

Ce aș schimba

  • Task-urile și workflow-urile au aceeași structură, dar în cod au evoluat separat. O singură implementare generică ar elimina în jur de o mie de linii.
  • Mapper-ul bazat pe reflexie era potrivit la zece DTO-uri, dar nu și la cincizeci: e mai lent, iar o redenumire greșită apare abia la runtime.
  • Subtask-urile încă nu trimit row version-ul și nu își anunță mutările. Task-urile arată deja cum trebuie făcut.
  • O reordonare scrie câte un UPDATE pentru fiecare card, într-o tranzacție. Cu un singur CASE, totul s-ar face dintr-un singur drum până la baza de date.
  • Permisiunile sunt definite în API și copiate în client. Dacă le-aș trimite odată cu sesiunea, cele două nu s-ar mai desincroniza.