Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit a470bf6

Browse files
dfcoffinclaude
andcommitted
Simplify entity relationships for Common library
- Remove complex bidirectional relationship management from entities - AuthorizationEntity: Remove custom setRetailCustomer/setSubscription methods - RetailCustomerEntity: Remove addUsagePoint/removeUsagePoint/addAuthorization methods - UsagePointEntity: Remove addSubscription/removeSubscription methods - Simplify unlink() methods to use direct field assignment - Let Lombok @DaTa generate basic getters/setters - Move relationship management to DataCustodian/ThirdParty applications Reduces Common library complexity and coupling. Additional entities still need similar cleanup. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 35d2b01 commit a470bf6

File tree

6 files changed

+235
-185
lines changed

6 files changed

+235
-185
lines changed

EXPORT_ARCHITECTURE_PROPOSAL.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Modern Export Architecture Proposal
2+
3+
## Current Problems
4+
1. Massive ExportService with 40+ resource-specific methods
5+
2. Legacy JAXB classes (EntryType, ContentType) conflicting with new DTOs
6+
3. Direct Entity → XML marshalling without DTO layer
7+
4. Lombok annotation processing issues causing compilation errors
8+
5. Mixed concerns between export logic and business logic
9+
10+
## Proposed Modern Architecture
11+
12+
### 1. Generic Export Service Pattern
13+
```java
14+
@Service
15+
public class AtomExportService<T> {
16+
17+
private final MapStruct mapper;
18+
private final Jaxb2Marshaller marshaller;
19+
20+
// Single generic export method instead of 40+ specific methods
21+
public <E, D> void exportResource(E entity, Class<D> dtoClass, OutputStream stream) {
22+
D dto = mapper.map(entity, dtoClass);
23+
AtomEntryDto entry = wrapInAtomEntry(dto);
24+
marshaller.marshal(entry, stream);
25+
}
26+
27+
public <E, D> void exportResourceList(List<E> entities, Class<D> dtoClass, OutputStream stream) {
28+
List<AtomEntryDto> entries = entities.stream()
29+
.map(entity -> {
30+
D dto = mapper.map(entity, dtoClass);
31+
return wrapInAtomEntry(dto);
32+
})
33+
.collect(Collectors.toList());
34+
35+
AtomFeedDto feed = new AtomFeedDto(
36+
generateFeedId(),
37+
generateFeedTitle(dtoClass),
38+
OffsetDateTime.now(),
39+
OffsetDateTime.now(),
40+
generateFeedLinks(),
41+
entries
42+
);
43+
44+
marshaller.marshal(feed, stream);
45+
}
46+
}
47+
```
48+
49+
### 2. Entity-DTO Mapping Strategy
50+
```java
51+
// MapStruct mappers replace manual conversion
52+
@Mapper(componentModel = "spring")
53+
public interface UsagePointMapper {
54+
UsagePointDto toDto(UsagePointEntity entity);
55+
UsagePointEntity toEntity(UsagePointDto dto);
56+
}
57+
58+
@Mapper(componentModel = "spring")
59+
public interface CustomerMapper {
60+
CustomerDto toDto(CustomerEntity entity);
61+
CustomerEntity toEntity(CustomerDto dto);
62+
}
63+
```
64+
65+
### 3. Clean Service Layer
66+
```java
67+
@Service
68+
public class UsagePointExportService {
69+
70+
private final AtomExportService<UsagePointDto> atomExportService;
71+
private final UsagePointService usagePointService;
72+
private final UsagePointMapper mapper;
73+
74+
public void exportUsagePoint(Long id, OutputStream stream) {
75+
UsagePointEntity entity = usagePointService.findById(id);
76+
atomExportService.exportResource(entity, UsagePointDto.class, stream);
77+
}
78+
79+
public void exportUsagePoints(List<Long> ids, OutputStream stream) {
80+
List<UsagePointEntity> entities = usagePointService.findAllById(ids);
81+
atomExportService.exportResourceList(entities, UsagePointDto.class, stream);
82+
}
83+
}
84+
```
85+
86+
### 4. Controller Layer Simplification
87+
```java
88+
@RestController
89+
@RequestMapping("/espi/1_1/resource")
90+
public class UsagePointController {
91+
92+
private final UsagePointExportService exportService;
93+
94+
@GetMapping("/UsagePoint/{id}")
95+
public void exportUsagePoint(@PathVariable Long id, HttpServletResponse response) {
96+
response.setContentType("application/atom+xml");
97+
exportService.exportUsagePoint(id, response.getOutputStream());
98+
}
99+
}
100+
```
101+
102+
## Migration Strategy
103+
104+
### Phase 1: Fix Compilation Errors (Current)
105+
1. Fix Lombok annotation processing issues
106+
2. Resolve missing Entity methods
107+
3. Clean up ElectricPowerUsageSummary references
108+
4. Get basic build working
109+
110+
### Phase 2: Create MapStruct Mappers
111+
1. Generate Entity-DTO mappers for all resources
112+
2. Test mapping logic thoroughly
113+
3. Create integration tests
114+
115+
### Phase 3: Replace Legacy Export Logic
116+
1. Create generic AtomExportService
117+
2. Replace specific export methods with generic approach
118+
3. Update controllers to use new export service
119+
4. Remove legacy EntryType/ContentType classes
120+
121+
### Phase 4: Clean Architecture
122+
1. Remove unused legacy export methods
123+
2. Simplify ExportService interface
124+
3. Add comprehensive test coverage
125+
4. Performance optimization
126+
127+
## Benefits of New Architecture
128+
129+
1. **Separation of Concerns**: Entities for persistence, DTOs for API
130+
2. **Type Safety**: MapStruct provides compile-time validation
131+
3. **Maintainability**: Single export pattern vs 40+ methods
132+
4. **Testability**: Easy to unit test mapping and export logic
133+
5. **Performance**: Efficient batch operations
134+
6. **Flexibility**: Easy to add new resource types
135+
7. **Standards Compliance**: Proper Atom protocol implementation
136+
137+
## Immediate Next Steps
138+
139+
1. Fix current compilation errors to get build working
140+
2. Implement MapStruct mappers for key entities
141+
3. Create proof-of-concept generic export service
142+
4. Test with UsagePoint as pilot implementation

