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:
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.
<?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.
package com.icom.icosys.hrm.types;
public enum HrmEmployeeStatus {
ACTIVE,
ON_LEAVE,
TERMINATED
}
<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¶
@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_idis reserved — userefEntityIdinstead - Every entity must implement
Serializablewith explicitserialVersionUID - Inherited fields from
AbstractICEntity6:crProcess,version,xrefId,creationDate,createdBy,lastUpdateDate,lastUpdatedBy
DDL Script¶
Document all CREATE TABLE statements:
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¶
@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¶
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¶
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
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}
cors.allowed-origins=https://test.icosys.com
cors.allowed-methods=GET,POST,PUT,DELETE
cors.allowed-headers=Authorization,Content-Type
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¶
IC-HRM-2001=Required field is missing
IC-HRM-2002=Employee email already exists
IC-HRM-4001=Employee not found
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¶
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¶
3. Create Axios Instance¶
Add a new service client in service-clients.ts:
4. Register Routes¶
Add entity routes to service-registry.ts:
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 extendICEntity6FilterDto - [ ] Converters use
dto.mapSuperFields(entity)infrom()method - [ ] Service has
beforeCreate()hook setting CrProcess - [ ] Specification filters by
crProcess.idas first predicate - [ ] DDL scripts in
docs/database/ddl_scripts.md - [ ]
messages.properties+messages_tr.propertiescreated - [ ] All error messages use
ServiceErrorMsgCodesconstants - [ ] No
@SuperBuilder— using@Builder+ setter - [ ] No
entity_idcolumn name - [ ]
@Transactionalonly 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