CommunityProgramación y desarrollogithub.com

Piyush-Nagar/coding-agents-skills

Nuestros servicios backend siguen una estructura de **Paquete por Funcionalidad** organizada en cuatro módulos hermanos. Este documento es la fuente de verdad sobre dónde debe ir el nuevo código.

¿Qué es coding-agents-skills?

Esta habilidad define una estructura de código estricta y específica para proyectos backend, basada en el principio de 'Paquete por Funcionalidad'. Organiza el código en cuatro módulos hermanos: `capabilities/` (lógica de negocio), `db/` (persistencia), `client/` (integraciones externas) y `starter/` (punto de entrada y configuración). La habilidad es útil porque impone una separación de responsabilidades muy clara y un flujo de dependencias unidireccional (`starter → capabilities → {db, client}`), lo que facilita el mantenimiento, las pruebas y la evolución del código. Está diseñada para cualquier lenguaje backend (Java, Kotlin, Go, etc.) y para plataformas como Claude Code, Cursor o Codex. La documentación detalla convenciones de nomenclatura, cómo estructurar nuevas funcionalidades y APIs dentro de `capabilities/`, y reglas estrictas sobre cómo los módulos deben depender entre sí (por ejemplo, `capabilities` solo debe usar interfaces `*Repo` y `*Client`).

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

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Estructura de Paquetes

Nuestros servicios backend siguen una estructura de Paquete por Funcionalidad organizada en cuatro módulos hermanos. Este documento es la fuente de verdad sobre dónde debe ir el nuevo código.

Nota sobre el lenguaje. Los ejemplos a continuación están escritos en Java/Spring Boot porque es nuestro stack backend principal, pero la estructura en sí misma es independiente del lenguaje y obligatoria en todos los lenguajes backend que usamos (Kotlin, Go, Python, Node, etc.). Traduzca las extensiones de archivo y las anotaciones del framework; mantenga la estructura de carpetas, los sufijos de nomenclatura (*Db, *Repo, *RepoImpl, *Dao, *Controller, *ControllerImpl) y la división interfaz/implementación sin cambios.

Principio fundamental

Agrupe por funcionalidad, no por capa técnica. Un cambio en la funcionalidad "pedidos" debe tocar archivos que vivan uno al lado del otro, no archivos dispersos en los paquetes controllers/, services/, repositories/. La división por capas técnicas ocurre en el límite del módulo (db, client, capabilities, starter), y el paquete-por-funcionalidad ocurre dentro de cada módulo.

Los cuatro módulos

Cada servicio está compuesto por estos cuatro módulos hermanos:

<service>/
├── capabilities/   # funcionalidad/lógica de negocio — el cerebro
├── db/             # persistencia: entidades, repositorios, migraciones
├── client/        # integraciones salientes con sistemas externos
└── starter/        # Spring Boot starter — conecta todo y se ejecuta

capabilities/ — lógica de negocio

Cada funcionalidad obtiene su propio paquete, y dentro de cada funcionalidad hay un sub-paquete por endpoint de API (getById, create, updateById, getByQuery, …). Cada sub-funcionalidad posee tres carpetas hermanas:

  • service/ — la única clase de servicio para esa API
  • dto/ — request/ y response/ (lo que aplique)
  • mapper/ — traduce entidades *Db ↔ DTOs (ambas direcciones) solo para esta sub-funcionalidad

El controlador de la funcionalidad vive en la raíz del paquete de la funcionalidad, dividido en una interfaz y una implementación, al igual que los repositorios:

  • capabilities/<feature>/<Feature>Controller.java — interfaz; declara rutas, verbos HTTP, tipos de request/response, anotaciones de documentación de API
  • capabilities/<feature>/<Feature>ControllerImpl.java — implementación; lleva la anotación del framework (@RestController) y delega cada ruta al servicio de sub-funcionalidad correspondiente

Compartido en todas las sub-funcionalidades de esa funcionalidad. Ver Controladores.

  • Depende de: db (solo a través de interfaces *Repo — ver Regla de uso de DB), client
  • Nunca depende de: starter
  • No contiene: clase main, ninguna @SpringBootApplication
  • Posee: controladores HTTP, DTOs de request/response de API, mappers, servicios

db/ — persistencia

Organizado una carpeta por tabla. Cada carpeta de tabla tiene exactamente tres sub-carpetas:

