CommunityCodierung & Entwicklunggithub.com

Piyush-Nagar/coding-agents-skills

Unsere Backend-Dienste folgen dem Prinzip **Package by Feature**, organisiert über vier Geschwister-Module. Dieses Dokument ist die verbindliche Quelle dafür, wo neuer Code hingehört.

Was ist coding-agents-skills?

Dieses Skill definiert eine strikte, sprachunabhängige Verzeichnisstruktur für Backend-Dienste, die auf dem Prinzip "Package by Feature" basiert. Es organisiert den Code in vier Module: `capabilities` (Geschäftslogik), `db` (Persistenz), `client` (Ausgehende Integrationen) und `starter` (Composition Root). Jedes Feature hat sein eigenes Paket innerhalb von `capabilities/`, das wiederum in Unterpakete für jeden API-Endpunkt (`getById`, `create`, etc.) unterteilt ist. Das Skill legt klare Abhängigkeitsregeln fest: `capabilities` kommuniziert nur über Schnittstellen (`*Repo`, `*Client`) mit den anderen Modulen, um Austauschbarkeit und fokussierte Tests zu ermöglichen. Es bietet außerdem ein ausführliches Beispiel `testcase-service` und spezifische Faustregeln, wie neue Features, APIs und Mapper hinzugefügt werden, ohne die Struktur zu brechen. Das Skill ist primär für Java/Spring Boot beschrieben, aber die Verzeichnisstruktur und Namenskonventionen (`*Db`, `*Repo`, `*RepoImpl`, `*Dao`, `*Controller`, `*ControllerImpl`) sind für alle unterstützten Sprachen (Kotlin, Go, Python, Node.js) verpflichtend.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Piyush-Nagar/coding-agents-skills/tree/HEAD/backend/core

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Paketstruktur

Unsere Backend-Dienste folgen dem Prinzip Package by Feature, organisiert über vier Geschwister-Module. Dieses Dokument ist die verbindliche Quelle dafür, wo neuer Code hingehört.

Sprachnotiz. Die Beispiele unten sind in Java/Spring Boot geschrieben, da dies unser primärer Backend-Stack ist, aber die Struktur selbst ist sprachunabhängig und für jede von uns verwendete Backend-Sprache verbindlich (Kotlin, Go, Python, Node usw.). Übersetzen Sie die Dateierweiterungen und Framework-Annotationen; behalten Sie die Ordnerstruktur, die Namenssuffixe (*Db, *Repo, *RepoImpl, *Dao, *Controller, *ControllerImpl) und die Aufteilung in Schnittstelle/Implementierung unverändert bei.

Kernprinzip

Gruppieren nach Feature, nicht nach technischer Schicht. Eine Änderung am Feature "Bestellungen" sollte Dateien betreffen, die nebeneinander liegen, nicht Dateien, die über Pakete wie controllers/, services/, repositories/ verstreut sind. Die technische Schichtung erfolgt auf Modul-Ebene (db, client, capabilities, starter), und Package-by-Feature findet innerhalb jedes Moduls statt.

Die vier Module

Jeder Dienst besteht aus diesen vier Geschwister-Modulen:

<service>/
├── capabilities/   # Feature/Geschäftslogik — das Gehirn
├── db/             # Persistenz: Entitäten, Repositories, Migrationen
├── client/         # Ausgehende Integrationen mit externen Systemen
└── starter/        # Spring Boot Starter — verdrahtet alles und führt aus

capabilities/ — Geschäftslogik

Jedes Feature erhält ein eigenes Paket, und innerhalb jedes Features gibt es ein Unterpaket pro API-Endpunkt (getById, create, updateById, getByQuery, …). Jedes Unterfeature besitzt drei Geschwister-Ordner:

  • service/ — die einzelne Service-Klasse für diese API
  • dto/ — request/ und response/ (je nach Bedarf)
  • mapper/ — übersetzt *Db-Entitäten ↔ DTOs (beide Richtungen) nur für dieses Unterfeature

Der Controller für das Feature befindet sich im Stamm des Feature-Pakets, aufgeteilt in eine Schnittstelle und eine Implementierung, genau wie die Repositories:

  • capabilities/<feature>/<Feature>Controller.java — Schnittstelle; deklariert Routen, HTTP-Verben, Anfrage-/Antworttypen, API-Dokumentationsannotationen
  • capabilities/<feature>/<Feature>ControllerImpl.java — Implementierung; trägt die Framework-Annotation (@RestController) und delegiert jede Route an den passenden Unterfeature-Service

Gemeinsam für alle Unterfeatures dieses Features. Siehe Controller.

  • Abhängigkeiten: db (nur über *Repo-Schnittstellen — siehe DB-Nutzungsregel), client
  • Hat niemals Abhängigkeiten zu: starter
  • Enthält keine main-Klasse, kein @SpringBootApplication
  • Besitzt: HTTP-Controller, API-Anfrage-/Antwort-DTOs, Mapper, Services

