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 (no Impl suffix)
  • Tests: *Test with 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}_seq with allocation size 50
  • JSONB: for extensible attributes (custom fields, settings, features)
  • Reserved words: userapp_user

Flyway Migrations

  • Naming: V{PPP}__{description}.sql where 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 Authorization header
  • Pagination: Spring Data Pageable (page, size, sort params)
  • Error responses: ErrorResponse record 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
  • @Version on every entity
  • BigDecimal(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