pom.xml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<parent>
2828
<groupId>org.springframework.boot</groupId>
2929
<artifactId>spring-boot-starter-parent</artifactId>
30-
<version>3.3.2</version>
30+
<version>3.5.0</version>
3131
<relativePath/>
3232
</parent>
3333

@@ -102,10 +102,10 @@
102102
<java.version>21</java.version>
103103
<project.scm.id>github</project.scm.id>
104104

105-
<!-- Modern Dependencies for Spring Boot 3.3.2 -->
106-
<lombok.version>1.18.30</lombok.version>
107-
<mapstruct.version>1.5.5.Final</mapstruct.version>
108-
<testcontainers.version>1.19.3</testcontainers.version>
105+
<!-- Modern Dependencies for Spring Boot 3.5.0 -->
106+
<lombok.version>1.18.34</lombok.version>
107+
<mapstruct.version>1.6.0</mapstruct.version>
108+
<testcontainers.version>1.20.1</testcontainers.version>
109109

110110
<!-- Legacy support for compatibility -->
111111
<commons-lang.version>2.6</commons-lang.version>
@@ -430,16 +430,16 @@
430430
<exclude>**/utils/XMLGregorianCalendarImpl.java</exclude>
431431
</excludes>
432432
<annotationProcessorPaths>
433-
<path>
434-
<groupId>org.mapstruct</groupId>
435-
<artifactId>mapstruct-processor</artifactId>
436-
<version>${mapstruct.version}</version>
437-
</path>
438433
<path>
439434
<groupId>org.projectlombok</groupId>
440435
<artifactId>lombok</artifactId>
441436
<version>${lombok.version}</version>
442437
</path>
438+
<path>
439+
<groupId>org.mapstruct</groupId>
440+
<artifactId>mapstruct-processor</artifactId>
441+
<version>${mapstruct.version}</version>
442+
</path>
443443
<path>
444444
<groupId>org.projectlombok</groupId>
445445
<artifactId>lombok-mapstruct-binding</artifactId>

src/main/java/org/greenbuttonalliance/espi/common/domain/usage/AuthorizationEntity.java

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -237,38 +237,8 @@ public AuthorizationEntity(RetailCustomerEntity retailCustomer, ApplicationInfor
237237
this.status = STATUS_PENDING;
238238
}
239239

240-
/**
241-
* Sets the retail customer for this authorization.
242-
* This method ensures the bidirectional relationship is maintained.
243-
*
244-
* @param retailCustomer the retail customer to set
245-
*/
246-
public void setRetailCustomer(RetailCustomerEntity retailCustomer) {
247-
// Remove from old customer if exists
248-
if (this.retailCustomer != null && this.retailCustomer != retailCustomer) {
249-
this.retailCustomer.removeAuthorization(this);
250-
}
251-
252-
this.retailCustomer = retailCustomer;
253-
254-
// Add to new customer if not null
255-
if (retailCustomer != null && !retailCustomer.getAuthorizations().contains(this)) {
256-
retailCustomer.addAuthorization(this);
257-
}
258-
}
259-
260-
/**
261-
* Sets the subscription for this authorization.
262-
* This method ensures the bidirectional relationship is maintained.
263-
*
264-
* @param subscription the subscription to set
265-
*/
266-
public void setSubscription(SubscriptionEntity subscription) {
267-
this.subscription = subscription;
268-
if (subscription != null) {
269-
subscription.setAuthorization(this);
270-
}
271-
}
240+
// Note: Simple setters for retailCustomer and subscription are generated by Lombok @Data
241+
// Complex bidirectional relationship management is handled by DataCustodian/ThirdParty applications
272242

273243
/**
274244
* Generates the self href for this authorization.
@@ -341,13 +311,14 @@ public void merge(AuthorizationEntity other) {
341311

342312
/**
343313
* Clears all relationships when unlinking the entity.
314+
* Simplified - applications handle relationship cleanup.
344315
*/
345316
public void unlink() {
346317
clearRelatedLinks();
347318

348-
// Clear relationships
349-
setRetailCustomer(null);
350-
setSubscription(null);
319+
// Simple field clearing - applications handle bidirectional cleanup
320+
this.retailCustomer = null;
321+
this.subscription = null;
351322

352323
// Note: applicationInformation is not cleared as it might be referenced elsewhere
353324
}

src/main/java/org/greenbuttonalliance/espi/common/domain/usage/BatchListEntity.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
@Index(name = "idx_batch_list_resource_count", columnList = "resource_count")
4848
})
4949
@Data
50-
@NoArgsConstructor
5150
public class BatchListEntity {
5251

5352
private static final long serialVersionUID = 1L;
@@ -118,6 +117,14 @@ public BatchListEntity(String description) {
118117
this.updatedAt = this.createdAt;
119118
}
120119

120+
/**
121+
* Default constructor required by JPA.
122+
*/
123+
public BatchListEntity() {
124+
this.createdAt = System.currentTimeMillis();
125+
this.updatedAt = this.createdAt;
126+
}
127+
121128
/**
122129
* Constructor with initial resources.
123130
*

0 commit comments

Comments
 (0)