db/<tableName>/
├── model/
│   ├── <EntityName>Db.java          # entidad JPA principal
│   └── <Other>Db.java               # sub-modelos (tipos embebidos, entidades hijas, proyecciones)
├── dao/
│   └── <EntityName>Dao.java         # acceso a datos de bajo nivel (consultas Criteria/JPQL/nativas)
└── repository/
    ├── <EntityName>Repo.java        # interfaz PÚBLICA — el único tipo que ve capabilities
    └── impl/
        └── <EntityName>RepoImpl.java # compone Spring Data + Dao detrás de la interfaz

Convención de nomenclatura

CapaSufijoEjemplo
Entidad JPADbTestcaseDb
Interfaz de repositorioRepoTestcaseRepo
Implementación de repositorioRepoImplTestcaseRepoImpl
Helper de acceso a datosDaoTestcaseDao

Regla de uso de DB

Capabilities debe depender solo de interfaces *Repo. *RepoImpl y *Dao son internos — nunca se inyectan o referencian desde fuera del paquete db/<tableName>/. Esto es lo que mantiene la capa de persistencia intercambiable y las pruebas de capabilities enfocadas en el contrato.

  • Depende de: nada en este servicio
  • Expone: interfaces *Repo y tipos de modelo *Db
  • Nunca contiene: lógica de negocio, transformación a DTOs de API

client/ — integraciones salientes

Contiene el código que llama a otros servicios de los que este servicio depende. Principalmente una sub-carpeta por microservicio dependiente en nuestra propia plataforma (paymentService, userService, executionService, …). Los envoltorios de SDK de terceros o productores de mensajes también pueden vivir aquí, pero el patrón dominante — y la razón por la que existe este módulo — son los clientes de microservicios dependientes.

client/<microserviceName>/
├── <Microservice>Client.java       # interfaz — de lo que depende capabilities
├── impl/
│   └── <Microservice>ClientImpl.java   # implementación: llamada HTTP/gRPC/SDK, reintentos, timeouts
├── dto/                            # formas de request/response para la API de ESTE microservicio
│   ├── request/
│   └── response/
└── mapper/                         # opcional: traduce entre sus formas y las nuestras

Reglas

  • Una sub-carpeta por microservicio dependiente, nombrada según el servicio. No agrupe múltiples microservicios en un solo paquete, incluso si están "relacionados".

  • Capabilities depende solo de interfaces *Client. *ClientImpl es interno — misma regla que *Repo / *RepoImpl.

  • La interfaz del cliente expone la API del microservicio en términos de sus propios DTOs; capabilities puede adaptarlos más si es necesario.

  • Las preocupaciones transversales (reintentos, circuit breaking, registro de request/response, cabeceras de autenticación) pertenecen a impl/ — capabilities nunca debe verlas.

  • Depende de: nada en este servicio

  • Expone: interfaces *Client y sus DTOs de request/response

  • Nunca contiene: lógica de negocio, orquestación de capabilities

starter/ — raíz de composición

El único módulo que se ejecuta. Contiene @SpringBootApplication, manejadores globales de excepciones REST, configuración de seguridad, filtros y application.yml. Sin controladores — esos viven en capabilities/.

  • Depende de: capabilities (y transitivamente db, client)
  • Posee: el artefacto desplegable, configuración web transversal (seguridad, CORS, manejo de errores)
  • Nunca posee: controladores de funcionalidad o DTOs de funcionalidad

Regla de dependencias

starter → capabilities → { db, client }

Las flechas van en una dirección. db y client nunca importan de capabilities o starter. capabilities nunca importa de starter.

Controladores

Hay un controlador por funcionalidad, no por sub-funcionalidad, y vive en la raíz del paquete de la funcionalidad dentro de capabilities/. Está dividido en dos archivos, reflejando la división *Repo / *RepoImpl:

  • capabilities/<feature>/<Feature>Controller.java — interfaz. Declara las rutas, verbos HTTP, parámetros de path/query, tipos de DTO de request/response y anotaciones de documentación de API (OpenAPI/Swagger). Cualquiera que lea la interfaz debe ver todo el contrato HTTP.
  • capabilities/<feature>/<Feature>ControllerImpl.java — implementación. Lleva la anotación del framework (@RestController en Spring), es el objetivo de inyección para los servicios de sub-funcionalidad y contiene un método corto por ruta que llama al service/ correspondiente y devuelve el resultado. Sin lógica más allá de la delegación.

