eva4j 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +426 -9
- package/DOMAIN_YAML_GUIDE.md +374 -21
- package/FUTURE_FEATURES.md +94 -1
- package/docs/commands/GENERATE_ENTITIES.md +252 -2404
- package/docs/commands/GENERATE_RECORD.md +335 -311
- package/examples/doctor-evaluation.yaml +3 -3
- package/examples/domain-audit-complete.yaml +2 -2
- package/examples/domain-collections.yaml +2 -2
- package/examples/domain-ecommerce.yaml +2 -2
- package/examples/domain-events.yaml +1 -1
- package/examples/domain-field-visibility.yaml +11 -5
- package/examples/domain-multi-aggregate.yaml +6 -6
- package/examples/domain-one-to-many.yaml +1 -1
- package/examples/domain-one-to-one.yaml +1 -1
- package/examples/domain-secondary-onetomany.yaml +1 -1
- package/examples/domain-secondary-onetoone.yaml +1 -1
- package/examples/domain-simple.yaml +1 -1
- package/examples/domain-soft-delete.yaml +3 -3
- package/examples/domain-transitions.yaml +1 -1
- package/examples/domain-value-objects.yaml +1 -1
- package/package.json +1 -1
- package/src/utils/yaml-to-entity.js +69 -2
- package/templates/aggregate/AggregateRoot.java.ejs +5 -1
- package/templates/aggregate/DomainEntity.java.ejs +5 -1
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -1
- package/templates/aggregate/JpaEntity.java.ejs +2 -1
- package/templates/base/root/AGENTS.md.ejs +916 -51
|
@@ -2,94 +2,94 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
1. [
|
|
8
|
-
2. [
|
|
9
|
-
3. [
|
|
10
|
-
4. [
|
|
11
|
-
5. [
|
|
12
|
-
6. [
|
|
13
|
-
7. [
|
|
14
|
-
8. [
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Description and purpose](#1-description-and-purpose)
|
|
8
|
+
2. [Syntax and YAML location](#2-syntax-and-yaml-location)
|
|
9
|
+
3. [Base domain.yaml structure](#3-base-domainyaml-structure)
|
|
10
|
+
4. [Supported data types](#4-supported-data-types)
|
|
11
|
+
5. [Field properties](#5-field-properties)
|
|
12
|
+
6. [JSR-303 Validations](#6-jsr-303-validations)
|
|
13
|
+
7. [Auditing](#7-auditing)
|
|
14
|
+
8. [Relationships](#8-relationships)
|
|
15
15
|
9. [Value Objects](#9-value-objects)
|
|
16
|
-
10. [Enums
|
|
17
|
-
11. [
|
|
18
|
-
12. [
|
|
19
|
-
13. [
|
|
20
|
-
14. [
|
|
21
|
-
15. [
|
|
16
|
+
10. [Enums and state transitions](#10-enums-and-state-transitions)
|
|
17
|
+
11. [Domain events](#11-domain-events)
|
|
18
|
+
12. [Multiple aggregates](#12-multiple-aggregates)
|
|
19
|
+
13. [Generated files](#13-generated-files)
|
|
20
|
+
14. [Complete examples](#14-complete-examples)
|
|
21
|
+
15. [Prerequisites and common errors](#15-prerequisites-and-common-errors)
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
## 1.
|
|
25
|
+
## 1. Description and purpose
|
|
26
26
|
|
|
27
|
-
`generate entities`
|
|
27
|
+
`generate entities` is the core command of eva4j. From a `domain.yaml` file, it generates the complete hexagonal architecture for the module:
|
|
28
28
|
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
29
|
+
- **Domain layer** – Entities, Value Objects, Enums, repository interfaces
|
|
30
|
+
- **Application layer** – Commands, Queries, handlers, DTOs, mappers
|
|
31
|
+
- **Infrastructure layer** – JPA entities, Spring Data repositories, repository implementations, REST controllers
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
The generator understands relationships, auditing, field visibility, validations, state transitions, and domain events.
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
## 2.
|
|
37
|
+
## 2. Syntax and YAML location
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
eva generate entities <module>
|
|
41
|
-
eva g entities <module> # alias
|
|
41
|
+
eva g entities <module> # short alias
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
###
|
|
44
|
+
### Parameters
|
|
45
45
|
|
|
46
|
-
|
|
|
47
|
-
|
|
48
|
-
| `<module>` |
|
|
46
|
+
| Parameter | Required | Description |
|
|
47
|
+
|-----------|----------|-------------|
|
|
48
|
+
| `<module>` | Yes | Module name (must already exist in the project) |
|
|
49
49
|
|
|
50
|
-
###
|
|
50
|
+
### Options
|
|
51
51
|
|
|
52
|
-
|
|
|
52
|
+
| Option | Description |
|
|
53
53
|
|--------|-------------|
|
|
54
|
-
| `--force` |
|
|
54
|
+
| `--force` | Overwrite files that have developer changes |
|
|
55
55
|
|
|
56
|
-
###
|
|
56
|
+
### YAML location
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
The file is read from:
|
|
59
59
|
|
|
60
60
|
```
|
|
61
61
|
src/main/java/<package>/<module>/domain.yaml
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
>
|
|
64
|
+
> The generator detects developer changes via checksums. If a file was manually modified, it is **not overwritten** unless you use `--force`.
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
-
## 3.
|
|
68
|
+
## 3. Base domain.yaml structure
|
|
69
69
|
|
|
70
70
|
```yaml
|
|
71
|
-
aggregates: #
|
|
72
|
-
- name: Order #
|
|
73
|
-
entities: #
|
|
74
|
-
- name:
|
|
75
|
-
isRoot: true # true =
|
|
76
|
-
tableName: orders #
|
|
77
|
-
audit: #
|
|
71
|
+
aggregates: # List of aggregates in the module
|
|
72
|
+
- name: Order # Aggregate name (PascalCase)
|
|
73
|
+
entities: # Entities of the aggregate
|
|
74
|
+
- name: Order # Entity name (PascalCase)
|
|
75
|
+
isRoot: true # true = aggregate root
|
|
76
|
+
tableName: orders # SQL table name (optional)
|
|
77
|
+
audit: # Auditing (optional)
|
|
78
78
|
enabled: true
|
|
79
79
|
trackUser: false
|
|
80
|
-
fields: #
|
|
80
|
+
fields: # Entity fields
|
|
81
81
|
- name: id
|
|
82
82
|
type: String
|
|
83
83
|
- name: status
|
|
84
|
-
type: OrderStatus #
|
|
85
|
-
relationships: #
|
|
84
|
+
type: OrderStatus # Reference to enum or VO
|
|
85
|
+
relationships: # JPA relationships (optional)
|
|
86
86
|
- type: OneToMany
|
|
87
87
|
target: OrderItem
|
|
88
88
|
mappedBy: order
|
|
89
89
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
90
90
|
fetch: LAZY
|
|
91
91
|
|
|
92
|
-
- name:
|
|
92
|
+
- name: OrderItem # Secondary entity (no isRoot or isRoot: false)
|
|
93
93
|
tableName: order_items
|
|
94
94
|
fields:
|
|
95
95
|
- name: id
|
|
@@ -97,7 +97,7 @@ aggregates: # Lista de agregados en el módulo
|
|
|
97
97
|
- name: quantity
|
|
98
98
|
type: Integer
|
|
99
99
|
|
|
100
|
-
valueObjects: # Value Objects
|
|
100
|
+
valueObjects: # Aggregate Value Objects
|
|
101
101
|
- name: Money
|
|
102
102
|
fields:
|
|
103
103
|
- name: amount
|
|
@@ -105,71 +105,71 @@ aggregates: # Lista de agregados en el módulo
|
|
|
105
105
|
- name: currency
|
|
106
106
|
type: String
|
|
107
107
|
|
|
108
|
-
enums: #
|
|
108
|
+
enums: # Aggregate enums
|
|
109
109
|
- name: OrderStatus
|
|
110
110
|
values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
|
111
111
|
|
|
112
|
-
events: #
|
|
112
|
+
events: # Domain events (optional)
|
|
113
113
|
- name: OrderPlaced
|
|
114
114
|
fields:
|
|
115
115
|
- name: customerId
|
|
116
116
|
type: String
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
> **
|
|
119
|
+
> **Supported synonyms**: `fields` = `properties`; `target` = `targetEntity`
|
|
120
120
|
|
|
121
|
-
###
|
|
121
|
+
### The `id` field rule
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
Every entity **must** have a field named exactly `id`:
|
|
124
124
|
|
|
125
|
-
|
|
|
126
|
-
|
|
125
|
+
| `id` type | Generated strategy |
|
|
126
|
+
|-----------|--------------------|
|
|
127
127
|
| `String` | `@GeneratedValue(strategy = GenerationType.UUID)` |
|
|
128
128
|
| `Long` | `@GeneratedValue(strategy = GenerationType.IDENTITY)` |
|
|
129
129
|
|
|
130
130
|
---
|
|
131
131
|
|
|
132
|
-
## 4.
|
|
132
|
+
## 4. Supported data types
|
|
133
133
|
|
|
134
|
-
|
|
|
135
|
-
|
|
136
|
-
| `String` | `String` |
|
|
137
|
-
| `Integer` | `Integer` |
|
|
138
|
-
| `Long` | `Long` |
|
|
134
|
+
| YAML type | Java type | Notes |
|
|
135
|
+
|-----------|-----------|-------|
|
|
136
|
+
| `String` | `String` | For `id` generates UUID |
|
|
137
|
+
| `Integer` | `Integer` | For `id` generates IDENTITY |
|
|
138
|
+
| `Long` | `Long` | For `id` generates IDENTITY |
|
|
139
139
|
| `Double` | `Double` | |
|
|
140
140
|
| `BigDecimal` | `BigDecimal` | |
|
|
141
141
|
| `Boolean` | `Boolean` | |
|
|
142
|
-
| `LocalDate` | `LocalDate` |
|
|
143
|
-
| `LocalDateTime` | `LocalDateTime` |
|
|
144
|
-
| `LocalTime` | `LocalTime` |
|
|
145
|
-
| `UUID` | `UUID` |
|
|
142
|
+
| `LocalDate` | `LocalDate` | Auto-imported |
|
|
143
|
+
| `LocalDateTime` | `LocalDateTime` | Auto-imported |
|
|
144
|
+
| `LocalTime` | `LocalTime` | Auto-imported |
|
|
145
|
+
| `UUID` | `UUID` | Auto-imported |
|
|
146
146
|
| `List<String>` | `List<String>` | `@ElementCollection` |
|
|
147
147
|
| `List<VO>` | `List<VoJpa>` | `@ElementCollection` |
|
|
148
|
-
|
|
|
149
|
-
|
|
|
148
|
+
| Enum name | Module enum | `@Enumerated(STRING)` |
|
|
149
|
+
| VO name | Value Object | `@Embedded` |
|
|
150
150
|
|
|
151
151
|
---
|
|
152
152
|
|
|
153
|
-
## 5.
|
|
153
|
+
## 5. Field properties
|
|
154
154
|
|
|
155
155
|
```yaml
|
|
156
156
|
fields:
|
|
157
|
-
- name: fieldName # camelCase,
|
|
158
|
-
type: String #
|
|
157
|
+
- name: fieldName # camelCase, required
|
|
158
|
+
type: String # Java type, required
|
|
159
159
|
readOnly: false # default false
|
|
160
160
|
hidden: false # default false
|
|
161
|
-
validations: [] #
|
|
162
|
-
annotations: [] #
|
|
163
|
-
reference: #
|
|
161
|
+
validations: [] # JSR-303 annotations
|
|
162
|
+
annotations: [] # raw JPA annotations
|
|
163
|
+
reference: # semantic reference to another aggregate
|
|
164
164
|
aggregate: Customer
|
|
165
165
|
module: customers
|
|
166
|
-
enumValues: [] # enum
|
|
166
|
+
enumValues: [] # inline enum (alternative to enums:)
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
###
|
|
169
|
+
### Visibility matrix
|
|
170
170
|
|
|
171
|
-
|
|
|
172
|
-
|
|
171
|
+
| Field | Creation constructor | CreateDto/Command | Full constructor | ResponseDto |
|
|
172
|
+
|-------|---------------------|-------------------|------------------|-------------|
|
|
173
173
|
| normal | ✅ | ✅ | ✅ | ✅ |
|
|
174
174
|
| `readOnly: true` | ❌ | ❌ | ✅ | ✅ |
|
|
175
175
|
| `hidden: true` | ✅ | ✅ | ✅ | ❌ |
|
|
@@ -177,31 +177,31 @@ fields:
|
|
|
177
177
|
|
|
178
178
|
### readOnly
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
Marks a field as calculated/derived: excluded from the business constructor and `CreateDto`/`CreateCommand`, but present in the full constructor (reconstruction from persistence) and in `ResponseDto`.
|
|
181
181
|
|
|
182
182
|
```yaml
|
|
183
183
|
fields:
|
|
184
184
|
- name: totalAmount
|
|
185
185
|
type: BigDecimal
|
|
186
|
-
readOnly: true #
|
|
186
|
+
readOnly: true # calculated from the sum of items
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
>
|
|
189
|
+
> When an enum has `initialValue`, the corresponding field is automatically treated as `readOnly`.
|
|
190
190
|
|
|
191
191
|
### hidden
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
Marks a field as sensitive: included on creation but does NOT appear in `ResponseDto`.
|
|
194
194
|
|
|
195
195
|
```yaml
|
|
196
196
|
fields:
|
|
197
197
|
- name: passwordHash
|
|
198
198
|
type: String
|
|
199
|
-
hidden: true #
|
|
199
|
+
hidden: true # do not expose in API
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
-
### annotations (JPA
|
|
202
|
+
### annotations (raw JPA)
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
Allows adding custom JPA annotations to the generated JPA entity.
|
|
205
205
|
|
|
206
206
|
```yaml
|
|
207
207
|
fields:
|
|
@@ -213,7 +213,7 @@ fields:
|
|
|
213
213
|
|
|
214
214
|
### reference
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
Declares a semantic reference to a field in another aggregate. Generates a Javadoc comment indicating the relationship, without creating a code dependency.
|
|
217
217
|
|
|
218
218
|
```yaml
|
|
219
219
|
fields:
|
|
@@ -224,7 +224,7 @@ fields:
|
|
|
224
224
|
module: customers
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
Generated in the domain entity:
|
|
228
228
|
|
|
229
229
|
```java
|
|
230
230
|
/** @see customers.Customer */
|
|
@@ -233,9 +233,9 @@ private String customerId;
|
|
|
233
233
|
|
|
234
234
|
---
|
|
235
235
|
|
|
236
|
-
## 6.
|
|
236
|
+
## 6. JSR-303 Validations
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
Validations are declared on the field and applied to `CreateCommand` and `CreateDto`. They are **not** added to domain entities.
|
|
239
239
|
|
|
240
240
|
```yaml
|
|
241
241
|
fields:
|
|
@@ -243,34 +243,34 @@ fields:
|
|
|
243
243
|
type: String
|
|
244
244
|
validations:
|
|
245
245
|
- type: NotBlank
|
|
246
|
-
message: "
|
|
246
|
+
message: "Name is required"
|
|
247
247
|
- type: Size
|
|
248
248
|
min: 2
|
|
249
249
|
max: 100
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
Auto-generates import: `import jakarta.validation.constraints.*;`
|
|
253
253
|
|
|
254
|
-
###
|
|
254
|
+
### Supported parameters
|
|
255
255
|
|
|
256
|
-
|
|
|
256
|
+
| Parameter | Description |
|
|
257
257
|
|-----------|-------------|
|
|
258
|
-
| `type` |
|
|
259
|
-
| `message` |
|
|
260
|
-
| `value` |
|
|
261
|
-
| `min` |
|
|
262
|
-
| `max` |
|
|
263
|
-
| `regexp` |
|
|
264
|
-
| `integer` |
|
|
265
|
-
| `fraction` |
|
|
266
|
-
| `inclusive` |
|
|
267
|
-
|
|
268
|
-
###
|
|
258
|
+
| `type` | Annotation name without `@` (required) |
|
|
259
|
+
| `message` | Custom error message |
|
|
260
|
+
| `value` | Single value (for `@Min`, `@Max`) |
|
|
261
|
+
| `min` | Minimum value (for `@Size`, `@DecimalMin`) |
|
|
262
|
+
| `max` | Maximum value (for `@Size`, `@DecimalMax`) |
|
|
263
|
+
| `regexp` | Regular expression (for `@Pattern`) |
|
|
264
|
+
| `integer` | Integer digits (for `@Digits`) |
|
|
265
|
+
| `fraction` | Decimal digits (for `@Digits`) |
|
|
266
|
+
| `inclusive` | Inclusive boundary (for `@DecimalMin`, `@DecimalMax`) |
|
|
267
|
+
|
|
268
|
+
### Examples by type
|
|
269
269
|
|
|
270
270
|
```yaml
|
|
271
271
|
# @NotBlank
|
|
272
272
|
- type: NotBlank
|
|
273
|
-
message: "
|
|
273
|
+
message: "Field is required"
|
|
274
274
|
|
|
275
275
|
# @NotNull
|
|
276
276
|
- type: NotNull
|
|
@@ -283,7 +283,7 @@ Genera import automático: `import jakarta.validation.constraints.*;`
|
|
|
283
283
|
# @Email
|
|
284
284
|
- type: Email
|
|
285
285
|
|
|
286
|
-
# @Min / @Max (
|
|
286
|
+
# @Min / @Max (for numeric fields)
|
|
287
287
|
- type: Min
|
|
288
288
|
value: 1
|
|
289
289
|
- type: Max
|
|
@@ -292,7 +292,7 @@ Genera import automático: `import jakarta.validation.constraints.*;`
|
|
|
292
292
|
# @Pattern
|
|
293
293
|
- type: Pattern
|
|
294
294
|
regexp: "^[A-Z]{2}[0-9]{6}$"
|
|
295
|
-
message: "
|
|
295
|
+
message: "Invalid format"
|
|
296
296
|
|
|
297
297
|
# @DecimalMin / @DecimalMax
|
|
298
298
|
- type: DecimalMin
|
|
@@ -309,56 +309,56 @@ Genera import automático: `import jakarta.validation.constraints.*;`
|
|
|
309
309
|
|
|
310
310
|
---
|
|
311
311
|
|
|
312
|
-
## 7.
|
|
312
|
+
## 7. Auditing
|
|
313
313
|
|
|
314
|
-
###
|
|
314
|
+
### Syntax
|
|
315
315
|
|
|
316
316
|
```yaml
|
|
317
|
-
#
|
|
317
|
+
# New (recommended)
|
|
318
318
|
audit:
|
|
319
|
-
enabled: true #
|
|
320
|
-
trackUser: true #
|
|
319
|
+
enabled: true # adds createdAt, updatedAt
|
|
320
|
+
trackUser: true # also adds createdBy, updatedBy
|
|
321
321
|
|
|
322
|
-
# Legacy (
|
|
322
|
+
# Legacy (equivalent to audit.enabled: true, trackUser: false)
|
|
323
323
|
auditable: true
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
-
###
|
|
326
|
+
### Generated JPA inheritance
|
|
327
327
|
|
|
328
|
-
|
|
|
328
|
+
| Configuration | JPA base class |
|
|
329
329
|
|---------------|----------------|
|
|
330
|
-
|
|
|
330
|
+
| No auditing | no inheritance |
|
|
331
331
|
| `audit.enabled: true` | `extends AuditableEntity` |
|
|
332
332
|
| `audit.trackUser: true` | `extends FullAuditableEntity` |
|
|
333
333
|
|
|
334
|
-
###
|
|
334
|
+
### Generated fields
|
|
335
335
|
|
|
336
|
-
|
|
|
336
|
+
| Field | `audit.enabled` | `audit.trackUser` | In ResponseDto |
|
|
337
337
|
|-------|-----------------|-------------------|----------------|
|
|
338
338
|
| `createdAt` | ✅ | ✅ | ✅ |
|
|
339
339
|
| `updatedAt` | ✅ | ✅ | ✅ |
|
|
340
340
|
| `createdBy` | ❌ | ✅ | ❌ |
|
|
341
341
|
| `updatedBy` | ❌ | ✅ | ❌ |
|
|
342
342
|
|
|
343
|
-
> `createdBy`
|
|
343
|
+
> `createdBy` and `updatedBy` are administrative metadata: they are never exposed in response DTOs.
|
|
344
344
|
|
|
345
|
-
###
|
|
345
|
+
### Infrastructure generated with `trackUser: true`
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
When `trackUser` is enabled, eva4j automatically generates:
|
|
348
348
|
|
|
349
|
-
|
|
|
350
|
-
|
|
351
|
-
| `UserContextHolder.java` | ThreadLocal
|
|
352
|
-
| `UserContextFilter.java` |
|
|
353
|
-
| `AuditorAwareImpl.java` |
|
|
349
|
+
| File | Purpose |
|
|
350
|
+
|------|---------|
|
|
351
|
+
| `UserContextHolder.java` | ThreadLocal for the current user |
|
|
352
|
+
| `UserContextFilter.java` | Captures the `X-User` header from each request |
|
|
353
|
+
| `AuditorAwareImpl.java` | Provides the current user to JPA Auditing |
|
|
354
354
|
|
|
355
|
-
|
|
355
|
+
`Application.java` is configured with `@EnableJpaAuditing(auditorAwareRef = "auditorProvider")`.
|
|
356
356
|
|
|
357
|
-
###
|
|
357
|
+
### Example
|
|
358
358
|
|
|
359
359
|
```yaml
|
|
360
360
|
entities:
|
|
361
|
-
- name:
|
|
361
|
+
- name: Order
|
|
362
362
|
isRoot: true
|
|
363
363
|
tableName: orders
|
|
364
364
|
audit:
|
|
@@ -371,31 +371,31 @@ entities:
|
|
|
371
371
|
type: BigDecimal
|
|
372
372
|
```
|
|
373
373
|
|
|
374
|
-
>
|
|
374
|
+
> Audit fields **must not be defined manually** in `fields:`; they are inherited from the JPA base class.
|
|
375
375
|
|
|
376
376
|
---
|
|
377
377
|
|
|
378
|
-
## 8.
|
|
378
|
+
## 8. Relationships
|
|
379
379
|
|
|
380
|
-
###
|
|
380
|
+
### Properties
|
|
381
381
|
|
|
382
|
-
|
|
|
383
|
-
|
|
384
|
-
| `type` | `OneToMany`, `ManyToOne`, `OneToOne`, `ManyToMany` |
|
|
385
|
-
| `target` / `targetEntity` |
|
|
386
|
-
| `mappedBy` |
|
|
387
|
-
| `joinColumn` |
|
|
388
|
-
| `cascade` | array
|
|
389
|
-
| `fetch` | `LAZY` (default), `EAGER` |
|
|
382
|
+
| Property | Values | Description |
|
|
383
|
+
|----------|--------|-------------|
|
|
384
|
+
| `type` | `OneToMany`, `ManyToOne`, `OneToOne`, `ManyToMany` | Relationship type |
|
|
385
|
+
| `target` / `targetEntity` | Entity name | Related entity |
|
|
386
|
+
| `mappedBy` | field name | Inverse side of the relationship |
|
|
387
|
+
| `joinColumn` | column name | FK column name |
|
|
388
|
+
| `cascade` | array of `PERSIST`, `MERGE`, `REMOVE`, `REFRESH`, `DETACH`, `ALL` | Cascade operations |
|
|
389
|
+
| `fetch` | `LAZY` (default), `EAGER` | Loading strategy |
|
|
390
390
|
|
|
391
|
-
###
|
|
391
|
+
### Automatic inverse side generation
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
When you define `OneToMany` with `mappedBy`, eva4j automatically generates `@ManyToOne` in the target JPA entity. **Defining both sides is not required.**
|
|
394
394
|
|
|
395
395
|
```yaml
|
|
396
|
-
# ✅
|
|
396
|
+
# ✅ Only this is needed
|
|
397
397
|
entities:
|
|
398
|
-
- name:
|
|
398
|
+
- name: Order
|
|
399
399
|
isRoot: true
|
|
400
400
|
relationships:
|
|
401
401
|
- type: OneToMany
|
|
@@ -404,13 +404,13 @@ entities:
|
|
|
404
404
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
405
405
|
fetch: LAZY
|
|
406
406
|
|
|
407
|
-
#
|
|
407
|
+
# eva4j generates in OrderItemJpa:
|
|
408
408
|
# @ManyToOne(fetch = FetchType.LAZY)
|
|
409
409
|
# @JoinColumn(name = "order_id")
|
|
410
410
|
# private OrderJpa order;
|
|
411
411
|
```
|
|
412
412
|
|
|
413
|
-
>
|
|
413
|
+
> If you define `ManyToOne` manually, that definition takes priority over auto-generation.
|
|
414
414
|
|
|
415
415
|
### OneToMany
|
|
416
416
|
|
|
@@ -423,7 +423,7 @@ relationships:
|
|
|
423
423
|
fetch: LAZY
|
|
424
424
|
```
|
|
425
425
|
|
|
426
|
-
|
|
426
|
+
Generated in domain:
|
|
427
427
|
|
|
428
428
|
```java
|
|
429
429
|
private List<OrderItem> orderItems = new ArrayList<>();
|
|
@@ -431,7 +431,7 @@ public void addOrderItem(OrderItem item) { orderItems.add(item); }
|
|
|
431
431
|
public void removeOrderItem(OrderItem item) { orderItems.remove(item); }
|
|
432
432
|
```
|
|
433
433
|
|
|
434
|
-
### ManyToOne (manual,
|
|
434
|
+
### ManyToOne (manual, when you need a specific FK)
|
|
435
435
|
|
|
436
436
|
```yaml
|
|
437
437
|
relationships:
|
|
@@ -444,7 +444,7 @@ relationships:
|
|
|
444
444
|
### OneToOne
|
|
445
445
|
|
|
446
446
|
```yaml
|
|
447
|
-
#
|
|
447
|
+
# Inverse side (with mappedBy)
|
|
448
448
|
relationships:
|
|
449
449
|
- type: OneToOne
|
|
450
450
|
target: OrderSummary
|
|
@@ -452,7 +452,7 @@ relationships:
|
|
|
452
452
|
cascade: [PERSIST, MERGE]
|
|
453
453
|
fetch: LAZY
|
|
454
454
|
|
|
455
|
-
#
|
|
455
|
+
# Owner side (with FK)
|
|
456
456
|
relationships:
|
|
457
457
|
- type: OneToOne
|
|
458
458
|
target: Order
|
|
@@ -460,22 +460,22 @@ relationships:
|
|
|
460
460
|
fetch: LAZY
|
|
461
461
|
```
|
|
462
462
|
|
|
463
|
-
###
|
|
463
|
+
### When to define ManyToOne manually
|
|
464
464
|
|
|
465
|
-
|
|
|
466
|
-
|
|
467
|
-
|
|
|
468
|
-
| FK
|
|
469
|
-
|
|
|
470
|
-
|
|
|
465
|
+
| Scenario | Define ManyToOne? |
|
|
466
|
+
|----------|------------------|
|
|
467
|
+
| Standard relationship with `mappedBy` | ❌ eva4j generates it |
|
|
468
|
+
| FK with custom name | ✅ Yes, to control `joinColumn` |
|
|
469
|
+
| Multiple FKs to the same entity | ✅ Yes, for distinct names |
|
|
470
|
+
| Unidirectional relationship (no inverse) | ✅ Yes |
|
|
471
471
|
|
|
472
|
-
###
|
|
472
|
+
### Recommended cascade
|
|
473
473
|
|
|
474
474
|
```yaml
|
|
475
|
-
#
|
|
475
|
+
# Child has no meaning without parent → include REMOVE
|
|
476
476
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
477
477
|
|
|
478
|
-
#
|
|
478
|
+
# Child has an independent lifecycle
|
|
479
479
|
cascade: [PERSIST, MERGE]
|
|
480
480
|
```
|
|
481
481
|
|
|
@@ -483,7 +483,7 @@ cascade: [PERSIST, MERGE]
|
|
|
483
483
|
|
|
484
484
|
## 9. Value Objects
|
|
485
485
|
|
|
486
|
-
|
|
486
|
+
Immutable objects that represent domain concepts without their own identity.
|
|
487
487
|
|
|
488
488
|
```yaml
|
|
489
489
|
valueObjects:
|
|
@@ -495,26 +495,26 @@ valueObjects:
|
|
|
495
495
|
type: String
|
|
496
496
|
```
|
|
497
497
|
|
|
498
|
-
|
|
498
|
+
Generates:
|
|
499
499
|
|
|
500
|
-
- `Money.java` –
|
|
501
|
-
- `MoneyJpa.java` – `@Embeddable`
|
|
500
|
+
- `Money.java` – immutable domain class with constructor, getters, `equals()`, `hashCode()`
|
|
501
|
+
- `MoneyJpa.java` – `@Embeddable` with Lombok
|
|
502
502
|
|
|
503
|
-
|
|
503
|
+
Usage in a field:
|
|
504
504
|
|
|
505
505
|
```yaml
|
|
506
506
|
- name: totalAmount
|
|
507
|
-
type: Money #
|
|
507
|
+
type: Money # automatically detected as @Embedded
|
|
508
508
|
```
|
|
509
509
|
|
|
510
|
-
###
|
|
510
|
+
### List of Value Objects
|
|
511
511
|
|
|
512
512
|
```yaml
|
|
513
513
|
- name: addresses
|
|
514
514
|
type: List<Address>
|
|
515
515
|
```
|
|
516
516
|
|
|
517
|
-
|
|
517
|
+
Generates:
|
|
518
518
|
|
|
519
519
|
```java
|
|
520
520
|
@ElementCollection
|
|
@@ -525,9 +525,9 @@ private List<AddressJpa> addresses = new ArrayList<>();
|
|
|
525
525
|
|
|
526
526
|
---
|
|
527
527
|
|
|
528
|
-
## 10. Enums
|
|
528
|
+
## 10. Enums and state transitions
|
|
529
529
|
|
|
530
|
-
###
|
|
530
|
+
### Simple enum
|
|
531
531
|
|
|
532
532
|
```yaml
|
|
533
533
|
enums:
|
|
@@ -535,31 +535,31 @@ enums:
|
|
|
535
535
|
values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
|
536
536
|
```
|
|
537
537
|
|
|
538
|
-
|
|
538
|
+
Generates `OrderStatus.java` with the enumerated values. In JPA: `@Enumerated(EnumType.STRING)`.
|
|
539
539
|
|
|
540
|
-
### Enum
|
|
540
|
+
### Enum with state transitions
|
|
541
541
|
|
|
542
|
-
|
|
542
|
+
Transitions generate business methods in the entity, validation logic in the enum, and prevent invalid states.
|
|
543
543
|
|
|
544
544
|
```yaml
|
|
545
545
|
enums:
|
|
546
546
|
- name: OrderStatus
|
|
547
|
-
initialValue: PENDING #
|
|
547
|
+
initialValue: PENDING # assigns an initial value; field becomes readOnly
|
|
548
548
|
values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
|
549
549
|
transitions:
|
|
550
|
-
- from: PENDING #
|
|
550
|
+
- from: PENDING # can be a string or [array]
|
|
551
551
|
to: CONFIRMED
|
|
552
|
-
method: confirm #
|
|
552
|
+
method: confirm # name of the method generated in the entity
|
|
553
553
|
- from: [PENDING, CONFIRMED]
|
|
554
554
|
to: CANCELLED
|
|
555
555
|
method: cancel
|
|
556
|
-
guard: "this.status == OrderStatus.DELIVERED" # BusinessException
|
|
556
|
+
guard: "this.status == OrderStatus.DELIVERED" # throws BusinessException if true
|
|
557
557
|
- from: CONFIRMED
|
|
558
558
|
to: SHIPPED
|
|
559
559
|
method: ship
|
|
560
560
|
```
|
|
561
561
|
|
|
562
|
-
####
|
|
562
|
+
#### What is generated in the Enum
|
|
563
563
|
|
|
564
564
|
```java
|
|
565
565
|
private static final Map<OrderStatus, List<OrderStatus>> VALID_TRANSITIONS = Map.of(
|
|
@@ -579,9 +579,9 @@ public OrderStatus transitionTo(OrderStatus next) {
|
|
|
579
579
|
}
|
|
580
580
|
```
|
|
581
581
|
|
|
582
|
-
####
|
|
582
|
+
#### What is generated in the aggregate root
|
|
583
583
|
|
|
584
|
-
|
|
584
|
+
One method per transition, plus `is*()` and `can*()` helpers:
|
|
585
585
|
|
|
586
586
|
```java
|
|
587
587
|
public void confirm() {
|
|
@@ -601,7 +601,7 @@ public boolean canConfirm() { return this.status.canTransitionTo(OrderStatus.CON
|
|
|
601
601
|
|
|
602
602
|
### `initialValue`
|
|
603
603
|
|
|
604
|
-
|
|
604
|
+
Assigns a default value to the status field in the creation constructor. The field is automatically marked as `readOnly` (does not appear in `CreateDto`/`CreateCommand`).
|
|
605
605
|
|
|
606
606
|
```yaml
|
|
607
607
|
enums:
|
|
@@ -611,7 +611,7 @@ enums:
|
|
|
611
611
|
|
|
612
612
|
### `guard`
|
|
613
613
|
|
|
614
|
-
|
|
614
|
+
Java condition evaluated in the transition method. If the expression is `true`, a `BusinessException` is thrown.
|
|
615
615
|
|
|
616
616
|
```yaml
|
|
617
617
|
- from: [PENDING, CONFIRMED]
|
|
@@ -622,15 +622,15 @@ Condición Java evaluada en el método de transición. Si la expresión es `true
|
|
|
622
622
|
|
|
623
623
|
---
|
|
624
624
|
|
|
625
|
-
## 11.
|
|
625
|
+
## 11. Domain events
|
|
626
626
|
|
|
627
|
-
|
|
627
|
+
Events are declared under the aggregate (at the same level as `entities:`, `enums:`, `valueObjects:`).
|
|
628
628
|
|
|
629
629
|
```yaml
|
|
630
630
|
aggregates:
|
|
631
631
|
- name: Order
|
|
632
632
|
events:
|
|
633
|
-
- name: OrderPlaced
|
|
633
|
+
- name: OrderPlaced
|
|
634
634
|
fields:
|
|
635
635
|
- name: customerId
|
|
636
636
|
type: String
|
|
@@ -641,29 +641,29 @@ aggregates:
|
|
|
641
641
|
- name: reason
|
|
642
642
|
type: String
|
|
643
643
|
entities:
|
|
644
|
-
- name:
|
|
644
|
+
- name: Order
|
|
645
645
|
# ...
|
|
646
646
|
```
|
|
647
647
|
|
|
648
|
-
###
|
|
648
|
+
### Generated files
|
|
649
649
|
|
|
650
|
-
|
|
|
651
|
-
|
|
652
|
-
| `shared/domain/DomainEvent.java` |
|
|
653
|
-
| `domain/models/events/
|
|
654
|
-
| `domain/models/events/
|
|
655
|
-
| `raise()` / `pullDomainEvents()`
|
|
656
|
-
| `OrderRepositoryImpl.java` |
|
|
657
|
-
| `OrderDomainEventHandler.java` |
|
|
650
|
+
| File | Description |
|
|
651
|
+
|------|-------------|
|
|
652
|
+
| `shared/domain/DomainEvent.java` | Abstract base class (generated once per project) |
|
|
653
|
+
| `domain/models/events/OrderPlaced.java` | Concrete event extending `DomainEvent` |
|
|
654
|
+
| `domain/models/events/OrderCancelled.java` | Concrete event |
|
|
655
|
+
| `raise()` / `pullDomainEvents()` in the aggregate root | Event infrastructure in the entity |
|
|
656
|
+
| `OrderRepositoryImpl.java` | Calls `eventPublisher.publishEvent()` when saving |
|
|
657
|
+
| `OrderDomainEventHandler.java` | Class with `@TransactionalEventListener` per event |
|
|
658
658
|
|
|
659
|
-
###
|
|
659
|
+
### Generated event
|
|
660
660
|
|
|
661
661
|
```java
|
|
662
|
-
public final class
|
|
662
|
+
public final class OrderPlaced extends DomainEvent {
|
|
663
663
|
private final String customerId;
|
|
664
664
|
private final BigDecimal totalAmount;
|
|
665
665
|
|
|
666
|
-
public
|
|
666
|
+
public OrderPlaced(String customerId, BigDecimal totalAmount) {
|
|
667
667
|
this.customerId = customerId;
|
|
668
668
|
this.totalAmount = totalAmount;
|
|
669
669
|
}
|
|
@@ -672,15 +672,15 @@ public final class OrderPlacedEvent extends DomainEvent {
|
|
|
672
672
|
}
|
|
673
673
|
```
|
|
674
674
|
|
|
675
|
-
###
|
|
675
|
+
### How to raise an event in the entity
|
|
676
676
|
|
|
677
677
|
```java
|
|
678
678
|
public class Order {
|
|
679
679
|
private final List<DomainEvent> domainEvents = new ArrayList<>();
|
|
680
680
|
|
|
681
681
|
public void place(String customerId, BigDecimal totalAmount) {
|
|
682
|
-
//
|
|
683
|
-
raise(new
|
|
682
|
+
// business logic...
|
|
683
|
+
raise(new OrderPlaced(customerId, totalAmount));
|
|
684
684
|
}
|
|
685
685
|
|
|
686
686
|
protected void raise(DomainEvent event) {
|
|
@@ -697,15 +697,15 @@ public class Order {
|
|
|
697
697
|
|
|
698
698
|
---
|
|
699
699
|
|
|
700
|
-
## 12.
|
|
700
|
+
## 12. Multiple aggregates
|
|
701
701
|
|
|
702
|
-
|
|
702
|
+
A `domain.yaml` can contain multiple aggregates. Each one generates its own set of files.
|
|
703
703
|
|
|
704
704
|
```yaml
|
|
705
705
|
aggregates:
|
|
706
706
|
- name: Customer
|
|
707
707
|
entities:
|
|
708
|
-
- name:
|
|
708
|
+
- name: Customer
|
|
709
709
|
isRoot: true
|
|
710
710
|
fields:
|
|
711
711
|
- name: id
|
|
@@ -715,7 +715,7 @@ aggregates:
|
|
|
715
715
|
|
|
716
716
|
- name: Product
|
|
717
717
|
entities:
|
|
718
|
-
- name:
|
|
718
|
+
- name: Product
|
|
719
719
|
isRoot: true
|
|
720
720
|
fields:
|
|
721
721
|
- name: id
|
|
@@ -727,59 +727,59 @@ aggregates:
|
|
|
727
727
|
values: [ELECTRONICS, CLOTHING, FOOD]
|
|
728
728
|
```
|
|
729
729
|
|
|
730
|
-
>
|
|
730
|
+
> Enums and Value Objects are local to the aggregate where they are defined. If two aggregates need the same VO, it must be declared in each one.
|
|
731
731
|
|
|
732
732
|
---
|
|
733
733
|
|
|
734
|
-
## 13.
|
|
734
|
+
## 13. Generated files
|
|
735
735
|
|
|
736
|
-
|
|
736
|
+
For each aggregate, approximately the following files are generated:
|
|
737
737
|
|
|
738
|
-
|
|
|
739
|
-
|
|
740
|
-
| `{Root}.java` | Domain |
|
|
741
|
-
| `{Entity}.java` | Domain |
|
|
738
|
+
| File | Layer | Description |
|
|
739
|
+
|------|-------|-------------|
|
|
740
|
+
| `{Root}.java` | Domain | Aggregate root entity |
|
|
741
|
+
| `{Entity}.java` | Domain | Secondary entities |
|
|
742
742
|
| `{Vo}.java` | Domain | Value Objects |
|
|
743
|
-
| `{Enum}.java` | Domain | Enums (
|
|
744
|
-
| `{Root}Repository.java` | Domain |
|
|
745
|
-
| `Create{Root}Command.java` | Application |
|
|
746
|
-
| `Create{Root}CommandHandler.java` | Application |
|
|
747
|
-
| `Get{Root}Query.java` | Application |
|
|
748
|
-
| `Get{Root}QueryHandler.java` | Application |
|
|
749
|
-
| `List{Root}Query.java` | Application |
|
|
750
|
-
| `List{Root}QueryHandler.java` | Application |
|
|
751
|
-
| `{Root}ResponseDto.java` | Application | DTO
|
|
752
|
-
| `Create{Root}Dto.java` | Application | DTO
|
|
743
|
+
| `{Enum}.java` | Domain | Enums (with VALID_TRANSITIONS if transitions exist) |
|
|
744
|
+
| `{Root}Repository.java` | Domain | Repository interface (port) |
|
|
745
|
+
| `Create{Root}Command.java` | Application | Create command |
|
|
746
|
+
| `Create{Root}CommandHandler.java` | Application | Command handler |
|
|
747
|
+
| `Get{Root}Query.java` | Application | Get by ID query |
|
|
748
|
+
| `Get{Root}QueryHandler.java` | Application | Query handler |
|
|
749
|
+
| `List{Root}Query.java` | Application | Paginated list query |
|
|
750
|
+
| `List{Root}QueryHandler.java` | Application | List handler |
|
|
751
|
+
| `{Root}ResponseDto.java` | Application | Response DTO |
|
|
752
|
+
| `Create{Root}Dto.java` | Application | Create DTO |
|
|
753
753
|
| `{Root}ApplicationMapper.java` | Application | Mapper Command/DTO ↔ Domain |
|
|
754
|
-
| `{Root}Jpa.java` | Infrastructure |
|
|
755
|
-
| `{Entity}Jpa.java` | Infrastructure |
|
|
756
|
-
| `{Vo}Jpa.java` | Infrastructure | Value Objects
|
|
754
|
+
| `{Root}Jpa.java` | Infrastructure | JPA entity |
|
|
755
|
+
| `{Entity}Jpa.java` | Infrastructure | Secondary JPA entities |
|
|
756
|
+
| `{Vo}Jpa.java` | Infrastructure | JPA Value Objects (@Embeddable) |
|
|
757
757
|
| `{Root}Mapper.java` | Infrastructure | Mapper Domain ↔ JPA |
|
|
758
|
-
| `{Root}JpaRepository.java` | Infrastructure |
|
|
759
|
-
| `{Root}RepositoryImpl.java` | Infrastructure |
|
|
760
|
-
| `{Root}Controller.java` | Infrastructure |
|
|
758
|
+
| `{Root}JpaRepository.java` | Infrastructure | Spring Data repository |
|
|
759
|
+
| `{Root}RepositoryImpl.java` | Infrastructure | Repository implementation |
|
|
760
|
+
| `{Root}Controller.java` | Infrastructure | REST controller |
|
|
761
761
|
|
|
762
|
-
###
|
|
762
|
+
### Generated REST endpoints
|
|
763
763
|
|
|
764
|
-
|
|
|
764
|
+
| Method | Path | Description |
|
|
765
765
|
|--------|------|-------------|
|
|
766
|
-
| `POST` | `/api/{module}/{entity}` |
|
|
767
|
-
| `GET` | `/api/{module}/{entity}/{id}` |
|
|
768
|
-
| `GET` | `/api/{module}/{entity}?page=0&size=20` |
|
|
769
|
-
| `PUT` | `/api/{module}/{entity}/{id}` |
|
|
770
|
-
| `DELETE` | `/api/{module}/{entity}/{id}` |
|
|
766
|
+
| `POST` | `/api/{module}/{entity}` | Create |
|
|
767
|
+
| `GET` | `/api/{module}/{entity}/{id}` | Get by ID |
|
|
768
|
+
| `GET` | `/api/{module}/{entity}?page=0&size=20` | Paginated list |
|
|
769
|
+
| `PUT` | `/api/{module}/{entity}/{id}` | Update |
|
|
770
|
+
| `DELETE` | `/api/{module}/{entity}/{id}` | Delete |
|
|
771
771
|
|
|
772
772
|
---
|
|
773
773
|
|
|
774
|
-
## 14.
|
|
774
|
+
## 14. Complete examples
|
|
775
775
|
|
|
776
|
-
###
|
|
776
|
+
### Example 1: Order with transitions and events
|
|
777
777
|
|
|
778
778
|
```yaml
|
|
779
779
|
aggregates:
|
|
780
780
|
- name: Order
|
|
781
781
|
entities:
|
|
782
|
-
- name:
|
|
782
|
+
- name: Order
|
|
783
783
|
isRoot: true
|
|
784
784
|
tableName: orders
|
|
785
785
|
audit:
|
|
@@ -804,7 +804,7 @@ aggregates:
|
|
|
804
804
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
805
805
|
fetch: LAZY
|
|
806
806
|
|
|
807
|
-
- name:
|
|
807
|
+
- name: OrderItem
|
|
808
808
|
tableName: order_items
|
|
809
809
|
fields:
|
|
810
810
|
- name: id
|
|
@@ -846,13 +846,13 @@ aggregates:
|
|
|
846
846
|
type: String
|
|
847
847
|
```
|
|
848
848
|
|
|
849
|
-
###
|
|
849
|
+
### Example 2: User with auditing and a sensitive field
|
|
850
850
|
|
|
851
851
|
```yaml
|
|
852
852
|
aggregates:
|
|
853
853
|
- name: User
|
|
854
854
|
entities:
|
|
855
|
-
- name:
|
|
855
|
+
- name: User
|
|
856
856
|
isRoot: true
|
|
857
857
|
tableName: users
|
|
858
858
|
audit:
|
|
@@ -889,2173 +889,21 @@ aggregates:
|
|
|
889
889
|
|
|
890
890
|
---
|
|
891
891
|
|
|
892
|
-
## 15.
|
|
892
|
+
## 15. Prerequisites and common errors
|
|
893
893
|
|
|
894
|
-
###
|
|
894
|
+
### Prerequisites
|
|
895
895
|
|
|
896
|
-
-
|
|
897
|
-
-
|
|
898
|
-
-
|
|
896
|
+
- Project created with `eva create`
|
|
897
|
+
- Existing module (`eva add module <module>`)
|
|
898
|
+
- `domain.yaml` file at `src/main/java/<package>/<module>/`
|
|
899
899
|
|
|
900
|
-
###
|
|
900
|
+
### Common errors
|
|
901
901
|
|
|
902
|
-
| Error |
|
|
902
|
+
| Error | Cause | Solution |
|
|
903
903
|
|-------|-------|----------|
|
|
904
|
-
| `Module does not exist` |
|
|
905
|
-
| `YAML file not found` | No
|
|
906
|
-
| `Invalid relationship target` |
|
|
907
|
-
| `Column 'x_id' is duplicated` | ManyToOne
|
|
908
|
-
|
|
|
909
|
-
| Import errors |
|
|
910
|
-
|
|
911
|
-
## 💡 Examples
|
|
912
|
-
|
|
913
|
-
### Example 1: Simple Customer Aggregate
|
|
914
|
-
|
|
915
|
-
**File:** `examples/customer.yaml`
|
|
916
|
-
|
|
917
|
-
```yaml
|
|
918
|
-
module: customer
|
|
919
|
-
|
|
920
|
-
aggregates:
|
|
921
|
-
- name: Customer
|
|
922
|
-
tableName: customers
|
|
923
|
-
auditable: true
|
|
924
|
-
|
|
925
|
-
entities:
|
|
926
|
-
- name: customer
|
|
927
|
-
isRoot: true
|
|
928
|
-
fields:
|
|
929
|
-
- name: id
|
|
930
|
-
type: Long
|
|
931
|
-
- name: firstName
|
|
932
|
-
type: String
|
|
933
|
-
validations:
|
|
934
|
-
- "@NotBlank"
|
|
935
|
-
- "@Size(max = 100)"
|
|
936
|
-
- name: email
|
|
937
|
-
type: String
|
|
938
|
-
validations:
|
|
939
|
-
- "@Email"
|
|
940
|
-
- name: status
|
|
941
|
-
type: CustomerStatus
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
**Generate:**
|
|
945
|
-
```bash
|
|
946
|
-
eva4j g entities customer
|
|
947
|
-
```
|
|
948
|
-
|
|
949
|
-
### Example 2: Complex Order with Relations
|
|
950
|
-
|
|
951
|
-
**File:** `examples/order.yaml`
|
|
952
|
-
|
|
953
|
-
```yaml
|
|
954
|
-
module: order
|
|
955
|
-
|
|
956
|
-
aggregates:
|
|
957
|
-
- name: Order
|
|
958
|
-
tableName: orders
|
|
959
|
-
auditable: true
|
|
960
|
-
|
|
961
|
-
entities:
|
|
962
|
-
- name: order
|
|
963
|
-
isRoot: true
|
|
964
|
-
fields:
|
|
965
|
-
- name: id
|
|
966
|
-
type: Long
|
|
967
|
-
- name: orderNumber
|
|
968
|
-
type: String
|
|
969
|
-
- name: totalAmount
|
|
970
|
-
type: BigDecimal
|
|
971
|
-
- name: status
|
|
972
|
-
type: OrderStatus
|
|
973
|
-
relationships:
|
|
974
|
-
- type: OneToMany
|
|
975
|
-
target: OrderItem
|
|
976
|
-
mappedBy: order
|
|
977
|
-
cascade: ALL
|
|
978
|
-
fetch: LAZY
|
|
979
|
-
|
|
980
|
-
- name: orderItem
|
|
981
|
-
isRoot: false
|
|
982
|
-
tableName: order_items
|
|
983
|
-
fields:
|
|
984
|
-
- name: id
|
|
985
|
-
type: Long
|
|
986
|
-
- name: quantity
|
|
987
|
-
type: Integer
|
|
988
|
-
- name: unitPrice
|
|
989
|
-
type: BigDecimal
|
|
990
|
-
relationships:
|
|
991
|
-
- type: ManyToOne
|
|
992
|
-
target: Order
|
|
993
|
-
fetch: LAZY
|
|
994
|
-
|
|
995
|
-
enums:
|
|
996
|
-
- name: OrderStatus
|
|
997
|
-
values:
|
|
998
|
-
- PENDING
|
|
999
|
-
- CONFIRMED
|
|
1000
|
-
- SHIPPED
|
|
1001
|
-
- DELIVERED
|
|
1002
|
-
- CANCELLED
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
**Generate:**
|
|
1006
|
-
```bash
|
|
1007
|
-
eva4j g entities order
|
|
1008
|
-
```
|
|
1009
|
-
|
|
1010
|
-
### Example 3: With Value Objects
|
|
1011
|
-
|
|
1012
|
-
**File:** `examples/evaluation.yaml`
|
|
1013
|
-
|
|
1014
|
-
```yaml
|
|
1015
|
-
module: evaluation
|
|
1016
|
-
|
|
1017
|
-
aggregates:
|
|
1018
|
-
- name: Evaluation
|
|
1019
|
-
tableName: evaluations
|
|
1020
|
-
|
|
1021
|
-
entities:
|
|
1022
|
-
- name: evaluation
|
|
1023
|
-
isRoot: true
|
|
1024
|
-
fields:
|
|
1025
|
-
- name: id
|
|
1026
|
-
type: String
|
|
1027
|
-
- name: score
|
|
1028
|
-
type: Integer
|
|
1029
|
-
relationships:
|
|
1030
|
-
- type: OneToMany
|
|
1031
|
-
target: EvaluationDoctor
|
|
1032
|
-
cascade: ALL
|
|
1033
|
-
|
|
1034
|
-
- name: evaluationDoctor
|
|
1035
|
-
isRoot: false
|
|
1036
|
-
fields:
|
|
1037
|
-
- name: id
|
|
1038
|
-
type: Long
|
|
1039
|
-
- name: degrees
|
|
1040
|
-
type: List<Degrees>
|
|
1041
|
-
|
|
1042
|
-
valueObjects:
|
|
1043
|
-
- name: Degrees
|
|
1044
|
-
fields:
|
|
1045
|
-
- name: title
|
|
1046
|
-
type: String
|
|
1047
|
-
- name: institution
|
|
1048
|
-
type: String
|
|
1049
|
-
- name: year
|
|
1050
|
-
type: Integer
|
|
1051
|
-
- name: typeDegrees
|
|
1052
|
-
type: TypeDegrees
|
|
1053
|
-
|
|
1054
|
-
enums:
|
|
1055
|
-
- name: TypeDegrees
|
|
1056
|
-
values:
|
|
1057
|
-
- BACHELOR
|
|
1058
|
-
- MASTER
|
|
1059
|
-
- PHD
|
|
1060
|
-
```
|
|
1061
|
-
|
|
1062
|
-
**Generate:**
|
|
1063
|
-
```bash
|
|
1064
|
-
eva4j g entities evaluation
|
|
1065
|
-
```
|
|
1066
|
-
|
|
1067
|
-
## 📦 Generated Code Structure
|
|
1068
|
-
|
|
1069
|
-
```
|
|
1070
|
-
src/main/java/com/example/project/<module>/
|
|
1071
|
-
├── domain/
|
|
1072
|
-
│ ├── models/
|
|
1073
|
-
│ │ ├── Customer.java # Domain entity (root)
|
|
1074
|
-
│ │ ├── OrderItem.java # Domain entity (secondary)
|
|
1075
|
-
│ │ ├── valueobjects/
|
|
1076
|
-
│ │ │ └── Degrees.java # Value object
|
|
1077
|
-
│ │ └── enums/
|
|
1078
|
-
│ │ └── OrderStatus.java # Enum
|
|
1079
|
-
│ └── repositories/
|
|
1080
|
-
│ └── CustomerRepository.java # Repository port (interface)
|
|
1081
|
-
│
|
|
1082
|
-
├── application/
|
|
1083
|
-
│ ├── commands/
|
|
1084
|
-
│ │ ├── CreateCustomerCommand.java # Create command
|
|
1085
|
-
│ │ └── CreateCustomerCommandHandler.java # Command handler
|
|
1086
|
-
│ ├── queries/
|
|
1087
|
-
│ │ ├── GetCustomerQuery.java # Get query
|
|
1088
|
-
│ │ ├── GetCustomerQueryHandler.java # Get handler
|
|
1089
|
-
│ │ ├── ListCustomersQuery.java # List query
|
|
1090
|
-
│ │ └── ListCustomersQueryHandler.java # List handler
|
|
1091
|
-
│ ├── dtos/
|
|
1092
|
-
│ │ ├── CreateCustomerDto.java # Create DTO
|
|
1093
|
-
│ │ ├── CreateOrderItemDto.java # Nested entity DTO
|
|
1094
|
-
│ │ └── CustomerResponseDto.java # Response DTO
|
|
1095
|
-
│ └── mappers/
|
|
1096
|
-
│ └── CustomerApplicationMapper.java # Application mapper (Command/DTO → Domain)
|
|
1097
|
-
│
|
|
1098
|
-
└── infrastructure/
|
|
1099
|
-
├── database/
|
|
1100
|
-
│ ├── entities/
|
|
1101
|
-
│ │ ├── CustomerJpa.java # JPA entity (root)
|
|
1102
|
-
│ │ ├── OrderItemJpa.java # JPA entity (secondary)
|
|
1103
|
-
│ │ └── valueobjects/
|
|
1104
|
-
│ │ └── DegreesJpa.java # JPA value object
|
|
1105
|
-
│ ├── repositories/
|
|
1106
|
-
│ │ ├── CustomerJpaRepository.java # Spring Data repository
|
|
1107
|
-
│ │ └── CustomerRepositoryImpl.java # Repository implementation
|
|
1108
|
-
│ └── mappers/
|
|
1109
|
-
│ └── CustomerMapper.java # Infrastructure mapper (Domain ↔ JPA)
|
|
1110
|
-
└── rest/
|
|
1111
|
-
└── controllers/
|
|
1112
|
-
└── CustomerController.java # REST controller with CRUD endpoints
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
## ✨ Features
|
|
1116
|
-
|
|
1117
|
-
### 1. Domain Layer (Pure Business Logic)
|
|
1118
|
-
- ✅ **Entities** - Aggregate root and secondary entities
|
|
1119
|
-
- ✅ **Value Objects** - Immutable value types with `@Embedded` support
|
|
1120
|
-
- ✅ **Enums** - Type-safe enumerations
|
|
1121
|
-
- ✅ **Repository Interfaces** - Ports for persistence
|
|
1122
|
-
|
|
1123
|
-
### 2. Application Layer (Use Cases - CQRS)
|
|
1124
|
-
- ✅ **Commands** - `CreateCustomerCommand` with validation
|
|
1125
|
-
- ✅ **CommandHandlers** - Business logic orchestration
|
|
1126
|
-
- ✅ **Queries** - `GetCustomerQuery`, `ListCustomersQuery`
|
|
1127
|
-
- ✅ **QueryHandlers** - Read operations with pagination
|
|
1128
|
-
- ✅ **DTOs** - Request/Response data transfer objects
|
|
1129
|
-
- ✅ **Application Mappers** - Command/DTO → Domain transformations
|
|
1130
|
-
|
|
1131
|
-
### 3. Infrastructure Layer (Technical Details)
|
|
1132
|
-
- ✅ **JPA Entities** - Persistence annotations (`@Entity`, `@Table`)
|
|
1133
|
-
- ✅ **JPA Repositories** - Spring Data JPA implementation
|
|
1134
|
-
- ✅ **Infrastructure Mappers** - Domain ↔ JPA bidirectional mapping
|
|
1135
|
-
- ✅ **REST Controllers** - CRUD endpoints (`POST`, `GET`, `GET list`)
|
|
1136
|
-
|
|
1137
|
-
### 4. Advanced Capabilities
|
|
1138
|
-
- ✅ **Relationships** - OneToMany, ManyToOne, OneToOne, ManyToMany
|
|
1139
|
-
- ✅ **Nested Entities** - Secondary entities with their own relationships
|
|
1140
|
-
- ✅ **Value Object Collections** - `List<ValueObject>` with `@ElementCollection`
|
|
1141
|
-
- ✅ **Auditing** - `@CreatedDate`, `@LastModifiedDate` when `auditable: true`
|
|
1142
|
-
- ✅ **Cascade Operations** - Configurable cascade types
|
|
1143
|
-
- ✅ **Fetch Strategies** - LAZY/EAGER configuration
|
|
1144
|
-
- ✅ **Validations** - Bean Validation annotations
|
|
1145
|
-
- ✅ **Pagination** - Built-in pagination support for list queries
|
|
1146
|
-
|
|
1147
|
-
## 🔄 Supported Relationships
|
|
1148
|
-
|
|
1149
|
-
### OneToMany / ManyToOne (Bidirectional)
|
|
1150
|
-
|
|
1151
|
-
```yaml
|
|
1152
|
-
# Parent entity
|
|
1153
|
-
entities:
|
|
1154
|
-
- name: order
|
|
1155
|
-
relationships:
|
|
1156
|
-
- type: OneToMany
|
|
1157
|
-
target: OrderItem
|
|
1158
|
-
mappedBy: order # Field in OrderItem that owns the relationship
|
|
1159
|
-
cascade: ALL
|
|
1160
|
-
fetch: LAZY
|
|
1161
|
-
|
|
1162
|
-
# Child entity
|
|
1163
|
-
- name: orderItem
|
|
1164
|
-
relationships:
|
|
1165
|
-
- type: ManyToOne
|
|
1166
|
-
target: Order
|
|
1167
|
-
fetch: LAZY
|
|
1168
|
-
```
|
|
1169
|
-
|
|
1170
|
-
### OneToOne
|
|
1171
|
-
|
|
1172
|
-
```yaml
|
|
1173
|
-
entities:
|
|
1174
|
-
- name: user
|
|
1175
|
-
relationships:
|
|
1176
|
-
- type: OneToOne
|
|
1177
|
-
target: UserProfile
|
|
1178
|
-
cascade: ALL
|
|
1179
|
-
|
|
1180
|
-
- name: userProfile
|
|
1181
|
-
relationships:
|
|
1182
|
-
- type: OneToOne
|
|
1183
|
-
target: User
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1186
|
-
### ManyToMany
|
|
1187
|
-
|
|
1188
|
-
```yaml
|
|
1189
|
-
entities:
|
|
1190
|
-
- name: student
|
|
1191
|
-
relationships:
|
|
1192
|
-
- type: ManyToMany
|
|
1193
|
-
target: Course
|
|
1194
|
-
cascade: PERSIST
|
|
1195
|
-
|
|
1196
|
-
- name: course
|
|
1197
|
-
relationships:
|
|
1198
|
-
- type: ManyToMany
|
|
1199
|
-
target: Student
|
|
1200
|
-
mappedBy: courses
|
|
1201
|
-
```
|
|
1202
|
-
|
|
1203
|
-
### Relations Between Secondary Entities
|
|
1204
|
-
|
|
1205
|
-
```yaml
|
|
1206
|
-
entities:
|
|
1207
|
-
- name: evaluationDoctor
|
|
1208
|
-
relationships:
|
|
1209
|
-
- type: OneToMany
|
|
1210
|
-
target: EvaluationBranch # Another secondary entity
|
|
1211
|
-
cascade: ALL
|
|
1212
|
-
|
|
1213
|
-
- name: evaluationBranch
|
|
1214
|
-
relationships:
|
|
1215
|
-
- type: ManyToOne
|
|
1216
|
-
target: EvaluationDoctor
|
|
1217
|
-
```
|
|
1218
|
-
|
|
1219
|
-
## 🎯 Supported Data Types
|
|
1220
|
-
|
|
1221
|
-
### Primitive Types
|
|
1222
|
-
- `String`, `Integer`, `Long`, `Double`, `Float`, `Boolean`
|
|
1223
|
-
- `BigDecimal`, `BigInteger`
|
|
1224
|
-
- `LocalDate`, `LocalDateTime`, `LocalTime`
|
|
1225
|
-
- `ZonedDateTime`, `Instant`
|
|
1226
|
-
|
|
1227
|
-
### Collections
|
|
1228
|
-
- `List<ValueObject>` - Generates `@ElementCollection`
|
|
1229
|
-
- `List<Entity>` - Generates `@OneToMany`
|
|
1230
|
-
|
|
1231
|
-
### Custom Types
|
|
1232
|
-
- Value Objects (defined in `valueObjects` section)
|
|
1233
|
-
- Enums (defined in `enums` section)
|
|
1234
|
-
|
|
1235
|
-
## 🚀 Next Steps
|
|
1236
|
-
|
|
1237
|
-
After generating entities:
|
|
1238
|
-
|
|
1239
|
-
1. **Review generated code:**
|
|
1240
|
-
```bash
|
|
1241
|
-
# Check domain models
|
|
1242
|
-
cat src/main/java/com/example/project/<module>/domain/models/*.java
|
|
1243
|
-
```
|
|
1244
|
-
|
|
1245
|
-
2. **Add business logic:**
|
|
1246
|
-
- Edit domain entities to add business methods
|
|
1247
|
-
- Implement domain validations
|
|
1248
|
-
- Add domain events if needed
|
|
1249
|
-
|
|
1250
|
-
3. **Test the API:**
|
|
1251
|
-
```bash
|
|
1252
|
-
./gradlew bootRun
|
|
1253
|
-
# POST http://localhost:8080/api/<module>/<entity>
|
|
1254
|
-
# GET http://localhost:8080/api/<module>/<entity>/{id}
|
|
1255
|
-
# GET http://localhost:8080/api/<module>/<entity>
|
|
1256
|
-
```
|
|
1257
|
-
|
|
1258
|
-
4. **Extend functionality:**
|
|
1259
|
-
```bash
|
|
1260
|
-
eva4j g usecase UpdateCustomer --type command
|
|
1261
|
-
eva4j g usecase DeleteCustomer --type command
|
|
1262
|
-
```
|
|
1263
|
-
|
|
1264
|
-
## ⚠️ Prerequisites
|
|
1265
|
-
|
|
1266
|
-
- Be in a project created with `eva4j create`
|
|
1267
|
-
- Module must exist (created with `eva4j add module`)
|
|
1268
|
-
- YAML file must exist at `examples/<aggregate-name>.yaml`
|
|
1269
|
-
|
|
1270
|
-
## 🔍 Validations
|
|
1271
|
-
|
|
1272
|
-
The command validates:
|
|
1273
|
-
- ✅ Valid eva4j project
|
|
1274
|
-
- ✅ Target module exists
|
|
1275
|
-
- ✅ YAML file exists and is valid
|
|
1276
|
-
- ✅ No syntax errors in YAML
|
|
1277
|
-
- ✅ Entity names are unique
|
|
1278
|
-
- ✅ Relationship targets exist
|
|
1279
|
-
- ✅ Field types are valid
|
|
1280
|
-
|
|
1281
|
-
## 📚 See Also
|
|
1282
|
-
|
|
1283
|
-
- [DOMAIN_YAML_GUIDE.md](../../DOMAIN_YAML_GUIDE.md) - Complete YAML syntax reference
|
|
1284
|
-
- [add-module](./ADD_MODULE.md) - Create modules
|
|
1285
|
-
- [generate-usecase](./GENERATE_USECASE.md) - Add more use cases
|
|
1286
|
-
|
|
1287
|
-
## 🐛 Troubleshooting
|
|
1288
|
-
|
|
1289
|
-
**Error: "YAML file not found"**
|
|
1290
|
-
- Solution: Create `examples/<aggregate-name>.yaml` file first
|
|
1291
|
-
|
|
1292
|
-
**Error: "Module does not exist"**
|
|
1293
|
-
- Solution: Run `eva4j add module <module-name>` first
|
|
1294
|
-
|
|
1295
|
-
**Error: "Invalid relationship target"**
|
|
1296
|
-
- Solution: Ensure the target entity is defined in the same aggregate
|
|
1297
|
-
|
|
1298
|
-
**Import errors after generation**
|
|
1299
|
-
- Solution: This has been fixed in recent versions. Make sure you're using eva4j 1.0.3+
|
|
1300
|
-
- If still happening, check that field types match defined ValueObjects/Enums
|
|
1301
|
-
|
|
1302
|
-
**Compilation errors with List<ValueObject>**
|
|
1303
|
-
- Solution: Updated in latest version to use `List<ValueObjectJpa>` in JPA entities
|
|
1304
|
-
- Mapper name: `OrderMapper.java`
|
|
1305
|
-
- File organization
|
|
1306
|
-
- Generated code references
|
|
1307
|
-
|
|
1308
|
-
---
|
|
1309
|
-
|
|
1310
|
-
## Entities
|
|
1311
|
-
|
|
1312
|
-
### Root Entity (Aggregate Root)
|
|
1313
|
-
|
|
1314
|
-
The root entity is the entry point to the aggregate. All operations must go through it.
|
|
1315
|
-
|
|
1316
|
-
**⚠️ Important**: The root entity is defined within the `entities` array with `isRoot: true`.
|
|
1317
|
-
|
|
1318
|
-
```yaml
|
|
1319
|
-
aggregates:
|
|
1320
|
-
- name: Order
|
|
1321
|
-
entities:
|
|
1322
|
-
- name: order # Entity name (camelCase or snake_case)
|
|
1323
|
-
isRoot: true # ← REQUIRED to mark the root
|
|
1324
|
-
tableName: orders # Table name in DB (optional)
|
|
1325
|
-
|
|
1326
|
-
fields:
|
|
1327
|
-
- name: id
|
|
1328
|
-
type: String # String generates UUID, Long generates IDENTITY
|
|
1329
|
-
|
|
1330
|
-
- name: orderNumber
|
|
1331
|
-
type: String
|
|
1332
|
-
|
|
1333
|
-
- name: status
|
|
1334
|
-
type: OrderStatus # Reference to an enum
|
|
1335
|
-
|
|
1336
|
-
- name: totalAmount
|
|
1337
|
-
type: Money # Reference to a value object
|
|
1338
|
-
|
|
1339
|
-
- name: createdAt
|
|
1340
|
-
type: LocalDateTime
|
|
1341
|
-
|
|
1342
|
-
relationships:
|
|
1343
|
-
- type: OneToMany
|
|
1344
|
-
target: OrderItem
|
|
1345
|
-
mappedBy: order
|
|
1346
|
-
cascade: [PERSIST, MERGE, REMOVE]
|
|
1347
|
-
fetch: LAZY
|
|
1348
|
-
```
|
|
1349
|
-
|
|
1350
|
-
### Secondary Entities
|
|
1351
|
-
|
|
1352
|
-
Entities that belong to the aggregate but are not the root. They are defined in the same `entities` array **without** `isRoot` (or with `isRoot: false`).
|
|
1353
|
-
|
|
1354
|
-
```yaml
|
|
1355
|
-
aggregates:
|
|
1356
|
-
- name: Order
|
|
1357
|
-
entities:
|
|
1358
|
-
# ... root entity order with isRoot: true ...
|
|
1359
|
-
|
|
1360
|
-
- name: orderItem # ← Secondary entity
|
|
1361
|
-
tableName: order_items
|
|
1362
|
-
# Without isRoot or isRoot: false = secondary
|
|
1363
|
-
|
|
1364
|
-
fields:
|
|
1365
|
-
- name: id
|
|
1366
|
-
type: Long
|
|
1367
|
-
|
|
1368
|
-
- name: productId
|
|
1369
|
-
type: String
|
|
1370
|
-
|
|
1371
|
-
- name: quantity
|
|
1372
|
-
type: Integer
|
|
1373
|
-
|
|
1374
|
-
- name: unitPrice
|
|
1375
|
-
type: Money
|
|
1376
|
-
|
|
1377
|
-
relationships:
|
|
1378
|
-
- type: ManyToOne
|
|
1379
|
-
target: Order
|
|
1380
|
-
joinColumn: order_id
|
|
1381
|
-
fetch: LAZY
|
|
1382
|
-
```
|
|
1383
|
-
|
|
1384
|
-
### Fields
|
|
1385
|
-
|
|
1386
|
-
#### Syntax
|
|
1387
|
-
|
|
1388
|
-
```yaml
|
|
1389
|
-
fields:
|
|
1390
|
-
- name: fieldName # Field name (camelCase) - REQUIRED
|
|
1391
|
-
type: String # Java data type - REQUIRED
|
|
1392
|
-
```
|
|
1393
|
-
|
|
1394
|
-
**Supported properties:**
|
|
1395
|
-
- `name`: Field name (required)
|
|
1396
|
-
- `type`: Java data type (required)
|
|
1397
|
-
|
|
1398
|
-
#### Automatic Type Detection
|
|
1399
|
-
|
|
1400
|
-
eva4j automatically detects field types based **only** on `type`:
|
|
1401
|
-
|
|
1402
|
-
**✅ Value Objects** - Automatically detected
|
|
1403
|
-
```yaml
|
|
1404
|
-
fields:
|
|
1405
|
-
- name: totalAmount
|
|
1406
|
-
type: Money # If Money is in valueObjects → automatic @Embedded
|
|
1407
|
-
```
|
|
1408
|
-
|
|
1409
|
-
**✅ Enums** - Automatically detected
|
|
1410
|
-
```yaml
|
|
1411
|
-
fields:
|
|
1412
|
-
- name: status
|
|
1413
|
-
type: OrderStatus # If OrderStatus is in enums → @Enumerated(STRING)
|
|
1414
|
-
```
|
|
1415
|
-
|
|
1416
|
-
**✅ Primitive types**
|
|
1417
|
-
```yaml
|
|
1418
|
-
fields:
|
|
1419
|
-
- name: name
|
|
1420
|
-
type: String # → VARCHAR
|
|
1421
|
-
- name: age
|
|
1422
|
-
type: Integer # → INTEGER
|
|
1423
|
-
- name: price
|
|
1424
|
-
type: BigDecimal # → DECIMAL
|
|
1425
|
-
```
|
|
1426
|
-
|
|
1427
|
-
**✅ Date types** - Automatically imported
|
|
1428
|
-
```yaml
|
|
1429
|
-
fields:
|
|
1430
|
-
- name: createdAt
|
|
1431
|
-
type: LocalDateTime # → timestamp + import java.time.LocalDateTime
|
|
1432
|
-
```
|
|
1433
|
-
|
|
1434
|
-
**✅ Collections** - Automatic @ElementCollection
|
|
1435
|
-
```yaml
|
|
1436
|
-
fields:
|
|
1437
|
-
- name: tags
|
|
1438
|
-
type: List<String> # → @ElementCollection with secondary table
|
|
1439
|
-
```
|
|
1440
|
-
|
|
1441
|
-
#### ❌ NO need to specify
|
|
1442
|
-
|
|
1443
|
-
eva4j automatically generates the correct JPA annotations:
|
|
1444
|
-
- `@Embedded` for Value Objects
|
|
1445
|
-
- `@Enumerated(EnumType.STRING)` for Enums
|
|
1446
|
-
- `@ElementCollection` for lists
|
|
1447
|
-
- Required imports
|
|
1448
|
-
|
|
1449
|
-
#### ⚠️ MANDATORY RULE: `id` Field
|
|
1450
|
-
|
|
1451
|
-
**All entities MUST have a field named exactly `id`.**
|
|
1452
|
-
|
|
1453
|
-
```yaml
|
|
1454
|
-
# ✅ CORRECT - All entities have 'id'
|
|
1455
|
-
entities:
|
|
1456
|
-
- name: order
|
|
1457
|
-
isRoot: true
|
|
1458
|
-
fields:
|
|
1459
|
-
- name: id # ← REQUIRED
|
|
1460
|
-
type: String # String = UUID, Long = IDENTITY
|
|
1461
|
-
- name: orderNumber
|
|
1462
|
-
type: String
|
|
1463
|
-
|
|
1464
|
-
- name: orderItem
|
|
1465
|
-
fields:
|
|
1466
|
-
- name: id # ← REQUIRED also in secondary entities
|
|
1467
|
-
type: Long
|
|
1468
|
-
- name: productId
|
|
1469
|
-
type: String
|
|
1470
|
-
```
|
|
1471
|
-
|
|
1472
|
-
**Reasons:**
|
|
1473
|
-
- ✅ JPA requires `@Id` in all entities
|
|
1474
|
-
- ✅ Eva4j automatically generates `@Id` and `@GeneratedValue` for the `id` field
|
|
1475
|
-
- ✅ Clear and consistent convention across the domain
|
|
1476
|
-
|
|
1477
|
-
**Supported types for `id`:**
|
|
1478
|
-
- `String` → Generates `@GeneratedValue(strategy = GenerationType.UUID)`
|
|
1479
|
-
- `Long` → Generates `@GeneratedValue(strategy = GenerationType.IDENTITY)`
|
|
1480
|
-
|
|
1481
|
-
**❌ INCORRECT:**
|
|
1482
|
-
```yaml
|
|
1483
|
-
# ❌ Without 'id' field - Application will fail
|
|
1484
|
-
fields:
|
|
1485
|
-
- name: orderNumber
|
|
1486
|
-
type: String
|
|
1487
|
-
# ← Missing 'id' field
|
|
1488
|
-
|
|
1489
|
-
# ❌ Different name - Won't work
|
|
1490
|
-
fields:
|
|
1491
|
-
- name: orderId # ← Must be named exactly 'id'
|
|
1492
|
-
type: String
|
|
1493
|
-
```
|
|
1494
|
-
|
|
1495
|
-
**💡 Business Identifiers:**
|
|
1496
|
-
|
|
1497
|
-
If you need a business identifier in addition to the technical ID:
|
|
1498
|
-
|
|
1499
|
-
```yaml
|
|
1500
|
-
fields:
|
|
1501
|
-
- name: id # ← Technical ID (required)
|
|
1502
|
-
type: String
|
|
1503
|
-
- name: orderNumber # ← Business ID (optional)
|
|
1504
|
-
type: String
|
|
1505
|
-
- name: invoiceNumber # ← Another business identifier
|
|
1506
|
-
type: String
|
|
1507
|
-
```
|
|
1508
|
-
|
|
1509
|
-
---
|
|
1510
|
-
|
|
1511
|
-
#### Correct Examples
|
|
1512
|
-
|
|
1513
|
-
```yaml
|
|
1514
|
-
# Value Object
|
|
1515
|
-
fields:
|
|
1516
|
-
- name: totalAmount
|
|
1517
|
-
type: Money # ✅ Sufficient - eva4j automatically detects
|
|
1518
|
-
|
|
1519
|
-
# Enum
|
|
1520
|
-
fields:
|
|
1521
|
-
- name: status
|
|
1522
|
-
type: OrderStatus # ✅ Sufficient - eva4j automatically detects
|
|
1523
|
-
|
|
1524
|
-
# Primitive type
|
|
1525
|
-
fields:
|
|
1526
|
-
- name: description
|
|
1527
|
-
type: String # ✅ Basic type
|
|
1528
|
-
|
|
1529
|
-
# Collection
|
|
1530
|
-
fields:
|
|
1531
|
-
- name: tags
|
|
1532
|
-
type: List<String> # ✅ Automatic @ElementCollection
|
|
1533
|
-
```
|
|
1534
|
-
|
|
1535
|
-
---
|
|
1536
|
-
|
|
1537
|
-
### Automatic Auditing
|
|
1538
|
-
|
|
1539
|
-
eva4j supports automatic entity auditing using the `auditable` property. When set to `true`, the entity will automatically include creation and modification date fields.
|
|
1540
|
-
|
|
1541
|
-
#### Syntax
|
|
1542
|
-
|
|
1543
|
-
```yaml
|
|
1544
|
-
entities:
|
|
1545
|
-
- name: order
|
|
1546
|
-
isRoot: true
|
|
1547
|
-
auditable: true # ← Activates automatic auditing
|
|
1548
|
-
fields:
|
|
1549
|
-
- name: orderNumber
|
|
1550
|
-
type: String
|
|
1551
|
-
```
|
|
1552
|
-
|
|
1553
|
-
#### What `auditable: true` Generates
|
|
1554
|
-
|
|
1555
|
-
**In the domain entity (`Order.java`):**
|
|
1556
|
-
```java
|
|
1557
|
-
public class Order {
|
|
1558
|
-
private String orderNumber;
|
|
1559
|
-
private LocalDateTime createdAt; // ← Automatically added
|
|
1560
|
-
private LocalDateTime updatedAt; // ← Automatically added
|
|
1561
|
-
|
|
1562
|
-
// getters/setters automatically generated
|
|
1563
|
-
}
|
|
1564
|
-
```
|
|
1565
|
-
|
|
1566
|
-
**In the JPA entity (`OrderJpa.java`):**
|
|
1567
|
-
```java
|
|
1568
|
-
@Entity
|
|
1569
|
-
@Table(name = "orders")
|
|
1570
|
-
public class OrderJpa extends AuditableEntity { // ← Extends base class
|
|
1571
|
-
@Id
|
|
1572
|
-
@GeneratedValue(strategy = GenerationType.UUID)
|
|
1573
|
-
private String orderNumber;
|
|
1574
|
-
|
|
1575
|
-
// createdAt/updatedAt fields inherited from AuditableEntity
|
|
1576
|
-
}
|
|
1577
|
-
```
|
|
1578
|
-
|
|
1579
|
-
**Generated base class (`AuditableEntity.java`):**
|
|
1580
|
-
```java
|
|
1581
|
-
@MappedSuperclass
|
|
1582
|
-
@EntityListeners(AuditingEntityListener.class)
|
|
1583
|
-
public abstract class AuditableEntity {
|
|
1584
|
-
|
|
1585
|
-
@CreatedDate
|
|
1586
|
-
@Column(name = "created_at", nullable = false, updatable = false)
|
|
1587
|
-
private LocalDateTime createdAt;
|
|
1588
|
-
|
|
1589
|
-
@LastModifiedDate
|
|
1590
|
-
@Column(name = "updated_at", nullable = false)
|
|
1591
|
-
private LocalDateTime updatedAt;
|
|
1592
|
-
|
|
1593
|
-
// getters/setters
|
|
1594
|
-
}
|
|
1595
|
-
```
|
|
1596
|
-
|
|
1597
|
-
#### Features
|
|
1598
|
-
|
|
1599
|
-
✅ **Fully automatic**: Timestamps update without additional code
|
|
1600
|
-
✅ **Entity level**: Can be enabled for specific entities
|
|
1601
|
-
✅ **Spring Data JPA**: Uses `@CreatedDate` and `@LastModifiedDate`
|
|
1602
|
-
✅ **Mapper included**: Audit fields are automatically mapped between domain and JPA
|
|
1603
|
-
|
|
1604
|
-
#### Required Configuration
|
|
1605
|
-
|
|
1606
|
-
The Spring Boot application already has JPA auditing enabled in the main class:
|
|
1607
|
-
|
|
1608
|
-
```java
|
|
1609
|
-
@SpringBootApplication
|
|
1610
|
-
@EnableJpaAuditing // ← Already configured by eva4j
|
|
1611
|
-
public class Application {
|
|
1612
|
-
public static void main(String[] args) {
|
|
1613
|
-
SpringApplication.run(Application.class, args);
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
```
|
|
1617
|
-
|
|
1618
|
-
#### Complete Example
|
|
1619
|
-
|
|
1620
|
-
```yaml
|
|
1621
|
-
aggregates:
|
|
1622
|
-
- name: Product
|
|
1623
|
-
entities:
|
|
1624
|
-
- name: product
|
|
1625
|
-
isRoot: true
|
|
1626
|
-
auditable: true # ← Enables auditing
|
|
1627
|
-
fields:
|
|
1628
|
-
- name: productId
|
|
1629
|
-
type: String
|
|
1630
|
-
- name: name
|
|
1631
|
-
type: String
|
|
1632
|
-
- name: price
|
|
1633
|
-
type: BigDecimal
|
|
1634
|
-
# createdAt and updatedAt are automatically added
|
|
1635
|
-
|
|
1636
|
-
- name: review
|
|
1637
|
-
auditable: true # ← Secondary entities can also have auditing
|
|
1638
|
-
fields:
|
|
1639
|
-
- name: reviewId
|
|
1640
|
-
type: Long
|
|
1641
|
-
- name: comment
|
|
1642
|
-
type: String
|
|
1643
|
-
relationships:
|
|
1644
|
-
- type: ManyToOne
|
|
1645
|
-
target: product
|
|
1646
|
-
fetch: LAZY
|
|
1647
|
-
joinColumn: product_id
|
|
1648
|
-
```
|
|
1649
|
-
|
|
1650
|
-
**Resultado en la tabla:**
|
|
1651
|
-
```sql
|
|
1652
|
-
CREATE TABLE products (
|
|
1653
|
-
product_id VARCHAR(36) PRIMARY KEY,
|
|
1654
|
-
name VARCHAR(255),
|
|
1655
|
-
price DECIMAL(19,2),
|
|
1656
|
-
created_at TIMESTAMP NOT NULL, -- ← Automático
|
|
1657
|
-
updated_at TIMESTAMP NOT NULL -- ← Automático
|
|
1658
|
-
);
|
|
1659
|
-
|
|
1660
|
-
CREATE TABLE reviews (
|
|
1661
|
-
review_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
1662
|
-
comment TEXT,
|
|
1663
|
-
product_id VARCHAR(36),
|
|
1664
|
-
created_at TIMESTAMP NOT NULL, -- ← Automático
|
|
1665
|
-
updated_at TIMESTAMP NOT NULL, -- ← Automático
|
|
1666
|
-
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
|
1667
|
-
);
|
|
1668
|
-
```
|
|
1669
|
-
|
|
1670
|
-
#### Notas importantes
|
|
1671
|
-
|
|
1672
|
-
- ✅ `auditable` es **opcional** - por defecto es `false`
|
|
1673
|
-
- ✅ Puede usarse en **entidad raíz** o **entidades secundarias**
|
|
1674
|
-
- ✅ Los campos `createdAt` y `updatedAt` **no deben** definirse manualmente en `fields`
|
|
1675
|
-
- ✅ El tipo es siempre `LocalDateTime`
|
|
1676
|
-
- ❌ **No incluye** auditoría de usuario (createdBy/updatedBy) - ver [FUTURE_FEATURES.md](FUTURE_FEATURES.md) para esa funcionalidad
|
|
1677
|
-
|
|
1678
|
-
---
|
|
1679
|
-
|
|
1680
|
-
## Value Objects
|
|
1681
|
-
|
|
1682
|
-
Los Value Objects son objetos inmutables que representan conceptos del dominio sin identidad propia.
|
|
1683
|
-
|
|
1684
|
-
### Definición básica
|
|
1685
|
-
|
|
1686
|
-
```yaml
|
|
1687
|
-
valueObjects:
|
|
1688
|
-
- name: Money
|
|
1689
|
-
fields:
|
|
1690
|
-
- name: amount
|
|
1691
|
-
type: BigDecimal
|
|
1692
|
-
|
|
1693
|
-
- name: currency
|
|
1694
|
-
type: String
|
|
1695
|
-
```
|
|
1696
|
-
|
|
1697
|
-
### Generated Value Object (Domain)
|
|
1698
|
-
|
|
1699
|
-
```java
|
|
1700
|
-
public class Money {
|
|
1701
|
-
private final BigDecimal amount;
|
|
1702
|
-
private final String currency;
|
|
1703
|
-
|
|
1704
|
-
public Money(BigDecimal amount, String currency) {
|
|
1705
|
-
this.amount = amount;
|
|
1706
|
-
this.currency = currency;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
// Getters
|
|
1710
|
-
public BigDecimal getAmount() { return amount; }
|
|
1711
|
-
public String getCurrency() { return currency; }
|
|
1712
|
-
|
|
1713
|
-
// equals() and hashCode() based on all fields
|
|
1714
|
-
}
|
|
1715
|
-
```
|
|
1716
|
-
|
|
1717
|
-
### Value Object JPA (@Embeddable)
|
|
1718
|
-
|
|
1719
|
-
```java
|
|
1720
|
-
@Embeddable
|
|
1721
|
-
public class MoneyJpa {
|
|
1722
|
-
private BigDecimal amount;
|
|
1723
|
-
private String currency;
|
|
1724
|
-
|
|
1725
|
-
// Constructor, getters, setters (Lombok)
|
|
1726
|
-
}
|
|
1727
|
-
```
|
|
1728
|
-
|
|
1729
|
-
### Usage in Entities
|
|
1730
|
-
|
|
1731
|
-
```yaml
|
|
1732
|
-
fields:
|
|
1733
|
-
- name: totalAmount
|
|
1734
|
-
type: Money # Automatically detected as VO
|
|
1735
|
-
```
|
|
1736
|
-
|
|
1737
|
-
Generates in JPA:
|
|
1738
|
-
```java
|
|
1739
|
-
@Embedded
|
|
1740
|
-
private MoneyJpa totalAmount;
|
|
1741
|
-
```
|
|
1742
|
-
|
|
1743
|
-
### Example: Complex Value Object
|
|
1744
|
-
|
|
1745
|
-
```yaml
|
|
1746
|
-
valueObjects:
|
|
1747
|
-
- name: Address
|
|
1748
|
-
fields:
|
|
1749
|
-
- name: street
|
|
1750
|
-
type: String
|
|
1751
|
-
|
|
1752
|
-
- name: city
|
|
1753
|
-
type: String
|
|
1754
|
-
|
|
1755
|
-
- name: state
|
|
1756
|
-
type: String
|
|
1757
|
-
|
|
1758
|
-
- name: zipCode
|
|
1759
|
-
type: String
|
|
1760
|
-
|
|
1761
|
-
- name: country
|
|
1762
|
-
type: String
|
|
1763
|
-
```
|
|
1764
|
-
|
|
1765
|
-
---
|
|
1766
|
-
|
|
1767
|
-
## Enums
|
|
1768
|
-
|
|
1769
|
-
### Definition
|
|
1770
|
-
|
|
1771
|
-
```yaml
|
|
1772
|
-
enums:
|
|
1773
|
-
- name: OrderStatus
|
|
1774
|
-
values:
|
|
1775
|
-
- PENDING
|
|
1776
|
-
- CONFIRMED
|
|
1777
|
-
- SHIPPED
|
|
1778
|
-
- DELIVERED
|
|
1779
|
-
- CANCELLED
|
|
1780
|
-
```
|
|
1781
|
-
|
|
1782
|
-
### Generated Enum
|
|
1783
|
-
|
|
1784
|
-
```java
|
|
1785
|
-
package com.example.myapp.order.domain.models.enums;
|
|
1786
|
-
|
|
1787
|
-
public enum OrderStatus {
|
|
1788
|
-
PENDING,
|
|
1789
|
-
CONFIRMED,
|
|
1790
|
-
SHIPPED,
|
|
1791
|
-
DELIVERED,
|
|
1792
|
-
CANCELLED
|
|
1793
|
-
}
|
|
1794
|
-
```
|
|
1795
|
-
|
|
1796
|
-
### Uso en entidades
|
|
1797
|
-
|
|
1798
|
-
```yaml
|
|
1799
|
-
fields:
|
|
1800
|
-
- name: status
|
|
1801
|
-
type: OrderStatus # Se detecta y se importa automáticamente
|
|
1802
|
-
```
|
|
1803
|
-
|
|
1804
|
-
Genera en JPA:
|
|
1805
|
-
```java
|
|
1806
|
-
@Enumerated(EnumType.STRING)
|
|
1807
|
-
private OrderStatus status;
|
|
1808
|
-
```
|
|
1809
|
-
|
|
1810
|
-
### Múltiples enums
|
|
1811
|
-
|
|
1812
|
-
```yaml
|
|
1813
|
-
enums:
|
|
1814
|
-
- name: OrderStatus
|
|
1815
|
-
values: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
|
1816
|
-
|
|
1817
|
-
- name: PaymentMethod
|
|
1818
|
-
values: [CREDIT_CARD, DEBIT_CARD, CASH, BANK_TRANSFER]
|
|
1819
|
-
|
|
1820
|
-
- name: ShippingMethod
|
|
1821
|
-
values: [STANDARD, EXPRESS, OVERNIGHT]
|
|
1822
|
-
```
|
|
1823
|
-
|
|
1824
|
-
---
|
|
1825
|
-
|
|
1826
|
-
## Relaciones
|
|
1827
|
-
|
|
1828
|
-
eva4j soporta relaciones JPA bidireccionales completas con generación automática del lado inverso.
|
|
1829
|
-
|
|
1830
|
-
### 🎯 Relaciones Bidireccionales Automáticas
|
|
1831
|
-
|
|
1832
|
-
**Característica clave**: Cuando defines una relación OneToMany con `mappedBy`, eva4j genera AUTOMÁTICAMENTE la relación inversa ManyToOne en la entidad target.
|
|
1833
|
-
|
|
1834
|
-
**Solo necesitas definir UN lado:**
|
|
1835
|
-
|
|
1836
|
-
```yaml
|
|
1837
|
-
entities:
|
|
1838
|
-
- name: order
|
|
1839
|
-
isRoot: true
|
|
1840
|
-
relationships:
|
|
1841
|
-
- type: OneToMany
|
|
1842
|
-
target: OrderItem
|
|
1843
|
-
mappedBy: order # ← eva4j crea automáticamente ManyToOne en OrderItem
|
|
1844
|
-
cascade: [PERSIST, MERGE]
|
|
1845
|
-
fetch: LAZY
|
|
1846
|
-
```
|
|
1847
|
-
|
|
1848
|
-
**eva4j genera automáticamente en OrderItem:**
|
|
1849
|
-
|
|
1850
|
-
```java
|
|
1851
|
-
// OrderItemJpa.java (automatically generated)
|
|
1852
|
-
@ManyToOne(fetch = FetchType.LAZY)
|
|
1853
|
-
@JoinColumn(name = "order_id")
|
|
1854
|
-
private OrderJpa order;
|
|
1855
|
-
```
|
|
1856
|
-
|
|
1857
|
-
**Ventajas:**
|
|
1858
|
-
- ✅ No necesitas definir ambos lados manualmente
|
|
1859
|
-
- ✅ Evita inconsistencias entre relaciones
|
|
1860
|
-
- ✅ JPA persiste correctamente la relación bidireccional
|
|
1861
|
-
- ✅ Menos código YAML, misma funcionalidad
|
|
1862
|
-
|
|
1863
|
-
**Nota**: Si defines manualmente ambos lados en el YAML, la definición manual tiene prioridad sobre la autogeneración.
|
|
1864
|
-
|
|
1865
|
-
---
|
|
1866
|
-
|
|
1867
|
-
### OneToMany (Uno a Muchos)
|
|
1868
|
-
|
|
1869
|
-
**Definición en la entidad que tiene la colección:**
|
|
1870
|
-
|
|
1871
|
-
```yaml
|
|
1872
|
-
entities:
|
|
1873
|
-
- name: order
|
|
1874
|
-
isRoot: true
|
|
1875
|
-
relationships:
|
|
1876
|
-
- type: OneToMany
|
|
1877
|
-
target: OrderItem # Entidad relacionada
|
|
1878
|
-
mappedBy: order # Campo en OrderItem que apunta a Order
|
|
1879
|
-
cascade: [PERSIST, MERGE, REMOVE]
|
|
1880
|
-
fetch: LAZY
|
|
1881
|
-
```
|
|
1882
|
-
|
|
1883
|
-
**Genera en dominio:**
|
|
1884
|
-
```java
|
|
1885
|
-
private List<OrderItem> orderItems = new ArrayList<>();
|
|
1886
|
-
|
|
1887
|
-
public void addOrderItem(OrderItem orderItem) {
|
|
1888
|
-
this.orderItems.add(orderItem);
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
public void removeOrderItem(OrderItem orderItem) {
|
|
1892
|
-
this.orderItems.remove(orderItem);
|
|
1893
|
-
}
|
|
1894
|
-
```
|
|
1895
|
-
|
|
1896
|
-
**Genera en JPA:**
|
|
1897
|
-
```java
|
|
1898
|
-
@OneToMany(mappedBy = "order", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY)
|
|
1899
|
-
@Builder.Default
|
|
1900
|
-
private List<OrderItemJpa> orderItems = new ArrayList<>();
|
|
1901
|
-
```
|
|
1902
|
-
|
|
1903
|
-
**Genera automáticamente en OrderItem (lado inverso):**
|
|
1904
|
-
```java
|
|
1905
|
-
@ManyToOne(fetch = FetchType.LAZY)
|
|
1906
|
-
@JoinColumn(name = "order_id") // Inferido desde mappedBy
|
|
1907
|
-
private OrderJpa order;
|
|
1908
|
-
```
|
|
1909
|
-
|
|
1910
|
-
### ManyToOne (Muchos a Uno)
|
|
1911
|
-
|
|
1912
|
-
**Definición manual (opcional si ya usaste mappedBy en OneToMany):**
|
|
1913
|
-
|
|
1914
|
-
```yaml
|
|
1915
|
-
entities:
|
|
1916
|
-
- name: orderItem
|
|
1917
|
-
# Sin isRoot = entidad secundaria
|
|
1918
|
-
relationships:
|
|
1919
|
-
- type: ManyToOne
|
|
1920
|
-
target: Order
|
|
1921
|
-
joinColumn: order_id # Columna FK en la tabla
|
|
1922
|
-
fetch: LAZY
|
|
1923
|
-
```
|
|
1924
|
-
|
|
1925
|
-
**Genera en JPA:**
|
|
1926
|
-
```java
|
|
1927
|
-
@ManyToOne(fetch = FetchType.LAZY)
|
|
1928
|
-
@JoinColumn(name = "order_id")
|
|
1929
|
-
private OrderJpa order;
|
|
1930
|
-
```
|
|
1931
|
-
|
|
1932
|
-
**💡 Tip**: Si ya definiste `OneToMany` con `mappedBy` en Order, NO necesitas definir manualmente el `ManyToOne` en OrderItem. eva4j lo genera automáticamente.
|
|
1933
|
-
|
|
1934
|
-
---
|
|
1935
|
-
|
|
1936
|
-
### ⚠️ REGLA CRÍTICA: Relaciones Bidireccionales
|
|
1937
|
-
|
|
1938
|
-
**Para relaciones bidireccionales OneToMany/ManyToOne:**
|
|
1939
|
-
|
|
1940
|
-
#### ✅ CORRECTO - Solo definir en la entidad raíz
|
|
1941
|
-
|
|
1942
|
-
```yaml
|
|
1943
|
-
entities:
|
|
1944
|
-
- name: invoice
|
|
1945
|
-
isRoot: true
|
|
1946
|
-
relationships:
|
|
1947
|
-
- type: OneToMany
|
|
1948
|
-
target: InvoiceItem
|
|
1949
|
-
mappedBy: invoice # ← Solo esta definición
|
|
1950
|
-
cascade: [PERSIST, MERGE, REMOVE]
|
|
1951
|
-
fetch: LAZY
|
|
1952
|
-
|
|
1953
|
-
- name: invoiceItem
|
|
1954
|
-
fields:
|
|
1955
|
-
- name: id
|
|
1956
|
-
type: Long
|
|
1957
|
-
# ← SIN relationships definidas
|
|
1958
|
-
# Eva4j genera automáticamente el ManyToOne en InvoiceItemJpa
|
|
1959
|
-
```
|
|
1960
|
-
|
|
1961
|
-
**Resultado generado:**
|
|
1962
|
-
```java
|
|
1963
|
-
// InvoiceJpa.java
|
|
1964
|
-
@OneToMany(mappedBy = "invoice", cascade = {...})
|
|
1965
|
-
private List<InvoiceItemJpa> invoiceItems;
|
|
1966
|
-
|
|
1967
|
-
// InvoiceItemJpa.java (automatically generated)
|
|
1968
|
-
@ManyToOne(fetch = FetchType.LAZY)
|
|
1969
|
-
@JoinColumn(name = "invoice_id")
|
|
1970
|
-
private InvoiceJpa invoice;
|
|
1971
|
-
```
|
|
1972
|
-
|
|
1973
|
-
#### ❌ INCORRECTO - Definir en ambos lados
|
|
1974
|
-
|
|
1975
|
-
```yaml
|
|
1976
|
-
entities:
|
|
1977
|
-
- name: invoice
|
|
1978
|
-
isRoot: true
|
|
1979
|
-
relationships:
|
|
1980
|
-
- type: OneToMany
|
|
1981
|
-
target: InvoiceItem
|
|
1982
|
-
mappedBy: invoice # ← Primera definición
|
|
1983
|
-
|
|
1984
|
-
- name: invoiceItem
|
|
1985
|
-
relationships:
|
|
1986
|
-
- type: ManyToOne # ← ❌ DUPLICADO - Causará error
|
|
1987
|
-
target: Invoice
|
|
1988
|
-
joinColumn: invoice_id
|
|
1989
|
-
```
|
|
1990
|
-
|
|
1991
|
-
**Problema:** Genera DOS relaciones `@ManyToOne` en `InvoiceItemJpa`, ambas mapeando a `invoice_id`:
|
|
1992
|
-
|
|
1993
|
-
```java
|
|
1994
|
-
// InvoiceItemJpa.java (INCORRECTO - Duplicado)
|
|
1995
|
-
@ManyToOne
|
|
1996
|
-
@JoinColumn(name = "invoice_id")
|
|
1997
|
-
private InvoiceJpa invoice; // ← Del mappedBy
|
|
1998
|
-
|
|
1999
|
-
@ManyToOne
|
|
2000
|
-
@JoinColumn(name = "invoice_id")
|
|
2001
|
-
private InvoiceJpa invoices; // ← Del ManyToOne explícito
|
|
2002
|
-
|
|
2003
|
-
// Error de Hibernate:
|
|
2004
|
-
// "Column 'invoice_id' is duplicated in mapping"
|
|
2005
|
-
```
|
|
2006
|
-
|
|
2007
|
-
#### 📋 Regla de Oro
|
|
2008
|
-
|
|
2009
|
-
| Escenario | Definir en Raíz | Definir en Secundaria | Eva4j Genera |
|
|
2010
|
-
|-----------|-----------------|----------------------|-------------|
|
|
2011
|
-
| **Bidireccional** | `OneToMany` con `mappedBy` | ❌ NADA | `@OneToMany` en raíz + `@ManyToOne` en JPA de secundaria |
|
|
2012
|
-
| **Unidireccional** | Opcional | `ManyToOne` con `joinColumn` | Solo lo definido |
|
|
2013
|
-
|
|
2014
|
-
#### 💡 Separación Dominio/Persistencia
|
|
2015
|
-
|
|
2016
|
-
**Importante:** Eva4j sigue correctamente DDD:
|
|
2017
|
-
|
|
2018
|
-
- **Capa de Dominio:** Las entidades secundarias NO tienen referencia a la raíz
|
|
2019
|
-
```java
|
|
2020
|
-
// InvoiceItem.java (dominio puro)
|
|
2021
|
-
public class InvoiceItem {
|
|
2022
|
-
private Long id;
|
|
2023
|
-
private String description;
|
|
2024
|
-
// ← SIN private Invoice invoice
|
|
2025
|
-
}
|
|
2026
|
-
```
|
|
2027
|
-
|
|
2028
|
-
- **Capa de Persistencia (JPA):** Solo aquí existe la relación
|
|
2029
|
-
```java
|
|
2030
|
-
// InvoiceItemJpa.java (persistencia)
|
|
2031
|
-
public class InvoiceItemJpa {
|
|
2032
|
-
private Long id;
|
|
2033
|
-
|
|
2034
|
-
@ManyToOne
|
|
2035
|
-
@JoinColumn(name = "invoice_id")
|
|
2036
|
-
private InvoiceJpa invoice; // ← Solo en capa JPA
|
|
2037
|
-
}
|
|
2038
|
-
```
|
|
2039
|
-
|
|
2040
|
-
**Ventajas:**
|
|
2041
|
-
- ✅ Sin dependencias circulares en dominio
|
|
2042
|
-
- ✅ Modelo de dominio más simple
|
|
2043
|
-
- ✅ Relación bidireccional solo donde se necesita (persistencia)
|
|
2044
|
-
- ✅ Cumple principios de DDD y arquitectura hexagonal
|
|
2045
|
-
|
|
2046
|
-
---
|
|
2047
|
-
|
|
2048
|
-
### OneToOne (Uno a Uno)
|
|
2049
|
-
|
|
2050
|
-
**Bidireccional con mappedBy:**
|
|
2051
|
-
|
|
2052
|
-
```yaml
|
|
2053
|
-
entities:
|
|
2054
|
-
- name: order
|
|
2055
|
-
isRoot: true
|
|
2056
|
-
relationships:
|
|
2057
|
-
- type: OneToOne
|
|
2058
|
-
target: OrderSummary
|
|
2059
|
-
mappedBy: order
|
|
2060
|
-
cascade: [PERSIST, MERGE]
|
|
2061
|
-
fetch: LAZY
|
|
2062
|
-
```
|
|
2063
|
-
|
|
2064
|
-
**Sin mappedBy (owner):**
|
|
2065
|
-
|
|
2066
|
-
```yaml
|
|
2067
|
-
entities:
|
|
2068
|
-
- name: orderSummary
|
|
2069
|
-
relationships:
|
|
2070
|
-
- type: OneToOne
|
|
2071
|
-
target: Order
|
|
2072
|
-
joinColumn: order_id
|
|
2073
|
-
fetch: LAZY
|
|
2074
|
-
```
|
|
2075
|
-
|
|
2076
|
-
### Relationship Options
|
|
2077
|
-
|
|
2078
|
-
| Option | Values | Description |
|
|
2079
|
-
|--------|--------|-------------|
|
|
2080
|
-
| `type` | OneToMany, ManyToOne, OneToOne, ManyToMany | Relationship type |
|
|
2081
|
-
| `target` | EntityName | Related entity |
|
|
2082
|
-
| `mappedBy` | fieldName | For the inverse side of the relationship |
|
|
2083
|
-
| `joinColumn` | column_name | FK column name |
|
|
2084
|
-
| `cascade` | [PERSIST, MERGE, REMOVE, REFRESH, DETACH, ALL] | Cascade operations |
|
|
2085
|
-
| `fetch` | LAZY, EAGER | Loading strategy |
|
|
2086
|
-
|
|
2087
|
-
---
|
|
2088
|
-
|
|
2089
|
-
### 🔥 Cascade Options (Cascade Operations)
|
|
2090
|
-
|
|
2091
|
-
The `cascade` options determine which operations on the parent are automatically propagated to related entities.
|
|
2092
|
-
|
|
2093
|
-
#### **⚠️ IMPORTANT: Cascade and Persistence**
|
|
2094
|
-
|
|
2095
|
-
If you DON'T define `cascade`, related entities will **NOT be persisted automatically**. This is the most common error:
|
|
2096
|
-
|
|
2097
|
-
```yaml
|
|
2098
|
-
# ❌ BAD - OrderItems will NOT be saved in DB
|
|
2099
|
-
relationships:
|
|
2100
|
-
- type: OneToMany
|
|
2101
|
-
target: OrderItem
|
|
2102
|
-
mappedBy: order
|
|
2103
|
-
cascade: [] # ← Empty array = no cascade
|
|
2104
|
-
fetch: LAZY
|
|
2105
|
-
|
|
2106
|
-
# ✅ GOOD - OrderItems are saved automatically with Order
|
|
2107
|
-
relationships:
|
|
2108
|
-
- type: OneToMany
|
|
2109
|
-
target: OrderItem
|
|
2110
|
-
mappedBy: order
|
|
2111
|
-
cascade: [PERSIST, MERGE, REMOVE] # ← Required to persist
|
|
2112
|
-
fetch: LAZY
|
|
2113
|
-
```
|
|
2114
|
-
|
|
2115
|
-
#### **Cascade Options:**
|
|
2116
|
-
|
|
2117
|
-
| Option | Description | When to use? |
|
|
2118
|
-
|--------|-------------|--------------|
|
|
2119
|
-
| `PERSIST` | When saving the parent, saves new children | ✅ **Always in OneToMany** to create items |
|
|
2120
|
-
| `MERGE` | When updating the parent, updates children | ✅ **Always in OneToMany** to edit items |
|
|
2121
|
-
| `REMOVE` | When deleting the parent, deletes children | ✅ If children don't make sense without the parent |
|
|
2122
|
-
| `REFRESH` | When refreshing the parent, refreshes children | ⚠️ Rarely needed |
|
|
2123
|
-
| `DETACH` | When detaching the parent, detaches children | ⚠️ Rarely needed |
|
|
2124
|
-
| `ALL` | All of the above operations | ⚠️ Only if you're sure |
|
|
2125
|
-
|
|
2126
|
-
#### **Recommended Configurations:**
|
|
2127
|
-
|
|
2128
|
-
```yaml
|
|
2129
|
-
# 🎯 RECOMMENDED for OneToMany (Order → OrderItem)
|
|
2130
|
-
relationships:
|
|
2131
|
-
- type: OneToMany
|
|
2132
|
-
target: OrderItem
|
|
2133
|
-
mappedBy: order
|
|
2134
|
-
cascade: [PERSIST, MERGE, REMOVE] # ← Creates, updates and deletes items
|
|
2135
|
-
fetch: LAZY
|
|
2136
|
-
|
|
2137
|
-
# 🎯 RECOMMENDED for entities with independent lifecycle
|
|
2138
|
-
relationships:
|
|
2139
|
-
- type: OneToMany
|
|
2140
|
-
target: OrderItem
|
|
2141
|
-
mappedBy: order
|
|
2142
|
-
cascade: [PERSIST, MERGE] # ← Without REMOVE, items persist
|
|
2143
|
-
fetch: LAZY
|
|
2144
|
-
|
|
2145
|
-
# ⚠️ CAREFUL with ALL - includes REMOVE
|
|
2146
|
-
relationships:
|
|
2147
|
-
- type: OneToMany
|
|
2148
|
-
target: OrderItem
|
|
2149
|
-
mappedBy: order
|
|
2150
|
-
cascade: [ALL] # ← Deleting Order removes all OrderItems
|
|
2151
|
-
fetch: LAZY
|
|
2152
|
-
|
|
2153
|
-
# ❌ AVOID empty array if you want to persist children
|
|
2154
|
-
relationships:
|
|
2155
|
-
- type: OneToMany
|
|
2156
|
-
target: OrderItem
|
|
2157
|
-
mappedBy: order
|
|
2158
|
-
cascade: [] # ← Requires manually saving OrderItem
|
|
2159
|
-
fetch: LAZY
|
|
2160
|
-
```
|
|
2161
|
-
|
|
2162
|
-
#### **What happens without Cascade?**
|
|
2163
|
-
|
|
2164
|
-
```yaml
|
|
2165
|
-
# Without cascade: [PERSIST]
|
|
2166
|
-
cascade: []
|
|
2167
|
-
|
|
2168
|
-
# Behavior:
|
|
2169
|
-
order.addOrderItem(item);
|
|
2170
|
-
repository.save(order); // ❌ Order is saved, OrderItem is NOT
|
|
2171
|
-
```
|
|
2172
|
-
|
|
2173
|
-
```yaml
|
|
2174
|
-
# With cascade: [PERSIST, MERGE]
|
|
2175
|
-
cascade: [PERSIST, MERGE]
|
|
2176
|
-
|
|
2177
|
-
# Behavior:
|
|
2178
|
-
order.addOrderItem(item);
|
|
2179
|
-
repository.save(order); // ✅ Order and OrderItem are saved automatically
|
|
2180
|
-
```
|
|
2181
|
-
|
|
2182
|
-
---
|
|
2183
|
-
|
|
2184
|
-
### 🚀 Fetch Options (Loading Strategy)
|
|
2185
|
-
|
|
2186
|
-
The `fetch` options determine WHEN related entities are loaded from the database.
|
|
2187
|
-
|
|
2188
|
-
#### **Fetch Options:**
|
|
2189
|
-
|
|
2190
|
-
| Option | Description | Behavior | When to use? |
|
|
2191
|
-
|--------|-------------|----------|--------------|
|
|
2192
|
-
| `LAZY` | Load on demand (when accessed) | Only fetches parent initially | ✅ **Recommended by default** |
|
|
2193
|
-
| `EAGER` | Immediate load (always) | Fetches parent + children in same query | ⚠️ Only if you ALWAYS need children |
|
|
2194
|
-
|
|
2195
|
-
#### **LAZY Example (Recommended):**
|
|
2196
|
-
|
|
2197
|
-
```yaml
|
|
2198
|
-
relationships:
|
|
2199
|
-
- type: OneToMany
|
|
2200
|
-
target: OrderItem
|
|
2201
|
-
mappedBy: order
|
|
2202
|
-
cascade: [PERSIST, MERGE]
|
|
2203
|
-
fetch: LAZY # ← Loads items only when accessed
|
|
2204
|
-
```
|
|
2205
|
-
|
|
2206
|
-
**Generated SQL:**
|
|
2207
|
-
```sql
|
|
2208
|
-
-- First query: Only fetches Order
|
|
2209
|
-
SELECT * FROM orders WHERE id = ?
|
|
2210
|
-
|
|
2211
|
-
-- Second query: Only if you access order.getOrderItems()
|
|
2212
|
-
SELECT * FROM order_items WHERE order_id = ?
|
|
2213
|
-
```
|
|
2214
|
-
|
|
2215
|
-
**✅ Advantages:**
|
|
2216
|
-
- Better initial performance
|
|
2217
|
-
- Only loads what you need
|
|
2218
|
-
- Avoids loading unnecessary data
|
|
2219
|
-
|
|
2220
|
-
**⚠️ Disadvantage:**
|
|
2221
|
-
- Can cause N+1 queries if you don't use `JOIN FETCH`
|
|
2222
|
-
|
|
2223
|
-
#### **Ejemplo EAGER (Usar con cuidado):**
|
|
2224
|
-
|
|
2225
|
-
```yaml
|
|
2226
|
-
relationships:
|
|
2227
|
-
- type: OneToMany
|
|
2228
|
-
target: OrderItem
|
|
2229
|
-
mappedBy: order
|
|
2230
|
-
cascade: [PERSIST, MERGE]
|
|
2231
|
-
fetch: EAGER # ← Always loads items with Order
|
|
2232
|
-
```
|
|
2233
|
-
|
|
2234
|
-
**Generated SQL:**
|
|
2235
|
-
```sql
|
|
2236
|
-
-- Single query: Fetches Order + OrderItems
|
|
2237
|
-
SELECT o.*, i.*
|
|
2238
|
-
FROM orders o
|
|
2239
|
-
LEFT JOIN order_items i ON i.order_id = o.id
|
|
2240
|
-
WHERE o.id = ?
|
|
2241
|
-
```
|
|
2242
|
-
|
|
2243
|
-
**✅ Advantage:**
|
|
2244
|
-
- Single SQL query
|
|
2245
|
-
- Data available immediately
|
|
2246
|
-
|
|
2247
|
-
**❌ Disadvantages:**
|
|
2248
|
-
- Loads data even if unused
|
|
2249
|
-
- Heavier queries
|
|
2250
|
-
- Can cause performance issues
|
|
2251
|
-
|
|
2252
|
-
#### **Recommended Configurations by Type:**
|
|
2253
|
-
|
|
2254
|
-
```yaml
|
|
2255
|
-
# OneToMany: ALWAYS LAZY
|
|
2256
|
-
relationships:
|
|
2257
|
-
- type: OneToMany
|
|
2258
|
-
target: OrderItem
|
|
2259
|
-
mappedBy: order
|
|
2260
|
-
cascade: [PERSIST, MERGE]
|
|
2261
|
-
fetch: LAZY # ← Avoids loading all items always
|
|
2262
|
-
|
|
2263
|
-
# ManyToOne: LAZY by default, EAGER only if always needed
|
|
2264
|
-
relationships:
|
|
2265
|
-
- type: ManyToOne
|
|
2266
|
-
target: Customer
|
|
2267
|
-
joinColumn: customer_id
|
|
2268
|
-
fetch: LAZY # ← LAZY by default
|
|
2269
|
-
|
|
2270
|
-
# OneToOne: LAZY if optional, EAGER if always exists
|
|
2271
|
-
relationships:
|
|
2272
|
-
- type: OneToOne
|
|
2273
|
-
target: OrderSummary
|
|
2274
|
-
mappedBy: order
|
|
2275
|
-
cascade: [PERSIST, MERGE]
|
|
2276
|
-
fetch: LAZY # ← LAZY if not always used
|
|
2277
|
-
```
|
|
2278
|
-
|
|
2279
|
-
#### **N+1 Problem and how to solve it:**
|
|
2280
|
-
|
|
2281
|
-
**Problem:**
|
|
2282
|
-
```java
|
|
2283
|
-
// With LAZY fetch
|
|
2284
|
-
List<Order> orders = orderRepository.findAll(); // 1 query
|
|
2285
|
-
orders.forEach(order -> {
|
|
2286
|
-
order.getOrderItems().forEach(item -> { // N queries (one per Order)
|
|
2287
|
-
System.out.println(item.getProductName());
|
|
2288
|
-
});
|
|
2289
|
-
});
|
|
2290
|
-
// Total: 1 + N queries = N+1 problem
|
|
2291
|
-
```
|
|
2292
|
-
|
|
2293
|
-
**Solution - Use JOIN FETCH in queries:**
|
|
2294
|
-
```java
|
|
2295
|
-
@Query("SELECT o FROM OrderJpa o LEFT JOIN FETCH o.orderItems WHERE o.id = :id")
|
|
2296
|
-
OrderJpa findByIdWithItems(@Param("id") String id);
|
|
2297
|
-
```
|
|
2298
|
-
|
|
2299
|
-
---
|
|
2300
|
-
|
|
2301
|
-
### When to manually define inverse relationships?
|
|
2302
|
-
|
|
2303
|
-
#### ❌ You DON'T need to define ManyToOne if:
|
|
2304
|
-
|
|
2305
|
-
You already defined `OneToMany` with `mappedBy` on the "parent" side. eva4j automatically generates the inverse relationship.
|
|
2306
|
-
|
|
2307
|
-
**Example - Only define OneToMany:**
|
|
2308
|
-
|
|
2309
|
-
```yaml
|
|
2310
|
-
# ✅ SUFFICIENT: Only define this in Order
|
|
2311
|
-
entities:
|
|
2312
|
-
- name: order
|
|
2313
|
-
isRoot: true
|
|
2314
|
-
relationships:
|
|
2315
|
-
- type: OneToMany
|
|
2316
|
-
target: OrderItem
|
|
2317
|
-
mappedBy: order # ← eva4j generates ManyToOne automatically
|
|
2318
|
-
cascade: [PERSIST, MERGE, REMOVE]
|
|
2319
|
-
fetch: LAZY
|
|
2320
|
-
|
|
2321
|
-
# ❌ DON'T NEED this in OrderItem (generated automatically)
|
|
2322
|
-
# - name: orderItem
|
|
2323
|
-
# relationships:
|
|
2324
|
-
# - type: ManyToOne
|
|
2325
|
-
# target: Order
|
|
2326
|
-
# joinColumn: order_id
|
|
2327
|
-
# fetch: LAZY
|
|
2328
|
-
```
|
|
2329
|
-
|
|
2330
|
-
**Result:** Complete bidirectional relationship with FK `order_id` generated automatically.
|
|
2331
|
-
|
|
2332
|
-
**✅ Advantages:**
|
|
2333
|
-
- Less YAML code (only define one side)
|
|
2334
|
-
- No duplication or inconsistencies
|
|
2335
|
-
- Works the same as defining both sides
|
|
2336
|
-
- FK inferred automatically: `{mappedBy}_id`
|
|
2337
|
-
|
|
2338
|
-
---
|
|
2339
|
-
|
|
2340
|
-
#### ✅ You SHOULD define ManyToOne manually if:
|
|
2341
|
-
|
|
2342
|
-
##### 1. **You need a specific FK column name**
|
|
2343
|
-
|
|
2344
|
-
```yaml
|
|
2345
|
-
# Define both sides to control FK name
|
|
2346
|
-
entities:
|
|
2347
|
-
- name: order
|
|
2348
|
-
isRoot: true
|
|
2349
|
-
relationships:
|
|
2350
|
-
- type: OneToMany
|
|
2351
|
-
target: OrderItem
|
|
2352
|
-
mappedBy: order
|
|
2353
|
-
cascade: [PERSIST, MERGE]
|
|
2354
|
-
fetch: LAZY
|
|
2355
|
-
|
|
2356
|
-
- name: orderItem
|
|
2357
|
-
relationships:
|
|
2358
|
-
- type: ManyToOne
|
|
2359
|
-
target: Order
|
|
2360
|
-
joinColumn: fk_pedido_uuid # ← Custom name
|
|
2361
|
-
fetch: LAZY
|
|
2362
|
-
```
|
|
2363
|
-
|
|
2364
|
-
**When to use:**
|
|
2365
|
-
- Your DB has specific conventions (`fk_*`, prefixes, etc.)
|
|
2366
|
-
- Need to maintain compatibility with existing schema
|
|
2367
|
-
- Migration from another tool/framework
|
|
2368
|
-
|
|
2369
|
-
---
|
|
2370
|
-
|
|
2371
|
-
##### 2. **Multiple FKs to the same entity**
|
|
2372
|
-
|
|
2373
|
-
```yaml
|
|
2374
|
-
# Transaction has 'from' and 'to' Account
|
|
2375
|
-
entities:
|
|
2376
|
-
- name: transaction
|
|
2377
|
-
tableName: transactions
|
|
2378
|
-
|
|
2379
|
-
fields:
|
|
2380
|
-
- name: id
|
|
2381
|
-
type: String
|
|
2382
|
-
- name: amount
|
|
2383
|
-
type: BigDecimal
|
|
2384
|
-
|
|
2385
|
-
relationships:
|
|
2386
|
-
# First relationship
|
|
2387
|
-
- type: ManyToOne
|
|
2388
|
-
target: Account
|
|
2389
|
-
joinColumn: from_account_id # ← Explicit name required
|
|
2390
|
-
fetch: LAZY
|
|
2391
|
-
|
|
2392
|
-
# Second relationship to same entity
|
|
2393
|
-
- type: ManyToOne
|
|
2394
|
-
target: Account
|
|
2395
|
-
joinColumn: to_account_id # ← Different FK name
|
|
2396
|
-
fetch: LAZY
|
|
2397
|
-
```
|
|
2398
|
-
|
|
2399
|
-
**When to use:**
|
|
2400
|
-
- Self-relationships (category tree, org chart)
|
|
2401
|
-
- Multiple relationships to same type (from/to, parent/child)
|
|
2402
|
-
- Can't use `mappedBy` (which one would it be?)
|
|
2403
|
-
|
|
2404
|
-
---
|
|
2405
|
-
|
|
2406
|
-
##### 3. **Unidirectional relationship (no inverse side)**
|
|
2407
|
-
|
|
2408
|
-
```yaml
|
|
2409
|
-
# OrderItem needs Product, but Product DOESN'T need OrderItems
|
|
2410
|
-
entities:
|
|
2411
|
-
- name: orderItem
|
|
2412
|
-
relationships:
|
|
2413
|
-
- type: ManyToOne
|
|
2414
|
-
target: Product # Product has NO List<OrderItem>
|
|
2415
|
-
joinColumn: product_id
|
|
2416
|
-
fetch: LAZY
|
|
2417
|
-
|
|
2418
|
-
# In Product DON'T define OneToMany
|
|
2419
|
-
- name: product
|
|
2420
|
-
isRoot: true
|
|
2421
|
-
fields:
|
|
2422
|
-
- name: id
|
|
2423
|
-
type: String
|
|
2424
|
-
- name: name
|
|
2425
|
-
type: String
|
|
2426
|
-
# No relationships to OrderItem
|
|
2427
|
-
```
|
|
2428
|
-
|
|
2429
|
-
**When to use:**
|
|
2430
|
-
- Performance: avoid loading unnecessary collections
|
|
2431
|
-
- Product is not part of Order aggregate
|
|
2432
|
-
- Only need navigation in one direction
|
|
2433
|
-
|
|
2434
|
-
---
|
|
2435
|
-
|
|
2436
|
-
#### 📊 Quick Comparison
|
|
2437
|
-
|
|
2438
|
-
| Scenario | Define ManyToOne? | Why? |
|
|
2439
|
-
|----------|-------------------|------|
|
|
2440
|
-
| Standard relationship with `mappedBy` | ❌ No | eva4j generates it automatically |
|
|
2441
|
-
| FK with custom name | ✅ Yes | To control `joinColumn` |
|
|
2442
|
-
| Multiple FKs to same entity | ✅ Yes | Need explicit names |
|
|
2443
|
-
| Unidirectional relationship | ✅ Yes | No inverse side (`mappedBy`) |
|
|
2444
|
-
| Specific DB conventions | ✅ Yes | To comply with standards |
|
|
2445
|
-
| Simple standard case | ❌ No | Let eva4j generate it |
|
|
2446
|
-
|
|
2447
|
-
---
|
|
2448
|
-
|
|
2449
|
-
#### ⚠️ Error Común
|
|
2450
|
-
|
|
2451
|
-
**NO hagas esto:**
|
|
2452
|
-
|
|
2453
|
-
```yaml
|
|
2454
|
-
# ❌ INCORRECTO: Inconsistencia entre ambos lados
|
|
2455
|
-
entities:
|
|
2456
|
-
- name: order
|
|
2457
|
-
isRoot: true
|
|
2458
|
-
relationships:
|
|
2459
|
-
- type: OneToMany
|
|
2460
|
-
target: OrderItem
|
|
2461
|
-
mappedBy: order # ← Espera campo "order" en OrderItem
|
|
2462
|
-
fetch: LAZY
|
|
2463
|
-
|
|
2464
|
-
- name: orderItem
|
|
2465
|
-
relationships:
|
|
2466
|
-
- type: ManyToOne
|
|
2467
|
-
target: Order
|
|
2468
|
-
joinColumn: pedido_id # ← Pero la FK se llama diferente
|
|
2469
|
-
fetch: LAZY
|
|
2470
|
-
```
|
|
2471
|
-
|
|
2472
|
-
**Problema:** `mappedBy: order` busca un campo llamado `order`, pero `pedido_id` no coincide con la convención de nombres.
|
|
2473
|
-
|
|
2474
|
-
**✅ Soluciones:**
|
|
2475
|
-
|
|
2476
|
-
**Opción A - Deja que eva4j genere automáticamente:**
|
|
2477
|
-
```yaml
|
|
2478
|
-
# Solo define OneToMany, eva4j genera ManyToOne correctamente
|
|
2479
|
-
entities:
|
|
2480
|
-
- name: order
|
|
2481
|
-
isRoot: true
|
|
2482
|
-
relationships:
|
|
2483
|
-
- type: OneToMany
|
|
2484
|
-
target: OrderItem
|
|
2485
|
-
mappedBy: order
|
|
2486
|
-
fetch: LAZY
|
|
2487
|
-
```
|
|
2488
|
-
|
|
2489
|
-
**Opción B - Define ambos lados consistentemente:**
|
|
2490
|
-
```yaml
|
|
2491
|
-
entities:
|
|
2492
|
-
- name: order
|
|
2493
|
-
isRoot: true
|
|
2494
|
-
relationships:
|
|
2495
|
-
- type: OneToMany
|
|
2496
|
-
target: OrderItem
|
|
2497
|
-
mappedBy: pedido # ← Coincide con el nombre del campo
|
|
2498
|
-
fetch: LAZY
|
|
2499
|
-
|
|
2500
|
-
- name: orderItem
|
|
2501
|
-
relationships:
|
|
2502
|
-
- type: ManyToOne
|
|
2503
|
-
target: Order
|
|
2504
|
-
joinColumn: pedido_id
|
|
2505
|
-
fetch: LAZY
|
|
2506
|
-
```
|
|
2507
|
-
|
|
2508
|
-
---
|
|
2509
|
-
|
|
2510
|
-
#### 💡 Recomendación General
|
|
2511
|
-
|
|
2512
|
-
**Para el 90% de los casos:**
|
|
2513
|
-
|
|
2514
|
-
```yaml
|
|
2515
|
-
# ✅ MEJOR PRÁCTICA: Solo define OneToMany
|
|
2516
|
-
entities:
|
|
2517
|
-
- name: order
|
|
2518
|
-
isRoot: true
|
|
2519
|
-
relationships:
|
|
2520
|
-
- type: OneToMany
|
|
2521
|
-
target: OrderItem
|
|
2522
|
-
mappedBy: order
|
|
2523
|
-
cascade: [PERSIST, MERGE, REMOVE]
|
|
2524
|
-
fetch: LAZY
|
|
2525
|
-
|
|
2526
|
-
# NO definas ManyToOne en OrderItem
|
|
2527
|
-
# eva4j lo genera automáticamente con:
|
|
2528
|
-
# - @JoinColumn(name = "order_id")
|
|
2529
|
-
# - @ManyToOne(fetch = FetchType.LAZY)
|
|
2530
|
-
```
|
|
2531
|
-
|
|
2532
|
-
**Solo define ambos lados cuando necesites control específico.**
|
|
2533
|
-
|
|
2534
|
-
---
|
|
2535
|
-
|
|
2536
|
-
## Tipos de Datos
|
|
2537
|
-
|
|
2538
|
-
### Tipos primitivos Java
|
|
2539
|
-
|
|
2540
|
-
| YAML | Java | JPA | Observaciones |
|
|
2541
|
-
|------|------|-----|---------------|
|
|
2542
|
-
| `String` | String | VARCHAR | En ID genera UUID |
|
|
2543
|
-
| `Integer` | Integer | INTEGER | En ID genera IDENTITY |
|
|
2544
|
-
| `Long` | Long | BIGINT | En ID genera IDENTITY |
|
|
2545
|
-
| `Double` | Double | DOUBLE | - |
|
|
2546
|
-
| `Float` | Float | FLOAT | - |
|
|
2547
|
-
| `Boolean` | Boolean | BOOLEAN | - |
|
|
2548
|
-
| `BigDecimal` | BigDecimal | DECIMAL | Importa automáticamente |
|
|
2549
|
-
|
|
2550
|
-
### Tipos de fecha/hora
|
|
2551
|
-
|
|
2552
|
-
| YAML | Java | Importa automáticamente |
|
|
2553
|
-
|------|------|------------------------|
|
|
2554
|
-
| `LocalDate` | LocalDate | java.time.LocalDate |
|
|
2555
|
-
| `LocalDateTime` | LocalDateTime | java.time.LocalDateTime |
|
|
2556
|
-
| `LocalTime` | LocalTime | java.time.LocalTime |
|
|
2557
|
-
|
|
2558
|
-
### Tipos especiales
|
|
2559
|
-
|
|
2560
|
-
| YAML | Java | Uso |
|
|
2561
|
-
|------|------|-----|
|
|
2562
|
-
| `UUID` | UUID | IDs únicos |
|
|
2563
|
-
| Cualquier Enum | Enum personalizado | Estados, tipos |
|
|
2564
|
-
| Cualquier VO | Value Object | Conceptos de dominio |
|
|
2565
|
-
|
|
2566
|
-
### Colecciones
|
|
2567
|
-
|
|
2568
|
-
#### Lista de primitivos
|
|
2569
|
-
|
|
2570
|
-
```yaml
|
|
2571
|
-
fields:
|
|
2572
|
-
- name: tags
|
|
2573
|
-
type: List<String>
|
|
2574
|
-
```
|
|
2575
|
-
|
|
2576
|
-
Genera:
|
|
2577
|
-
```java
|
|
2578
|
-
@ElementCollection
|
|
2579
|
-
@CollectionTable(name = "order_tags", joinColumns = @JoinColumn(name = "order_id"))
|
|
2580
|
-
@Column(name = "tags")
|
|
2581
|
-
@Builder.Default
|
|
2582
|
-
private List<String> tags = new ArrayList<>();
|
|
2583
|
-
```
|
|
2584
|
-
|
|
2585
|
-
#### Lista de Value Objects
|
|
2586
|
-
|
|
2587
|
-
```yaml
|
|
2588
|
-
fields:
|
|
2589
|
-
- name: addresses
|
|
2590
|
-
type: List<Address> # Address es un VO definido
|
|
2591
|
-
```
|
|
2592
|
-
|
|
2593
|
-
Genera:
|
|
2594
|
-
```java
|
|
2595
|
-
@ElementCollection
|
|
2596
|
-
@CollectionTable(name = "customer_addresses", joinColumns = @JoinColumn(name = "customer_id"))
|
|
2597
|
-
@Builder.Default
|
|
2598
|
-
private List<AddressJpa> addresses = new ArrayList<>();
|
|
2599
|
-
```
|
|
2600
|
-
|
|
2601
|
-
---
|
|
2602
|
-
|
|
2603
|
-
## Ejemplos Completos
|
|
2604
|
-
|
|
2605
|
-
### Ejemplo 1: E-Commerce (Order)
|
|
2606
|
-
|
|
2607
|
-
```yaml
|
|
2608
|
-
aggregates:
|
|
2609
|
-
- name: Order
|
|
2610
|
-
entities:
|
|
2611
|
-
- name: order
|
|
2612
|
-
isRoot: true
|
|
2613
|
-
tableName: orders
|
|
2614
|
-
|
|
2615
|
-
fields:
|
|
2616
|
-
- name: id
|
|
2617
|
-
type: String
|
|
2618
|
-
|
|
2619
|
-
- name: orderNumber
|
|
2620
|
-
type: String
|
|
2621
|
-
|
|
2622
|
-
- name: customerId
|
|
2623
|
-
type: String
|
|
2624
|
-
|
|
2625
|
-
- name: status
|
|
2626
|
-
type: OrderStatus
|
|
2627
|
-
|
|
2628
|
-
- name: totalAmount
|
|
2629
|
-
type: Money
|
|
2630
|
-
|
|
2631
|
-
- name: shippingAddress
|
|
2632
|
-
type: Address
|
|
2633
|
-
|
|
2634
|
-
- name: createdAt
|
|
2635
|
-
type: LocalDateTime
|
|
2636
|
-
|
|
2637
|
-
- name: updatedAt
|
|
2638
|
-
type: LocalDateTime
|
|
2639
|
-
|
|
2640
|
-
relationships:
|
|
2641
|
-
- type: OneToMany
|
|
2642
|
-
target: OrderItem
|
|
2643
|
-
mappedBy: order
|
|
2644
|
-
cascade: [PERSIST, MERGE, REMOVE]
|
|
2645
|
-
fetch: LAZY
|
|
2646
|
-
|
|
2647
|
-
- name: orderItem
|
|
2648
|
-
tableName: order_items
|
|
2649
|
-
|
|
2650
|
-
fields:
|
|
2651
|
-
- name: id
|
|
2652
|
-
type: Long
|
|
2653
|
-
|
|
2654
|
-
- name: productId
|
|
2655
|
-
type: String
|
|
2656
|
-
|
|
2657
|
-
- name: productName
|
|
2658
|
-
type: String
|
|
2659
|
-
|
|
2660
|
-
- name: quantity
|
|
2661
|
-
type: Integer
|
|
2662
|
-
|
|
2663
|
-
- name: unitPrice
|
|
2664
|
-
type: Money
|
|
2665
|
-
|
|
2666
|
-
- name: subtotal
|
|
2667
|
-
type: Money
|
|
2668
|
-
|
|
2669
|
-
relationships:
|
|
2670
|
-
- type: ManyToOne
|
|
2671
|
-
target: Order
|
|
2672
|
-
joinColumn: order_id
|
|
2673
|
-
fetch: LAZY
|
|
2674
|
-
|
|
2675
|
-
valueObjects:
|
|
2676
|
-
- name: Money
|
|
2677
|
-
fields:
|
|
2678
|
-
- name: amount
|
|
2679
|
-
type: BigDecimal
|
|
2680
|
-
- name: currency
|
|
2681
|
-
type: String
|
|
2682
|
-
|
|
2683
|
-
- name: Address
|
|
2684
|
-
fields:
|
|
2685
|
-
- name: street
|
|
2686
|
-
type: String
|
|
2687
|
-
- name: city
|
|
2688
|
-
type: String
|
|
2689
|
-
- name: state
|
|
2690
|
-
type: String
|
|
2691
|
-
- name: zipCode
|
|
2692
|
-
type: String
|
|
2693
|
-
- name: country
|
|
2694
|
-
type: String
|
|
2695
|
-
|
|
2696
|
-
enums:
|
|
2697
|
-
- name: OrderStatus
|
|
2698
|
-
values:
|
|
2699
|
-
- PENDING
|
|
2700
|
-
- CONFIRMED
|
|
2701
|
-
- PROCESSING
|
|
2702
|
-
- SHIPPED
|
|
2703
|
-
- DELIVERED
|
|
2704
|
-
- CANCELLED
|
|
2705
|
-
- REFUNDED
|
|
2706
|
-
```
|
|
2707
|
-
|
|
2708
|
-
### Ejemplo 2: Blog (Post)
|
|
2709
|
-
|
|
2710
|
-
```yaml
|
|
2711
|
-
aggregates:
|
|
2712
|
-
- name: Post
|
|
2713
|
-
entities:
|
|
2714
|
-
- name: post
|
|
2715
|
-
isRoot: true
|
|
2716
|
-
tableName: posts
|
|
2717
|
-
|
|
2718
|
-
fields:
|
|
2719
|
-
- name: id
|
|
2720
|
-
type: Long
|
|
2721
|
-
|
|
2722
|
-
- name: title
|
|
2723
|
-
type: String
|
|
2724
|
-
|
|
2725
|
-
- name: slug
|
|
2726
|
-
type: String
|
|
2727
|
-
|
|
2728
|
-
- name: content
|
|
2729
|
-
type: String
|
|
2730
|
-
|
|
2731
|
-
- name: authorId
|
|
2732
|
-
type: String
|
|
2733
|
-
|
|
2734
|
-
- name: status
|
|
2735
|
-
type: PostStatus
|
|
2736
|
-
|
|
2737
|
-
- name: publishedAt
|
|
2738
|
-
type: LocalDateTime
|
|
2739
|
-
|
|
2740
|
-
- name: tags
|
|
2741
|
-
type: List<String>
|
|
2742
|
-
|
|
2743
|
-
- name: metadata
|
|
2744
|
-
type: PostMetadata
|
|
2745
|
-
|
|
2746
|
-
relationships:
|
|
2747
|
-
- type: OneToMany
|
|
2748
|
-
target: Comment
|
|
2749
|
-
mappedBy: post
|
|
2750
|
-
cascade: [PERSIST, MERGE, REMOVE]
|
|
2751
|
-
fetch: LAZY
|
|
2752
|
-
|
|
2753
|
-
- name: comment
|
|
2754
|
-
tableName: comments
|
|
2755
|
-
|
|
2756
|
-
fields:
|
|
2757
|
-
- name: id
|
|
2758
|
-
type: Long
|
|
2759
|
-
|
|
2760
|
-
- name: authorId
|
|
2761
|
-
type: String
|
|
2762
|
-
|
|
2763
|
-
- name: authorName
|
|
2764
|
-
type: String
|
|
2765
|
-
|
|
2766
|
-
- name: content
|
|
2767
|
-
type: String
|
|
2768
|
-
|
|
2769
|
-
- name: createdAt
|
|
2770
|
-
type: LocalDateTime
|
|
2771
|
-
|
|
2772
|
-
- name: approved
|
|
2773
|
-
type: Boolean
|
|
2774
|
-
|
|
2775
|
-
relationships:
|
|
2776
|
-
- type: ManyToOne
|
|
2777
|
-
target: Post
|
|
2778
|
-
joinColumn: post_id
|
|
2779
|
-
fetch: LAZY
|
|
2780
|
-
|
|
2781
|
-
valueObjects:
|
|
2782
|
-
- name: PostMetadata
|
|
2783
|
-
fields:
|
|
2784
|
-
- name: viewCount
|
|
2785
|
-
type: Integer
|
|
2786
|
-
- name: likeCount
|
|
2787
|
-
type: Integer
|
|
2788
|
-
- name: shareCount
|
|
2789
|
-
type: Integer
|
|
2790
|
-
|
|
2791
|
-
enums:
|
|
2792
|
-
- name: PostStatus
|
|
2793
|
-
values: [DRAFT, PUBLISHED, ARCHIVED, DELETED]
|
|
2794
|
-
```
|
|
2795
|
-
|
|
2796
|
-
### Ejemplo 3: Banking (Account)
|
|
2797
|
-
|
|
2798
|
-
```yaml
|
|
2799
|
-
aggregates:
|
|
2800
|
-
- name: Account
|
|
2801
|
-
entities:
|
|
2802
|
-
- name: account
|
|
2803
|
-
isRoot: true
|
|
2804
|
-
tableName: accounts
|
|
2805
|
-
|
|
2806
|
-
fields:
|
|
2807
|
-
- name: id
|
|
2808
|
-
type: String
|
|
2809
|
-
|
|
2810
|
-
- name: accountNumber
|
|
2811
|
-
type: String
|
|
2812
|
-
|
|
2813
|
-
- name: customerId
|
|
2814
|
-
type: String
|
|
2815
|
-
|
|
2816
|
-
- name: accountType
|
|
2817
|
-
type: AccountType
|
|
2818
|
-
|
|
2819
|
-
- name: balance
|
|
2820
|
-
type: Money
|
|
2821
|
-
|
|
2822
|
-
- name: status
|
|
2823
|
-
type: AccountStatus
|
|
2824
|
-
|
|
2825
|
-
- name: openedAt
|
|
2826
|
-
type: LocalDate
|
|
2827
|
-
|
|
2828
|
-
relationships:
|
|
2829
|
-
- type: OneToMany
|
|
2830
|
-
target: Transaction
|
|
2831
|
-
mappedBy: account
|
|
2832
|
-
cascade: [PERSIST, MERGE]
|
|
2833
|
-
fetch: LAZY
|
|
2834
|
-
|
|
2835
|
-
- name: transaction
|
|
2836
|
-
tableName: transactions
|
|
2837
|
-
|
|
2838
|
-
fields:
|
|
2839
|
-
- name: id
|
|
2840
|
-
type: String
|
|
2841
|
-
|
|
2842
|
-
- name: transactionNumber
|
|
2843
|
-
type: String
|
|
2844
|
-
|
|
2845
|
-
- name: type
|
|
2846
|
-
type: TransactionType
|
|
2847
|
-
|
|
2848
|
-
- name: amount
|
|
2849
|
-
type: Money
|
|
2850
|
-
|
|
2851
|
-
- name: description
|
|
2852
|
-
type: String
|
|
2853
|
-
|
|
2854
|
-
- name: timestamp
|
|
2855
|
-
type: LocalDateTime
|
|
2856
|
-
|
|
2857
|
-
- name: balanceAfter
|
|
2858
|
-
type: Money
|
|
2859
|
-
|
|
2860
|
-
relationships:
|
|
2861
|
-
- type: ManyToOne
|
|
2862
|
-
target: Account
|
|
2863
|
-
joinColumn: account_id
|
|
2864
|
-
fetch: LAZY
|
|
2865
|
-
|
|
2866
|
-
valueObjects:
|
|
2867
|
-
- name: Money
|
|
2868
|
-
fields:
|
|
2869
|
-
- name: amount
|
|
2870
|
-
type: BigDecimal
|
|
2871
|
-
- name: currency
|
|
2872
|
-
type: String
|
|
2873
|
-
|
|
2874
|
-
enums:
|
|
2875
|
-
- name: AccountType
|
|
2876
|
-
values: [CHECKING, SAVINGS, INVESTMENT, CREDIT]
|
|
2877
|
-
|
|
2878
|
-
- name: AccountStatus
|
|
2879
|
-
values: [ACTIVE, INACTIVE, SUSPENDED, CLOSED]
|
|
2880
|
-
|
|
2881
|
-
- name: TransactionType
|
|
2882
|
-
values: [DEPOSIT, WITHDRAWAL, TRANSFER, FEE, INTEREST]
|
|
2883
|
-
```
|
|
2884
|
-
|
|
2885
|
-
### Ejemplo 4: Múltiples Agregados en un módulo
|
|
2886
|
-
|
|
2887
|
-
```yaml
|
|
2888
|
-
aggregates:
|
|
2889
|
-
- name: Customer
|
|
2890
|
-
entities:
|
|
2891
|
-
- name: customer
|
|
2892
|
-
isRoot: true
|
|
2893
|
-
fields:
|
|
2894
|
-
- name: id
|
|
2895
|
-
type: String
|
|
2896
|
-
- name: name
|
|
2897
|
-
type: String
|
|
2898
|
-
- name: email
|
|
2899
|
-
type: String
|
|
2900
|
-
- name: phone
|
|
2901
|
-
type: String
|
|
2902
|
-
- name: registeredAt
|
|
2903
|
-
type: LocalDateTime
|
|
2904
|
-
|
|
2905
|
-
valueObjects:
|
|
2906
|
-
- name: ContactInfo
|
|
2907
|
-
fields:
|
|
2908
|
-
- name: email
|
|
2909
|
-
type: String
|
|
2910
|
-
- name: phone
|
|
2911
|
-
type: String
|
|
2912
|
-
|
|
2913
|
-
- name: Product
|
|
2914
|
-
entities:
|
|
2915
|
-
- name: product
|
|
2916
|
-
isRoot: true
|
|
2917
|
-
fields:
|
|
2918
|
-
- name: id
|
|
2919
|
-
type: String
|
|
2920
|
-
- name: name
|
|
2921
|
-
type: String
|
|
2922
|
-
- name: description
|
|
2923
|
-
type: String
|
|
2924
|
-
- name: price
|
|
2925
|
-
type: Money
|
|
2926
|
-
- name: stock
|
|
2927
|
-
type: Integer
|
|
2928
|
-
- name: category
|
|
2929
|
-
type: ProductCategory
|
|
2930
|
-
|
|
2931
|
-
valueObjects:
|
|
2932
|
-
- name: Money
|
|
2933
|
-
fields:
|
|
2934
|
-
- name: amount
|
|
2935
|
-
type: BigDecimal
|
|
2936
|
-
- name: currency
|
|
2937
|
-
type: String
|
|
2938
|
-
|
|
2939
|
-
enums:
|
|
2940
|
-
- name: ProductCategory
|
|
2941
|
-
values: [ELECTRONICS, CLOTHING, FOOD, BOOKS, TOYS]
|
|
2942
|
-
```
|
|
2943
|
-
|
|
2944
|
-
---
|
|
2945
|
-
|
|
2946
|
-
## Comando de Generación
|
|
2947
|
-
|
|
2948
|
-
```bash
|
|
2949
|
-
# Generar todas las entidades del módulo
|
|
2950
|
-
eva4j generate entities <module-name>
|
|
2951
|
-
```
|
|
2952
|
-
|
|
2953
|
-
### Salida generada
|
|
2954
|
-
|
|
2955
|
-
```
|
|
2956
|
-
✓ Found 1 aggregate(s) and 1 enum(s)
|
|
2957
|
-
|
|
2958
|
-
📦 Aggregates to generate:
|
|
2959
|
-
├── Order (Root: Order)
|
|
2960
|
-
│ ├── OrderItem
|
|
2961
|
-
│ └── Money (VO)
|
|
2962
|
-
|
|
2963
|
-
⠋ Generating files...
|
|
2964
|
-
|
|
2965
|
-
✅ Successfully generated 13 files for module 'order'
|
|
2966
|
-
|
|
2967
|
-
📁 Generated Files:
|
|
2968
|
-
✓ Enum: OrderStatus
|
|
2969
|
-
✓ Domain Entity: Order
|
|
2970
|
-
✓ JPA Entity: OrderJpa
|
|
2971
|
-
✓ Domain Entity: OrderItem
|
|
2972
|
-
✓ JPA Entity: OrderItemJpa
|
|
2973
|
-
✓ Domain VO: Money
|
|
2974
|
-
✓ JPA VO: MoneyJpa
|
|
2975
|
-
✓ Mapper: OrderMapper
|
|
2976
|
-
✓ Repository: OrderRepository
|
|
2977
|
-
✓ JPA Repository: OrderJpaRepository
|
|
2978
|
-
✓ Repository Impl: OrderRepositoryImpl
|
|
2979
|
-
```
|
|
2980
|
-
|
|
2981
|
-
---
|
|
2982
|
-
|
|
2983
|
-
## Tips y Mejores Prácticas
|
|
2984
|
-
|
|
2985
|
-
### ✅ Hacer
|
|
2986
|
-
|
|
2987
|
-
1. **Usa nombres descriptivos**: `orderNumber` en lugar de `number`
|
|
2988
|
-
2. **PascalCase para tipos**: `OrderStatus`, `Money`, `Address`
|
|
2989
|
-
3. **camelCase para campos**: `totalAmount`, `createdAt`
|
|
2990
|
-
4. **snake_case para tablas**: `order_items`, `customer_addresses`
|
|
2991
|
-
5. **Define IDs apropiados**: String para UUIDs, Long para secuencias
|
|
2992
|
-
6. **Usa Value Objects**: Para conceptos cohesivos (Money, Address)
|
|
2993
|
-
7. **Cascade apropiado**: PERSIST, MERGE para agregados; evita ALL
|
|
2994
|
-
|
|
2995
|
-
### ❌ Evitar
|
|
2996
|
-
|
|
2997
|
-
1. **Don't use Long for UUIDs**: Use String
|
|
2998
|
-
2. **Don't create bidirectional relationships without mappedBy**: Define the owner
|
|
2999
|
-
3. **Don't use EAGER without reason**: LAZY is better for performance
|
|
3000
|
-
4. **Don't mix concepts**: One aggregate = one transaction
|
|
3001
|
-
5. **Don't use @Column in domain.yaml**: It's for JPA, generated automatically
|
|
3002
|
-
|
|
3003
|
-
---
|
|
3004
|
-
|
|
3005
|
-
## Current Support and Limitations
|
|
3006
|
-
|
|
3007
|
-
### ✅ Supported
|
|
3008
|
-
|
|
3009
|
-
- Aggregates with root and secondary entities
|
|
3010
|
-
- Embedded Value Objects
|
|
3011
|
-
- Enums with values
|
|
3012
|
-
- OneToMany, ManyToOne, OneToOne relationships
|
|
3013
|
-
- Java primitive and date types
|
|
3014
|
-
- Collections of primitives and VOs
|
|
3015
|
-
- IDs: String (UUID), Long/Integer (IDENTITY)
|
|
3016
|
-
- Custom Cascade and Fetch
|
|
3017
|
-
|
|
3018
|
-
### 🚧 Coming Soon
|
|
3019
|
-
|
|
3020
|
-
- JSR-303 validations
|
|
3021
|
-
- Automatic auditing
|
|
3022
|
-
- Soft delete
|
|
3023
|
-
- Custom query methods
|
|
3024
|
-
- Indexes and constraints
|
|
3025
|
-
- Entity inheritance
|
|
3026
|
-
|
|
3027
|
-
---
|
|
3028
|
-
|
|
3029
|
-
## Frequently Asked Questions
|
|
3030
|
-
|
|
3031
|
-
**Q: Can I have multiple aggregates in one domain.yaml?**
|
|
3032
|
-
A: Yes, define multiple entries in the `aggregates` array.
|
|
3033
|
-
|
|
3034
|
-
**Q: How do I reference an enum from another aggregate?**
|
|
3035
|
-
A: Enums are global to the module, just use the name: `type: OrderStatus`
|
|
3036
|
-
|
|
3037
|
-
**Q: Can I use a VO in multiple aggregates?**
|
|
3038
|
-
A: Yes, but you must define it in each aggregate (for now).
|
|
3039
|
-
|
|
3040
|
-
**Q: What happens if I regenerate the code?**
|
|
3041
|
-
A: Files are overwritten. Modify only in templates, not in generated code.
|
|
3042
|
-
|
|
3043
|
-
**Q: Can I customize generated entities?**
|
|
3044
|
-
A: Yes, modify the templates in `templates/aggregate/`.
|
|
3045
|
-
|
|
3046
|
-
---
|
|
3047
|
-
|
|
3048
|
-
## Additional Resources
|
|
3049
|
-
|
|
3050
|
-
- [Implementation Guide](IMPLEMENTATION_SUMMARY.md)
|
|
3051
|
-
- [Testing Guide](TESTING_GUIDE.md)
|
|
3052
|
-
- [Quick Reference](QUICK_REFERENCE.md)
|
|
3053
|
-
- [DDD Documentation](https://martinfowler.com/bliki/DomainDrivenDesign.html)
|
|
3054
|
-
|
|
3055
|
-
---
|
|
3056
|
-
|
|
3057
|
-
**Ready to start?** Create your `domain.yaml` and run:
|
|
3058
|
-
|
|
3059
|
-
```bash
|
|
3060
|
-
eva4j generate entities <your-module>
|
|
3061
|
-
```
|
|
904
|
+
| `Module does not exist` | Module was not created | Run `eva add module <module>` |
|
|
905
|
+
| `YAML file not found` | No `domain.yaml` at the expected path | Check `src/main/java/<pkg>/<module>/domain.yaml` |
|
|
906
|
+
| `Invalid relationship target` | Target entity not defined in the same YAML | Define the target entity in the same `domain.yaml` |
|
|
907
|
+
| `Column 'x_id' is duplicated` | ManyToOne defined manually + auto-generated | Remove the manual ManyToOne; let eva4j generate it |
|
|
908
|
+
| File not regenerated | File was manually modified (checksum) | Use `--force` to overwrite |
|
|
909
|
+
| Import errors | Field `type` doesn't match name in `enums:` or `valueObjects:` | Verify names match exactly |
|