Based on the ddd4j-boot project source code, codify the implicit conventions that LLMs do not know.
Why this skill is needed
LLM training data includes knowledge of the Spring Boot/MyBatis-Plus APIs, but it does not know the DDD4J project'sarchitectural conventions. Letting an LLM write code directly produces glue code that is architecturally correct but stylistically wrong. This skill codifies these rules.
Capability Boundaries
✅ Strong Suits
- Entity definition rules — must extend BaseEntity→Model(ActiveRecord), audit fields are already built in
- DDD annotation rules — distinguish SOURCE from RUNTIME annotations, know which annotation is already a Spring Bean
- Controller hierarchy — BaseController(success/fail/error)→BaseMapperController(Dozer injection)
- Service hierarchy — BaseServiceImpl already injects MessageSource/Cache/EventPublisher/Dozer
- Return value format — ApiRestResponse has three states (success/fail/error), pagination returns rows, not data
- Query parameter conventions — beginTime/endTime/keywords/params are on the Entity, not a separate QueryDTO
- Internationalization — NestedMessageSource, exception messages use keys rather than being hardcoded
⚠️ Requirements
- Entities must extend BaseEntity (or PaginationEntity), and must not directly extend Model
- Use
@DomainServiceinstead of@Service, use@ApplicationServiceinstead of application-layer@Service - Pagination queries use PaginationEntity; do not directly use MyBatis-Plus Page
❌ Out of Scope
- JSON serialization rules (null handling, date format, @Sensitive) → ddd4j-jackson
- MyBatis-Plus type handlers, @Param("model") convention → ddd4j-mybatis
- Custom validation annotations → ddd4j-validation
- Sa-Token extended usage (StpKit extended payload) → ddd4j-satoken
The 10 most common mistakes LLMs make
| # | Error | Correct approach |
|---|---|---|
| 1 | Treat @DomainEntity as a Spring Bean |
It is a SOURCE retention, only for documentation, not a @Component |
| 2 | Add @DomainService on top of @Service |
@DomainService already meta-annotates @Service; duplication causes problems |
| 3 | Create a separate QueryDTO to hold query parameters | Put beginTime/endTime/keywords/params directly on the Entity as extension fields |
| 4 | Pagination returns a data field |
Use rows(bootstrap-table protocol) |
| 5 | Directly use new Page<T>() for pagination |
Use PaginationEntity<T> encapsulation, default 15 per page |
| 6 | Write in MyBatis XML #{entity.beginTime} |
Should write #{model.beginTime}(@Param("model")) |
| 7 | Entity implements Serializable | Must extends BaseEntity<T extends Model<?>>(ActiveRecord) |
| 8 | Response returns {code:200,data:{}} |
Use BaseController.success/fail/error to return ApiRestResponse |
| 9 | Hardcode error messages | Use getMessage("error.key") NestedMessageSource |
| 10 | Manual setter/getter for object mapping | BaseMapperController has Dozer injected; use it directly |
Core Rules Quick Reference
// ✅ 正确:实体定义
@Data
@TableName("sys_user")
public class SysUser extends BaseEntity<SysUser> {
// createBy/createTime/updateBy/updateTime 已继承,不用再声明
// isDeleted 已继承(@TableLogic),不用再声明
// beginTime/endTime/keywords 已继承(@TableField(exist=false)),用于查询
private String username;
private String password;
}
// ✅ 正确:DDD 注解
@DomainEntity // SOURCE 保留 → 仅文档标注
public class Order { }
@DomainService // RUNTIME 保留 → 已经是 @Service,不要再加
public class OrderService { }
@ApplicationService // RUNTIME 保留 → 已经是 @Service,用于应用层
public class OrderAppService { }
// ✅ 正确:Controller 返回值
@RestController
public class UserController extends BaseMapperController {
@GetMapping("/list")
public ApiRestResponse<Result<UserDTO>> list(PaginationEntity<User> entity) {
List<User> list = getBaseService().getPagedList(new Page<>(entity.getPageNo(), entity.getLimit()), entity);
return success(new Result<>(list)); // 返回 {code:200, status:"success", rows:[...]}
}
}
// ✅ 正确:分页查询
PaginationEntity<User> entity = new PaginationEntity<>();
entity.setPageNo(1); entity.setLimit(15); entity.setBeginTime(LocalDateTime.now().minusDays(7));
List<User> list = baseService.getPagedList(new Page<>(1, 15), entity);
// XML: <select id="getPagedList"> WHERE create_time >= #{model.beginTime} </select>
Gotchas
- @DomainEntity cannot replace @Component/@Service — it is a documentation annotation with SOURCE retention
- @DomainService already includes @Service — do not add it again
- BaseEntity's beginTime/endTime are query helper fields — they are not persisted (@TableField(exist=false))
- PaginationEntity's getOffset() is Oracle-style —
((pageNo-1)*limit+1), not 0-based - ApiRestResponse has three states — success (200)/fail (business failure)/error (system error = 500)
- The pagination return field is rows, not data — required by the bootstrap-table protocol
- Audit fields are automatically populated on insert — createBy/createTime are handled by MyBatis-Plus FieldFill.INSERT
- updateBy/updateTime are automatically populated on update — handled by FieldFill.INSERT_UPDATE
- What BaseMapperController injects is Dozer — used for object mapping
mapper.map(source, Target.class) - All Controllers should extend BaseController or BaseMapperController — to get the success/fail/error methods
Data Privacy
This skill does not collect, store, or transmit any user data.