Skip to content

New Module Guide

This guide walks you through creating a complete backend service module from scratch. A module is a self-contained microservice with its own database schema, entities, DTOs, and REST API.

Slash Command

You can scaffold a module automatically with the Claude Code command:

/new-backend-module HRM

This guide explains what the scaffold creates and why.


Overview

Every module produces four Maven sub-projects under a parent aggregator:

ICoSys-{MODULE}/                          # Parent POM
├── ICoSys-{MODULE}-Types-1.0/            # Enums (no Spring)
├── ICoSys-{MODULE}-Entities-1.0/         # JPA Entities
│   └── docs/database/ddl_scripts.md
├── ICoSys-{MODULE}-Entities-Dto-1.0/     # DTOs
└── ICoSys-{MODULE}-Services-1.0/         # Spring Boot service

Naming Scheme

Item Pattern Example (HRM)
Parent directory ICoSys-{MODULE}/ ICoSys-HRM/
Module prefix ic{mod} ichrm
DB schema ic{mod} ichrm
ID table s_ic{mod}_id s_ichrm_id
Table prefix {mod}_ hrm_
Java package com.icom.icosys.{mod} com.icom.icosys.hrm
Context path /ic{mod}/services /ichrm/services
Service port 80{XX} 8040

Step 1 — Parent POM

The parent POM aggregates all sub-projects and defines shared properties.

ICoSys-HRM/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.icom.icosys</groupId>
    <artifactId>icosys-hrm</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <name>ICoSys HRM</name>

    <modules>
        <module>ICoSys-HRM-Types-1.0</module>
        <module>ICoSys-HRM-Entities-1.0</module>
        <module>ICoSys-HRM-Entities-Dto-1.0</module>
        <module>ICoSys-HRM-Services-1.0</module>
    </modules>
</project>

Step 2 — Types Sub-Project

Contains enums shared across entities and DTOs. Has no Spring dependency.

ICoSys-HRM-Types-1.0/
└── src/main/java/com/icom/icosys/hrm/types/
    └── HrmEmployeeStatus.java
HrmEmployeeStatus.java
package com.icom.icosys.hrm.types;

public enum HrmEmployeeStatus {
    ACTIVE,
    ON_LEAVE,
    TERMINATED
}
pom.xml (minimal)
<artifactId>icosys-hrm-types</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<!-- No Spring dependencies -->

Step 3 — Entities Sub-Project

JPA entity classes extending AbstractICEntity6.

ICoSys-HRM-Entities-1.0/
├── src/main/java/com/icom/icosys/hrm/entity/
│   └── HrmEmployee.java
└── docs/database/
    └── ddl_scripts.md

Entity Template

HrmEmployee.java
@Entity
@Table(name = "hrm_employee", schema = "ichrm", catalog = "ichrm")
@TableGenerator(
    name = "HrmEmployee_ID_GEN",
    schema = "ichrm", catalog = "ichrm",
    table = "s_ichrm_id",
    pkColumnName = "table_name",
    pkColumnValue = "hrm_employee",
    valueColumnName = "unique_id",
    initialValue = 0, allocationSize = 1
)
@DynamicInsert @DynamicUpdate
@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class HrmEmployee extends AbstractICEntity6 implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "HrmEmployee_ID_GEN")
    @Column(name = "id", unique = true, nullable = false)
    @EqualsAndHashCode.Include
    private Long id;

    @Column(name = "first_name", nullable = false, length = 100)
    private String firstName;

    @Column(name = "last_name", nullable = false, length = 100)
    private String lastName;

    @Enumerated(EnumType.STRING)
    @Column(name = "status", length = 20)
    private HrmEmployeeStatus status;
}

Entity Rules

  • Always use @Builder, never @SuperBuilder
  • Column name entity_id is reserved — use refEntityId instead
  • Every entity must implement Serializable with explicit serialVersionUID
  • Inherited fields from AbstractICEntity6: crProcess, version, xrefId, creationDate, createdBy, lastUpdateDate, lastUpdatedBy

DDL Script

Document all CREATE TABLE statements:

docs/database/ddl_scripts.md
CREATE DATABASE IF NOT EXISTS ichrm
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE ichrm;

CREATE TABLE s_ichrm_id (
    table_name  VARCHAR(255) NOT NULL,
    unique_id   BIGINT       NOT NULL DEFAULT 0,
    PRIMARY KEY (table_name)
) ENGINE=InnoDB;