db/ — Persistenz

Organisiert ein Ordner pro Tabelle. Jeder Tabellenordner hat genau drei Unterordner:

db/<tableName>/
├── model/
│   ├── <EntityName>Db.java          # Haupt-JPA-Entität
│   └── <Other>Db.java               # Unter-Modelle (eingebettete Typen, Kind-Entitäten, Projektionen)
├── dao/
│   └── <EntityName>Dao.java         # Low-Level-Datenzugriff (Criteria/JPQL/native Queries)
└── repository/
    ├── <EntityName>Repo.java        # ÖFFENTLICHE Schnittstelle — der einzige Typ, den capabilities sieht
    └── impl/
        └── <EntityName>RepoImpl.java # Kombiniert Spring Data + Dao hinter der Schnittstelle

Namenskonvention

SchichtSuffixBeispiel
JPA-EntitätDbTestcaseDb
Repository-SchnittstelleRepoTestcaseRepo
Repository-ImplementierungRepoImplTestcaseRepoImpl
Datenzugriffs-HelferDaoTestcaseDao

DB-Nutzungsregel

Capabilities dürfen nur von *Repo-Schnittstellen abhängen. *RepoImpl und *Dao sind intern — niemals außerhalb des db/<tableName>/-Pakets injiziert oder referenziert. Dadurch bleibt die Persistenzschicht austauschbar und Capability-Tests konzentrieren sich auf den Vertrag.

  • Abhängigkeiten: nichts in diesem Dienst
  • Stellt bereit: *Repo-Schnittstellen und *Db-Modelltypen
  • Enthält niemals: Geschäftslogik, Transformation zu API-DTOs

client/ — Ausgehende Integrationen

Enthält den Code, der andere Dienste aufruft, von denen dieser Dienst abhängt. Hauptsächlich ein Unterordner pro abhängigem Mikroservice in unserer eigenen Plattform (paymentService, userService, executionService, …). Wrapper für Drittanbieter-SDKs oder Message-Producer können ebenfalls hier liegen, aber das vorherrschende Muster — und der Grund für die Existenz dieses Moduls — sind abhängige Mikroservice-Clients.

client/<microserviceName>/
├── <Microservice>Client.java       # Schnittstelle — worauf capabilities angewiesen ist
├── impl/
│   └── <Microservice>ClientImpl.java   # Implementierung: HTTP/gRPC/SDK-Aufruf, Wiederholungen, Timeouts
├── dto/                            # Anfrage-/Antwort-Formate für DIESEN Mikroservice
│   ├── request/
│   └── response/
└── mapper/                         # optional: Übersetzung zwischen deren und unseren Formaten

Regeln

  • Ein Unterordner pro abhängigem Mikroservice, benannt nach dem Dienst. Fassen Sie nicht mehrere Mikroservices in einem Paket zusammen, selbst wenn sie "verwandt" sind.

  • Capabilities hängen nur von *Client-Schnittstellen ab. *ClientImpl ist intern — dieselbe Regel wie bei *Repo / *RepoImpl.

  • Die Client-Schnittstelle stellt die API des Mikroservices in Form seiner eigenen DTOs bereit; Capabilities können diese bei Bedarf weiter anpassen.

  • Übergreifende Belange (Wiederholungen, Circuit Breaker, Anfrage-/Antwort-Logging, Auth-Header) gehören in impl/ — Capabilities sollten sie niemals sehen.

  • Abhängigkeiten: nichts in diesem Dienst

  • Stellt bereit: *Client-Schnittstellen und deren Anfrage-/Antwort-DTOs

  • Enthält niemals: Geschäftslogik, Capability-Orchestrierung

starter/ — Composition Root

Das einzige Modul, das ausgeführt wird. Enthält @SpringBootApplication, globale REST-Exception-Handler, Sicherheitskonfiguration, Filter und application.yml. Keine Controller — diese leben in capabilities/.

  • Abhängigkeiten: capabilities (und transitiv db, client)
  • Besitzt: das auslieferbare Artefakt, übergreifende Web-Konfiguration (Sicherheit, CORS, Fehlerbehandlung)
  • Besitzt niemals: Feature-Controller oder Feature-DTOs

Abhängigkeitsregel

starter → capabilities → { db, client }

Pfeile gehen in eine Richtung. db und client importieren niemals aus capabilities oder starter. capabilities importiert niemals aus starter.

Controller