¿Por qué la división? La interfaz es el contrato de API — estable, documentado, comprobable de forma aislada. La implementación es cableado — puede cambiar sin cambiar el contrato. Misma razón que *Repo vs *RepoImpl.

El controlador delega cada ruta HTTP al servicio de sub-funcionalidad correspondiente en capabilities/<feature>/<sub-feature>/service/. Los DTOs de request/response son propiedad de la sub-funcionalidad (capabilities/<feature>/<sub-feature>/dto/); el controlador es solo un adaptador HTTP ligero.

¿Por qué dentro de capabilities/ y no en starter/? El controlador, su servicio y sus DTOs cambian juntos cuando una API cambia — mantenerlos en un solo módulo preserva la promesa de paquete-por-funcionalidad de "una funcionalidad, un lugar". starter/ está reservado para cableado transversal (seguridad, manejadores de errores, punto de entrada de la aplicación), no para código web por funcionalidad.

Ejemplo práctico: una funcionalidad testcase

El árbol a continuación usa extensiones .java como ilustración. Sustituya la extensión de archivo y los modismos del framework de su lenguaje — Kotlin .kt, paquetes Go, módulos Python, etc. — pero mantenga la estructura de carpetas y los sufijos de nomenclatura idénticos.

testcase-service/
├── capabilities/
│   └── com/example/testcase/capabilities/
│       └── testcase/                          # funcionalidad principal
│           ├── TestcaseController.java        # interfaz — contrato HTTP, declaraciones de rutas, docs de API
│           ├── TestcaseControllerImpl.java    # implementación — @RestController, delega a servicios de sub-funcionalidad
│           ├── getById/                       # sub-funcionalidad = una API
│           │   ├── service/
│           │   │   └── GetTestcaseByIdService.java
│           │   ├── dto/
│           │   │   └── response/
│           │   │       └── GetTestcaseByIdResponse.java
│           │   └── mapper/
│           │       └── GetTestcaseByIdMapper.java     # TestcaseDb → respuesta
│           ├── create/
│           │   ├── service/
│           │   │   └── CreateTestcaseService.java
│           │   ├── dto/
│           │   │   ├── request/
│           │   │   │   └── CreateTestcaseRequest.java
│           │   │   └── response/
│           │   │       └── CreateTestcaseResponse.java
│           │   └── mapper/
│           │       └── CreateTestcaseMapper.java      # request → TestcaseDb, TestcaseDb → respuesta
│           ├── 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/                                        # una carpeta por tabla
│   └── com/example/testcase/db/
│       └── testcase/                          # nombre de tabla
│           ├── model/
│           │   ├── TestcaseDb.java            # entidad JPA — principal
│           │   └── TestcaseStepDb.java        # sub-modelo (hijo/embebido)
│           ├── dao/
│           │   └── TestcaseDao.java           # helper de consultas/criteria, uso de paquete-privado
│           └── repository/
│               ├── TestcaseRepo.java          # interfaz — capabilities depende de ESTO
│               └── impl/
│                   └── TestcaseRepoImpl.java  # compone Spring Data + TestcaseDao
│
├── client/                                   # una carpeta por microservicio dependiente
│   └── com/example/testcase/client/
│       ├── executionService/                 # microservicio dependiente al que llamamos
│       │   ├── ExecutionServiceClient.java        # interfaz
│       │   ├── impl/
│       │   │   └── ExecutionServiceClientImpl.java  # llamada HTTP/gRPC, reintentos, timeouts
│       │   ├── dto/
│       │   │   ├── request/
│       │   │   │   └── RunTestcaseRequest.java
│       │   │   └── response/
│       │   │       └── RunTestcaseResponse.java
│       │   └── mapper/
│       │       └── ExecutionServiceMapper.java
│       └── userService/                      # otro microservicio dependiente
│           ├── UserServiceClient.java
│           └── impl/
│               └── UserServiceClientImpl.java
│
└── starter/                                  # solo cableado transversal — sin controladores
    └── com/example/testcase/
        ├── TestcaseServiceApplication.java
        ├── config/
        │   ├── SecurityConfig.java
        │   └── WebConfig.java
        ├── error/
        │   └── GlobalExceptionHandler.java   # @ControllerAdvice — transversal, no por funcionalidad
        └── resources/application.yml