INSERT INTO s_ichrm_id (table_name, unique_id) VALUES ('hrm_employee', 0);

CREATE TABLE hrm_employee (
    id               BIGINT       NOT NULL,
    first_name       VARCHAR(100) NOT NULL,
    last_name        VARCHAR(100) NOT NULL,
    status           VARCHAR(20),
    -- Inherited from AbstractICEntity6
    cr_process_id    BIGINT       NOT NULL,
    version          BIGINT       NOT NULL DEFAULT 0,
    xref_id          VARCHAR(255),
    creation_date    DATETIME(6),
    created_by       VARCHAR(255),
    last_update_date DATETIME(6),
    last_updated_by  VARCHAR(255),
    PRIMARY KEY (id),
    CONSTRAINT fk_hrm_employee_crp
        FOREIGN KEY (cr_process_id) REFERENCES icglb2.cr_process(id)
) ENGINE=InnoDB;

Inherited Columns (Always Include)

Every entity table must include these columns from AbstractICEntity6:

Column Type Nullable Source
cr_process_id BIGINT NOT NULL Tenant (FK to icglb2.cr_process)
version BIGINT NOT NULL Optimistic locking (@Version)
xref_id VARCHAR(255) YES UUID (auto @PrePersist)
creation_date DATETIME(6) YES Audit timestamp
created_by VARCHAR(255) YES Audit user
last_update_date DATETIME(6) YES Audit timestamp
last_updated_by VARCHAR(255) YES Audit user

Step 4 — DTOs Sub-Project

Three DTO types per entity:

ICoSys-HRM-Entities-Dto-1.0/
└── src/main/java/com/icom/icosys/hrm/dto/
    ├── HrmEmployeeEditDto.java
    ├── HrmEmployeeListDto.java
    └── HrmEmployeeFilterDto.java

EditDto (extends ICEntity6Dto)

Used for create and update operations. Inherits id, crProcessId, version.

@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
public class HrmEmployeeEditDto extends ICEntity6Dto {
    private String firstName;
    private String lastName;
    private HrmEmployeeStatus status;
}

ListDto (extends ICEntity6Dto)

Used for table display. Can have fewer fields than EditDto.

@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
public class HrmEmployeeListDto extends ICEntity6Dto {
    private String firstName;
    private String lastName;
    private HrmEmployeeStatus status;
}

FilterDto (extends ICEntity6FilterDto)

Used for list filtering. Inherits crProcessId.

@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
public class HrmEmployeeFilterDto extends ICEntity6FilterDto {
    private HrmEmployeeStatus status;
}

Step 5 — Service Sub-Project

The Spring Boot application with controllers, services, converters, specs, and repos.

ICoSys-HRM-Services-1.0/
├── src/main/java/com/icom/icosys/hrm/service/
│   ├── IcHrmServicesApplication.java
│   ├── codes/
│   │   └── ServiceErrorMsgCodes.java
│   └── zapi/employee/
│       ├── HrmEmployeeRepository.java
│       ├── controller/
│       │   └── HrmEmployeeController.java
│       ├── service/
│       │   └── HrmEmployeeService.java
│       ├── converter/
│       │   ├── HrmEmployeeEditDtoConverter.java
│       │   └── HrmEmployeeListDtoConverter.java
│       └── specification/
│           └── HrmEmployeeSpecification.java
└── src/main/resources/
    ├── application.properties
    ├── application-dev.properties
    ├── application-test.properties
    ├── application-prod.properties
    └── messages/
        ├── messages.properties
        └── messages_tr.properties

Application Class

IcHrmServicesApplication.java
@SpringBootApplication
@EntityScan(basePackages = {
    "com.icom.icosys.hrm.entity",
    "com.icom.icglb.entity"          // For CrProcess
})
@EnableJpaRepositories(basePackages = "com.icom.icosys.hrm.service")
@ComponentScan(basePackages = {
    "com.icom.icosys.hrm.service",
    "com.icom.micro.services"         // Shared filters, handlers
})
public class IcHrmServicesApplication {
    public static void main(String[] args) {
        SpringApplication.run(IcHrmServicesApplication.class, args);
    }
}

Error Message Codes

ServiceErrorMsgCodes.java
public final class ServiceErrorMsgCodes {

    // Validation
    public static final String FIELD_REQUIRED = "IC-HRM-2001";
    public static final String EMAIL_EXISTS = "IC-HRM-2002";

    // Data
    public static final String NOT_FOUND = "IC-HRM-4001";