Es gibt einen Controller pro Feature, nicht pro Unterfeature, und er befindet sich im Stamm des Feature-Pakets innerhalb von capabilities/. Er ist in zwei Dateien aufgeteilt, die die *Repo / *RepoImpl-Aufteilung widerspiegeln:

  • capabilities/<feature>/<Feature>Controller.java — Schnittstelle. Deklariert die Routen, HTTP-Verben, Pfad-/Query-Parameter, Anfrage-/Antwort-DTO-Typen und API-Dokumentationsannotationen (OpenAPI/Swagger). Jeder, der die Schnittstelle liest, sollte den gesamten HTTP-Vertrag sehen.
  • capabilities/<feature>/<Feature>ControllerImpl.java — Implementierung. Trägt die Framework-Annotation (@RestController in Spring), ist das Injektionsziel für Unterfeature-Services und enthält eine kurze Methode pro Route, die den passenden service/ aufruft und das Ergebnis zurückgibt. Keine Logik über die Delegation hinaus.

Warum die Aufteilung? Die Schnittstelle ist der API-Vertrag — stabil, dokumentiert, isoliert testbar. Die Implementierung ist Verkabelung — sie kann sich ändern, ohne den Vertrag zu ändern. Gleiche Begründung wie bei *Repo vs. *RepoImpl.

Der Controller delegiert jede HTTP-Route an den passenden Unterfeature-Service in capabilities/<feature>/<sub-feature>/service/. Anfrage-/Antwort-DTOs gehören zum Unterfeature (capabilities/<feature>/<sub-feature>/dto/); der Controller ist nur ein dünner HTTP-Adapter.

Warum innerhalb von capabilities/ und nicht in starter/? Der Controller, sein Service und seine DTOs ändern sich alle gemeinsam, wenn eine API geändert wird — die Zusammenlegung in einem Modul bewahrt das Package-by-Feature-Versprechen "ein Feature, ein Ort". starter/ ist für übergreifende Verkabelung reserviert (Sicherheit, Fehlerbehandler, App-Einstiegspunkt), nicht für per-Feature-Webcode.

Ausgearbeitetes Beispiel: ein testcase-Feature

Der folgende Baum verwendet .java-Erweiterungen zur Veranschaulichung. Ersetzen Sie die Dateierweiterung und Framework-Idiome Ihrer Sprache — Kotlin .kt, Go-Pakete, Python-Module usw. — aber behalten Sie die Ordnerstruktur und die Namenssuffixe identisch bei.

testcase-service/
├── capabilities/
│   └── com/example/testcase/capabilities/
│       └── testcase/                          # Haupt-Feature
│           ├── TestcaseController.java        # Schnittstelle — HTTP-Vertrag, Routendeklarationen, API-Dokumentation
│           ├── TestcaseControllerImpl.java    # Implementierung — @RestController, delegiert an Unterfeature-Services
│           ├── getById/                       # Unterfeature = eine API
│           │   ├── service/
│           │   │   └── GetTestcaseByIdService.java
│           │   ├── dto/
│           │   │   └── response/
│           │   │       └── GetTestcaseByIdResponse.java
│           │   └── mapper/
│           │       └── GetTestcaseByIdMapper.java     # TestcaseDb → response
│           ├── create/
│           │   ├── service/
│           │   │   └── CreateTestcaseService.java
│           │   ├── dto/
│           │   │   ├── request/
│           │   │   │   └── CreateTestcaseRequest.java
│           │   │   └── response/
│           │   │       └── CreateTestcaseResponse.java
│           │   └── mapper/
│           │       └── CreateTestcaseMapper.java      # request → TestcaseDb, TestcaseDb → response
│           ├── updateById/
│           │   ├── service/
│           │   │   └── UpdateTestcaseByIdService.java
│           │   ├── dto/
│           │   │   ├── request/
│           │   │   │   └── UpdateTestcaseByIdRequest.java
│           │   │   └── response/
│           │   │       └── UpdateTestcaseByIdResponse.java
│           │   └── mapper/
│           │       └── UpdateTestcaseByIdMapper.java
│           └── getByQuery/
│               ├── service/
│               │   └── GetTestcaseByQueryService.java
│               ├── dto/
│               │   ├── request/
│               │   │   └── GetTestcaseByQueryRequest.java
│               │   └── response/
│               │       └── GetTestcaseByQueryResponse.java
│               └── mapper/
│                   └── GetTestcaseByQueryMapper.java
│
├── db/                                        # ein Ordner pro Tabelle
│   └── com/example/testcase/db/
│       └── testcase/                          # Tabellenname
│           ├── model/
│           │   ├── TestcaseDb.java            # JPA-Entität — Haupt
│           │   └── TestcaseStepDb.java        # Unter-Modell (Kind/eingebettet)
│           ├── dao/
│           │   └── TestcaseDao.java           # Query/Criteria-Helfer, paketprivater Gebrauch
│           └── repository/
│               ├── TestcaseRepo.java          # Schnittstelle — capabilities hängt von DIESER ab
│               └── impl/
│                   └── TestcaseRepoImpl.java  # Kombiniert Spring Data + TestcaseDao
│
├── client/                                   # ein Ordner pro abhängigem Mikroservice
│   └── com/example/testcase/client/
│       ├── executionService/                 # abhängiger Mikroservice, den wir aufrufen
│       │   ├── ExecutionServiceClient.java        # Schnittstelle
│       │   ├── impl/
│       │   │   └── ExecutionServiceClientImpl.java  # HTTP/gRPC-Aufruf, Wiederholungen, Timeouts
│       │   ├── dto/
│       │   │   ├── request/
│       │   │   │   └── RunTestcaseRequest.java
│       │   │   └── response/
│       │   │       └── RunTestcaseResponse.java
│       │   └── mapper/
│       │       └── ExecutionServiceMapper.java
│       └── userService/                      # ein weiterer abhängiger Mikroservice
│           ├── UserServiceClient.java
│           └── impl/
│               └── UserServiceClientImpl.java
│
└── starter/                                  # nur übergreifende Verkabelung — keine Controller
    └── com/example/testcase/
        ├── TestcaseServiceApplication.java
        ├── config/
        │   ├── SecurityConfig.java
        │   └── WebConfig.java
        ├── error/
        │   └── GlobalExceptionHandler.java   # @ControllerAdvice — übergreifend, nicht per Feature
        └── resources/application.yml