Reglas prácticas

  • ¿Añadir una nueva API a una funcionalidad existente? Cree un nuevo paquete de sub-funcionalidad dentro de capabilities/<feature>/ con su propio service/, dto/{request,response}/ y mapper/, luego agregue la declaración de la ruta a <Feature>Controller.java (interfaz) y el método delegante a <Feature>ControllerImpl.java. No cree un segundo controlador.
  • ¿Añadir una funcionalidad completamente nueva? Cree el paquete de la funcionalidad en capabilities/ (controlador en la raíz, sub-funcionalidades por API) y la carpeta de tabla correspondiente en db/<tableName>/ con model/, dao/, repository/{impl/}. Añada un cliente solo si llama a un sistema externo. No toque starter/.
  • Siempre inyecte *Repo, nunca *RepoImpl o *Dao. Capabilities depende solo de la interfaz. *RepoImpl y *Dao son internos de db/<tableName>/ — existen para permitirle cambiar la estrategia de consulta o el almacenamiento subyacente sin cambiar el código de capabilities.
  • Siempre inyecte *Client, nunca *ClientImpl. Misma regla para llamadas a microservicios dependientes. Capabilities depende de la interfaz; el transporte (HTTP, gRPC, SDK) vive detrás de impl/.
  • Una carpeta por microservicio dependiente dentro de client/. Nómbrela según el microservicio. No agrupe "todos los servicios internos" en un solo paquete.
  • La granularidad de sub-funcionalidad se detiene en capabilities/. db/ se organiza por tabla; client/ por sistema externo. Dividir cualquiera de ellos por API dispersaría el código cohesivo.
  • Los DTOs de request/response y su mapper son por sub-funcionalidad, nunca compartidos. Si create y updateById se parecen hoy, mantenga dos DTOs y dos mappers. Compartir acopla dos APIs que divergirán independientemente.
  • Los mappers convierten entidad ↔ DTO directamente. Sin capa de modelo de dominio intermedio. <SubFeature>Mapper convierte *Db ↔ los DTOs de request/response de esa sub-funcionalidad.
  • ¿Tres sub-funcionalidades tienen código similar? Resista la tentación de extraer un servicio o mapper compartido hasta que tenga el mismo patrón en cuatro o más lugares.
  • ¿Reutilización entre funcionalidades? Ponga las utilidades compartidas en un paquete common/ dentro de capabilities/ si son de lógica de negocio, o en client/ si son de comunicación. No cree un módulo separado para ello.
  • ¿Añadir un nuevo microservicio dependiente? Cree una carpeta en client/<microserviceName>/ con *Client.java (interfaz), impl/, dto/{request,response}/ y opcionalmente mapper/. Siga el mismo patrón de división interfaz/implementación.
  • Nunca importe un tipo de starter/ desde capabilities/. starter/ es el consumidor final; capabilities/ no sabe nada sobre cómo se despliega, asegura o ejecuta la aplicación.
  • Los manejadores globales de excepciones, filtros y configuraciones de seguridad pertenecen a starter/. Si un manejador de errores se vuelve específico de una funcionalidad, muévalo al paquete de esa funcionalidad en capabilities/. Pero la línea base — @ControllerAdvice que captura Exception — se queda en starter/.
  • Un solo application.yml en starter/. No distribuya archivos de propiedades entre módulos. Use perfiles de Spring (application-{profile}.yml) para diferencias entre entornos, pero todos viven en starter/resources/.
  • Las migraciones de base de datos (Flyway/Liquibase) se colocan en db/src/main/resources/db/migration/. No en starter/. El módulo db posee el esquema y las migraciones; starter/ solo las ejecuta.
  • La modularidad es un firewall, no un dogma. Si una clase de capabilities/ realmente necesita un bean de configuración definido en starter/ (por ejemplo, un RestTemplate configurado), exponga una interfaz en capabilities/ e implemente la configuración en starter/. No importe el bean de configuración directamente.
  • Los tests unitarios viven en el mismo módulo que el código que prueban. Los tests de *Repo están en db/src/test/, los tests de *Service están en capabilities/src/test/, los tests de integración que arrancan toda la aplicación están en starter/src/test/.

Individual skills in this repo

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

Skills relacionados