    private ServiceErrorMsgCodes() {}
}

Properties Files

application.properties
server.servlet.context-path=/ichrm/services
spring.application.name=ichrm-services
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.open-in-view=false
spring.jackson.serialization.write-dates-as-timestamps=false
application-dev.properties
server.port=8040
spring.datasource.url=jdbc:mysql://localhost:3306/ichrm?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username=${DB_USERNAME:root}
spring.datasource.password=${DB_PASSWORD}
logging.file.name=C:/server/ICOSYS/log/ichrm/ichrm-services.log
cors.allowed-origins=http://localhost:8080,http://localhost:5174
cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
cors.allowed-headers=*
jwt.secret.key=${JWT_SECRET:dev-secret-change-in-production}
ims.security.api-key=${ICGLB_SECURITY_API_KEY:dev-secret-key-change-in-production-abc123xyz}
application-test.properties
cors.allowed-origins=https://test.icosys.com
cors.allowed-methods=GET,POST,PUT,DELETE
cors.allowed-headers=Authorization,Content-Type
application-prod.properties
cors.allowed-origins=https://app.icosys.com,https://admin.icosys.com
cors.allowed-methods=GET,POST,PUT,DELETE
cors.allowed-headers=Authorization,Content-Type

Message Files

messages/messages.properties
IC-HRM-2001=Required field is missing
IC-HRM-2002=Employee email already exists
IC-HRM-4001=Employee not found
messages/messages_tr.properties
IC-HRM-2001=Zorunlu alan eksik
IC-HRM-2002=Çalışan e-posta adresi zaten mevcut
IC-HRM-4001=Çalışan bulunamadı

Step 6 — Service Layer Components

Each entity needs these six components. See the First Module tutorial for complete code examples.

Component Description Reference
Repository ICrudRepository<E, Long> First Module — Step 3
EditDtoConverter IEditDtoConverter<E, EditDto> (3 methods) First Module — Step 4
ListDtoConverter IListDtoConverter<E, ListDto> (1 method) First Module — Step 4
Specification Dynamic JPA predicates from FilterDto First Module — Step 5
Service AbstractCrudService<E, Long, EditDto, ListDto, FilterDto> First Module — Step 6
Controller @RestController with 6 CRUD endpoints First Module — Step 7

Don't Duplicate

The First Module tutorial has complete, copy-pasteable code for every component. This guide focuses on module-level structure — refer there for implementation details.


Step 7 — Frontend Integration

After the backend is running, register the new service in the React frontend.

1. Add Vite Proxy

vite.config.ts
proxy: {
  '/icglb/services': 'http://localhost:8010',
  '/icbpm/services': 'http://localhost:8020',
  '/icdms/services': 'http://localhost:8030',
  '/ichrm/services': 'http://localhost:8040',  // New!
}

2. Add Environment Variable

.env.development
VITE_ICHRM_URL=/ichrm/services

3. Create Axios Instance

Add a new service client in service-clients.ts:

export const ichrmClient = createServiceClient(
  import.meta.env.VITE_ICHRM_URL
)

4. Register Routes

Add entity routes to service-registry.ts:

"hrm-employees": {
  service: "ichrm",
  apiPath: "/api/employee"
}

5. Create Handler Config

Create a handler config for the React CRUD engine — see the Config-Driven UI documentation.


Module Checklist

Before marking a module complete:

  • [ ] All four sub-projects compile: mvn clean install
  • [ ] All entities extend AbstractICEntity6
  • [ ] All EditDtos extend ICEntity6Dto, FilterDtos extend ICEntity6FilterDto
  • [ ] Converters use dto.mapSuperFields(entity) in from() method
  • [ ] Service has beforeCreate() hook setting CrProcess
  • [ ] Specification filters by crProcess.id as first predicate
  • [ ] DDL scripts in docs/database/ddl_scripts.md
  • [ ] messages.properties + messages_tr.properties created
  • [ ] All error messages use ServiceErrorMsgCodes constants
  • [ ] No @SuperBuilder — using @Builder + setter
  • [ ] No entity_id column name
  • [ ] @Transactional only on public methods
  • [ ] English JavaDoc on all public methods
  • [ ] Four properties files: common, dev, test, prod
  • [ ] Health check: curl localhost:{port}/ic{mod}/services/actuator/health

What's Next?

  • New Entity — Add more entities to this module
  • CRUD Engine — Lifecycle hooks and advanced patterns
  • Deployment — Deploy the module to test/production