Faustregeln

  • Neue API zu einem bestehenden Feature hinzufügen? Erstellen Sie ein neues Unterfeature-Paket in capabilities/<feature>/ mit eigenem service/, dto/{request,response}/ und mapper/, fügen Sie dann die Routendeklaration in <Feature>Controller.java (Schnittstelle) und die delegierende Methode in <Feature>ControllerImpl.java hinzu. Erstellen Sie keinen zweiten Controller.
  • Ein brandneues Feature hinzufügen? Erstellen Sie das Feature-Paket in capabilities/ (Controller im Stamm, Unterfeatures pro API) und den passenden Tabellenordner in db/<tableName>/ mit model/, dao/, repository/{impl/}. Fügen Sie einen Client nur hinzu, wenn Sie ein externes System aufrufen. Rühren Sie starter/ nicht an.
  • Immer *Repo injizieren, niemals *RepoImpl oder *Dao. Capabilities hängen nur von der Schnittstelle ab. *RepoImpl und *Dao sind intern in db/<tableName>/ — sie existieren, damit Sie die Abfragestrategie oder den zugrunde liegenden Speicher austauschen können, ohne Capability-Code zu ändern.
  • Immer *Client injizieren, niemals *ClientImpl. Gleiche Regel für Aufrufe abhängiger Mikroservices. Capabilities hängen von der Schnittstelle ab; der Transport (HTTP, gRPC, SDK) lebt hinter impl/.
  • Ein Ordner pro abhängigem Mikroservice in client/. Benennen Sie den Ordner nach dem Mikroservice. Fassen Sie nicht "alle internen Dienste" in einem Paket zusammen.
  • Die Granularität der Unterfeatures endet bei capabilities/. db/ ist pro Tabelle organisiert; client/ pro externem System. Eine Aufteilung pro API würde kohärenten Code zerstreuen.
  • Anfrage-/Antwort-DTOs und deren Mapper sind pro Unterfeature, niemals gemeinsam genutzt. Wenn create und updateById heute ähnlich aussehen, behalten Sie zwei DTOs und zwei Mapper. Die gemeinsame Nutzung koppelt zwei APIs, die unabhängig voneinander auseinanderdriften werden.
  • Mapper konvertieren Entität ↔ DTO direkt. Keine Zwischen-Domain-Modell-Schicht. <SubFeature>Mapper konvertiert *Db ↔ die Anfrage-/Antwort-DTOs dieses Unterfeatures.
  • Drei Unterfeatures haben ähnlichen Code? Widerstehen Sie dem Drang, einen gemeinsam genutzten Service oder Mapper zu extrahieren, bis Sie dasselbe Muster an vier oder mehr Stellen haben.
  • Wiederverwendung über Features hinweg? Legen Sie gemeinsam genutzte Hilfsprogramme in ein common/-Paket innerhalb von capabilities/ (oder im entsprechenden Modul) und importieren Sie sie von dort. Vermeiden Sie zirkuläre Abhängigkeiten zwischen Features.
  • Validierungslogik gehört zu den DTOs (dto/request/, dto/response/). Framework-spezifische Validierungsannotationen (z. B. @NotBlank, @Size, @Valid) direkt auf den DTO-Feldern. Unternehmensweite Validierungsregeln, die mehrere Felder betreffen, gehören in den Service (service/) dieses Unterfeatures.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

Verwandte Skills