Contributing to Smartsapp¶
Guide for adding new backend modules to the Spring Boot system.
Prerequisites¶
- Java 21+
- Podman (or Docker) — for local infrastructure and tests
- Git hooks — run once after cloning:
git config core.hooksPath .githooks
1. Decide where your module lives¶
| Category | Path | What goes here | Examples |
|---|---|---|---|
| Customer | modules/customer/<name>/ |
Feature modules — business logic for end users | canteen, school, student attendance, invoice |
| Platform | modules/platform/<name>/ |
Wrappers around OSS tooling that support the app | auth (Keycloak), chat, notifications (Novu), wallet (Formance) |
| Shared | modules/shared/ |
Cross-cutting utilities used by all modules | PagedResponse, ResourceNotFoundException |
Package names cannot contain hyphens: academic-report → academicreport.
2. Documentation first¶
Complete these before writing code.
Product Requirements (PRD)¶
- Get the module PRD from the product team (or write one). Save it as a PDF in the module's docs folder:
docs/modules/<name>/<Name>_PRD.pdf - Add a link to docs/PRD.md in the module index table.
If the PRD already exists, review it with the product team and update if needed. Do not start implementation against an outdated PRD.
C4 Architecture¶
Create a C4 architecture doc in the module's docs folder:
docs/modules/<name>/<name>-c4-architecture.md
Include (using Mermaid diagrams): - C3 Component diagram — controllers, services, repositories, events - C3.1 Entity Relationship Model — database schema - C3.2 REST API structure — endpoints, HTTP methods, pagination - C3.3 Event flow — Kafka topics, event payloads - C3.4 Caching strategy — Redis annotations and cache names
Use an existing module's C4 doc as a template:
docs/modules/school/school-c4-architecture.md
AI-assisted generation: After creating the PRD, prompt your AI agent:
Generate C4 architecture docs for the [module] module based on the PRD, following the school module's C4 doc as a template.
Then update the system-level docs/c4-architecture.md to include the new module in the C2 container diagram.
3. Implement the module¶
Directory structure¶
Create the package tree under modules/customer/<name>/ (or platform/<name>/):
<name>/
├── config/ Spring @Configuration beans
├── constants/ Static final values (API paths, Kafka topics, cache names)
├── controllers/ REST controllers
│ └── dto/
│ ├── request/ Inbound request bodies (records)
│ └── response/ Outbound response bodies (records)
├── entities/ JPA entities and enums
├── events/
│ ├── listeners/ @KafkaListener consumers
│ ├── messages/ Event payload records
│ └── producers/ KafkaTemplate publishers
├── mappers/ Entity <-> DTO conversion (@Component)
├── repositories/ Spring Data JPA interfaces
└── services/ Business logic (@Service)
See backend/src/main/java/com/smartsapp/system/modules/sample/README.md for a complete working reference.
Naming conventions¶
| Layer | Pattern | Example |
|---|---|---|
| Entities | ClassName |
Campus, Item |
| Repositories | *Repository |
CampusRepository |
| Services | *Service |
CampusService |
| Controllers | *Controller |
CampusController |
| Request DTOs | Create*Request / Update*Request |
CreateCampusRequest |
| Response DTOs | *Response |
CampusResponse |
| Event payloads | *Event |
CampusEvent |
| Producers | *EventProducer |
CampusEventProducer |
| Listeners | *EventListener |
CampusEventListener |
Register with OpenAPI¶
Add a GroupedOpenApi bean in backend/src/main/java/com/smartsapp/system/config/OpenApiConfig.java:
@Bean
public GroupedOpenApi customerNewModuleGroup() {
return GroupedOpenApi.builder()
.group("customer-newmodule")
.displayName("Customer – New Module")
.packagesToScan("com.smartsapp.system.modules.customer.newmodule.controllers")
.build();
}
Architecture rules¶
These are enforced by ArchUnit — violations fail the build:
- Cross-module imports: Only
servicesandentitiesfrom other modules. Everything else (controllers, repositories, events, mappers, config) is module-private. - Shared module: Always importable from any module.
Other conventions:
- Constructor injection — no @Autowired on fields
- DTOs as Java records (immutable, no boilerplate)
- Use PagedResponse<T> from shared.dto for paginated endpoints
- Throw ResourceNotFoundException from shared.exceptions for 404s
- Define API paths, Kafka topics, and cache names in constants/
Kafka setup¶
If your module produces or consumes events:
- Define topic names in
constants/KafkaTopics.java - Add your events package to the trusted packages in
application.yml:spring.json.trusted.packages: ...,com.smartsapp.system.modules.customer.<name>.events.messages
4. Add integration tests¶
Tests use real infrastructure via Testcontainers — no mocking.
Extend the shared base class¶
class NewModuleControllerTest extends IntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private NewModuleRepository repository;
@BeforeEach
void setUp() {
repository.deleteAll();
}
@Test
void list_returnsPaginatedResults() throws Exception {
// ...
}
}
IntegrationTest provides shared Postgres, Kafka, and Redis containers that start once for the entire test suite. Do not define your own @Container fields.
AI-assisted test generation¶
After implementing your module, prompt your AI agent:
Add integration tests for the [module] controller extending IntegrationTest, following the TodoControllerTest pattern.
Coverage requirement¶
JaCoCo enforces 85% minimum line coverage. The build fails if coverage drops below this threshold.
View the coverage report after running tests:
open build/reports/jacoco/test/html/index.html
5. Commit and push¶
Follow Conventional Commits format (enforced by git hook):
feat(school): add campus CRUD endpoints
fix(canteen): handle null menu item price
docs(school): add C4 architecture doc
test(school): add campus controller integration tests
The pre-commit hook automatically runs tests when backend files are staged.
Checklist¶
Before opening a PR, verify:
- [ ] Module PRD exists in
docs/modules/<name>/and is linked indocs/PRD.md - [ ] C4 architecture doc created in
docs/modules/<name>/ - [ ] System C4 (
docs/c4-architecture.md) updated with new module - [ ] Module follows the sample module directory structure
- [ ]
OpenApiConfig.javaupdated with newGroupedOpenApibean - [ ] Kafka trusted packages updated in
application.yml(if using events) - [ ] Integration tests extend
IntegrationTest(no per-class containers) - [ ] Test coverage >= 85%
- [ ] All tests pass locally (
./gradlew test) - [ ] Commits follow Conventional Commits format