Backend Developer Guide — Membership One
Overview
Membership One is a modular monolith built with Java 23 and Spring Boot 4.0.2. The backend consists of 17 Maven modules, each representing a bounded context. All modules share a common core (membership-core) and are assembled into a single deployable JAR via membership-runner.
Project Structure
backend/
├── pom.xml ← Root aggregator POM
├── membership-parent/ ← Parent POM (dependency management)
├── membership-core/ ← Base framework: security, JPA, i18n, exceptions
├── membership-entity/ ← Organization/tenant management
├── membership-auth/ ← JWT RS256, login, registration, brute-force protection
├── membership-member/ ← Member CRUD, search, audit trail
├── membership-contract/ ← Membership templates, contracts, state machine
├── membership-product/ ← Product catalog, VAT, stock management
├── membership-payment/ ← (Phase 2) Billing, Cash360 integration
├── membership-resource/ ← (Phase 5) Rooms, courts, equipment booking
├── membership-event/ ← (Phase 6) Events, courses, scheduling
├── membership-communication/ ← (Phase 6) Email, push, templates
├── membership-checkin/ ← (Phase 5) QR, NFC, access control
├── membership-import/ ← (Phase 7) CSV import, mapping engine
├── membership-document/ ← (Phase 7) PDF generation, file storage
├── membership-crm/ ← (Phase 8) Leads, deals, pipeline
├── membership-support/ ← (Phase 8) Tickets, SLA, knowledge base
├── membership-accounting/ ← (Phase 8) DATEV, chart of accounts
├── membership-runner/ ← Application entry point
└── docker-compose.yml ← Local infrastructure
Quick Start
Prerequisites
- JDK 23+ (Temurin recommended)
- Docker Desktop
- Maven (wrapper included)
Start Infrastructure
cd backend
docker compose up -d
# Starts: PostgreSQL 18 (port 5433), Redis 7 (port 6379), RabbitMQ 4 (port 5672/15672)
Build & Run
cd backend
./mvnw clean install # Full build + tests
./mvnw -pl membership-runner -am spring-boot:run # Run application
Verify
- API: http://localhost:8081/api/health
- Swagger UI: http://localhost:8081/api/swagger-ui.html
Architecture
Module Dependencies
membership-runner
├── membership-contract → membership-member, membership-entity, membership-core
├── membership-auth → membership-entity, membership-core
├── membership-product → membership-core
├── membership-member → membership-entity, membership-core
├── membership-entity → membership-core
└── membership-core (base framework)
Layer Pattern (per module)
Each module follows a consistent slice:
| Layer | Package | Responsibility |
|---|---|---|
| Entity | entity/ |
JPA entities extending BaseEntity |
| DTO | dto/ |
Records for request/response (immutable) |
| Repository | repository/ |
Spring Data JPA interfaces |
| Mapper | mapper/ |
Entity ↔ DTO mapping (Spring @Component) |
| Service | service/ |
Business logic (@Transactional) |
| Controller | controller/ |
REST endpoints (@RestController) |
Base Entity
All entities extend BaseEntity which provides:
- id (Long) — auto-generated sequence
- version (Long) — @Version for optimistic locking (mandatory)
- createdAt (Instant) — auto-set via JPA auditing
- updatedAt (Instant) — auto-set via JPA auditing
Multi-Tenancy
Tenant isolation is enforced via entityId on all business entities:
- JWT token includes tenantId claim
- TenantContext (ThreadLocal) holds current tenant
- Controllers extract tenant from AuthenticatedUser.tenantId()
- All repository queries filter by entityId
Conventions
Naming
- Packages:
com.membership.<module>.<layer> - Entities: PascalCase domain names (e.g.,
Member,Contract) - DTOs:
*Dto,Create*Request,Update*Request - Services:
*Service(noImplsuffix) - Tests:
*Testwith method naming:methodName_Condition_Expected
Database
- Schema:
public - Table names: snake_case (e.g.,
membership_template,app_user) - Column names: snake_case (e.g.,
entity_id,contact_email) - Sequences:
{table}_seqwith allocation size 50 - JSONB: for extensible attributes (custom fields, settings, features)
- Reserved words:
user→app_user
Flyway Migrations
- Naming:
V{PPP}__{description}.sqlwhere PPP = phase-based prefix - V000-V099: Phase 0 (seed data)
- V100-V199: Phase 1 (core backend)
- V200-V299: Phase 2 (billing)
- Location:
src/main/resources/db/migration/per module
API Conventions
- Context path:
/api - Authentication: Bearer JWT in
Authorizationheader - Pagination: Spring Data
Pageable(page, size, sort params) - Error responses:
ErrorResponserecord with code, message, details - Public endpoints: catalog, auth (login/register/verify/forgot/reset)
Security (Non-Negotiable)
- JWT: RS256 asymmetric keys (auto-generated in dev mode)
- Passwords: bcrypt, cost factor 12
- CORS: explicit origin whitelist
@Versionon every entityBigDecimal(19,4)for all monetary values- Same error message for wrong email/password (prevent enumeration)
- No PII in application logs
Testing
Test Pattern
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
@Mock private MyRepository repository;
@InjectMocks private MyService service;
@Test
void methodName_Condition_Expected() {
// Arrange
when(repository.findById(1L)).thenReturn(Optional.of(entity));
// Act
var result = service.getById(1L);
// Assert
assertNotNull(result);
assertEquals("expected", result.name());
}
}
- Framework: JUnit 5 + Mockito
- Assertions: JUnit 5 only (NOT AssertJ)
- Pattern: Arrange-Act-Assert (AAA)
- Service tests: @Mock repos, @InjectMocks service
- Controller tests: @Mock service, @InjectMocks controller
Run Tests
./mvnw test # All tests
./mvnw test -pl membership-auth # Single module
./mvnw verify # With JaCoCo coverage
Configuration
Profiles
| Profile | Purpose | DB Port |
|---|---|---|
local |
Local development | 5433 |
dev |
Development server | 5432 |
test |
Automated testing | Testcontainers |
staging |
Staging environment | 5432 |
prod |
Production | 5432 |
Environment Variables
| Variable | Default | Description |
|---|---|---|
DB_URL |
localhost | PostgreSQL host |
DB_PORT |
5432 | PostgreSQL port |
DB_NAME |
membership | Database name |
DB_USERNAME |
membership | DB username |
DB_PASSWORD |
membership | DB password |
REDIS_HOST |
localhost | Redis host |
RMQ_HOST |
localhost | RabbitMQ host |
JWT_PRIVATE_KEY_PATH |
(auto-gen) | RS256 private key file |
JWT_PUBLIC_KEY_PATH |
(auto-gen) | RS256 public key file |
CORS_ORIGINS |
localhost:3000,5000 | Allowed CORS origins |
Current Status (Phase 1)
| Module | Status | Entities | Endpoints | Tests |
|---|---|---|---|---|
| membership-core | Complete | BaseEntity, seed (8) | /health | 0 |
| membership-entity | Complete | Organization | 7 | 9 |
| membership-auth | Complete | User, UserRole | 10 | 12 |
| membership-member | Complete | Member, AuditLog | 7 | 10 |
| membership-contract | Complete | MembershipTemplate, Contract | 10 | 16 |
| membership-product | Complete | Product | 6 | 10 |