Phase 1: Extract and Strengthen API Contracts for FTGO Monolith Decoupling#41
Phase 1: Extract and Strengthen API Contracts for FTGO Monolith Decoupling#41devin-ai-integration[bot] wants to merge 2 commits intomasterfrom
Conversation
…pling - Create missing request/response DTOs in API modules: - Order API: GetOrderResponse, GetRestaurantResponse (moved from impl) - Consumer API: GetConsumerResponse, ValidateOrderForConsumerRequest - Restaurant API: CreateRestaurantResponse, GetRestaurantResponse (moved from impl) - Courier API: GetCourierResponse - Add OpenAPI/Swagger annotations (@Api, @apioperation, @apiresponse, @apimodel, @ApiModelProperty) to all controllers and DTOs - Implement API versioning with /api/v1 URL path prefix across all services - Document cross-service dependencies in CROSS-SERVICE-DEPENDENCIES.md - Remove old impl-level DTOs, ensuring API modules contain only DTOs Co-Authored-By: Jack Meigel <jack.meigel@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| GetCourierResponse response = new GetCourierResponse( | ||
| courier.getId(), | ||
| null, | ||
| null, | ||
| courier.isAvailable() | ||
| ); |
There was a problem hiding this comment.
🔴 NullPointerException when getting courier with unset available field due to Boolean-to-boolean unboxing
The CourierController.get() method calls courier.isAvailable() which auto-unboxes the Boolean available field to a primitive boolean. However, the Courier entity's available field is declared as Boolean (wrapper type) at ftgo-domain/src/main/java/net/chrisrichardson/ftgo/domain/Courier.java:28 and is never initialized in the constructor (Courier(PersonName, Address) at line 33-36). For any newly created courier that hasn't had noteAvailable() or noteUnavailable() called, available will be null, and the auto-unboxing in isAvailable() (ftgo-domain/src/main/java/net/chrisrichardson/ftgo/domain/Courier.java:51-52) will throw a NullPointerException.
Root Cause
The previous code at this endpoint returned the Courier entity directly as JSON (return new ResponseEntity<>(courier, HttpStatus.OK)), which serialized available as JSON null without issue. The new code explicitly calls courier.isAvailable() to pass the value to GetCourierResponse, triggering Java's auto-unboxing of Boolean → boolean.
The Courier constructor at ftgo-domain/src/main/java/net/chrisrichardson/ftgo/domain/Courier.java:33:
public Courier(PersonName name, Address address) {
this.name = name;
this.address = address;
// available is never set, remains null
}isAvailable() at line 51:
public boolean isAvailable() {
return available; // NullPointerException when available is null
}Impact: Any GET request for a courier that was created but never had its availability explicitly set will result in a 500 Internal Server Error.
| GetCourierResponse response = new GetCourierResponse( | |
| courier.getId(), | |
| null, | |
| null, | |
| courier.isAvailable() | |
| ); | |
| GetCourierResponse response = new GetCourierResponse( | |
| courier.getId(), | |
| null, | |
| null, | |
| courier.isAvailable() != null && courier.isAvailable() | |
| ); |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch! Fixed in 988b251. The Courier entity's available field is Boolean (wrapper) and never initialized in the constructor, so isAvailable() would NPE on auto-unboxing.
Fix:
- Added
getName(),getAddress(), andgetAvailable()getters to theCourierentity - Used
Boolean.TRUE.equals(courier.getAvailable())for null-safe availability check (defaults tofalsewhennull) - Now also populates
nameandaddressin theGetCourierResponseinstead of passingnull
- Add getName(), getAddress(), getAvailable() getters to Courier entity - Use Boolean.TRUE.equals() for null-safe availability check - Populate name and address fields in GetCourierResponse Co-Authored-By: Jack Meigel <jack.meigel@cognition.ai>
Phase 1: Extract and Strengthen API Contracts for FTGO Monolith Decoupling
Summary
Strengthens API contracts across all four FTGO services in preparation for future microservice extraction. The changes fall into four categories:
DTO extraction to API modules — Moved response DTOs (
GetOrderResponse,GetConsumerResponse,GetRestaurantResponse,CreateRestaurantResponse) out of implementation modules into their corresponding-apimodules so downstream consumers can depend on the API module alone.Swagger/OpenAPI annotations — Added
@Api,@ApiOperation,@ApiResponses,@ApiParamto all four controllers and@ApiModel/@ApiModelPropertyto all DTOs. Addedspringfox-swagger2:2.8.0dependency to all four-apimodulebuild.gradlefiles.URL path versioning — All controller base paths changed from
/{resource}to/api/v1/{resource}(e.g./orders→/api/v1/orders). This is a breaking change for any existing clients.Cross-service dependency documentation — New
CROSS-SERVICE-DEPENDENCIES.mdmapping direct repository/service calls that must become HTTP calls in Phase 2.New DTOs created:
GetOrderResponse(with nestedCourierActionDTO),GetConsumerResponse,ValidateOrderForConsumerRequest,GetCourierResponse,GetRestaurantResponse(in both order-api and restaurant-api),CreateRestaurantResponse(in restaurant-api).Updates since last revision
CourierController.get(): TheCourierentity'savailablefield isBoolean(wrapper type) and is never initialized in the constructor. The previous code calledcourier.isAvailable()which auto-unboxes to primitiveboolean, throwing NPE whenavailableisnull. Fixed by usingBoolean.TRUE.equals(courier.getAvailable()).Courierentity: AddedgetName(),getAddress(), andgetAvailable()methods to support safe field access in the controller.GetCourierResponsenow includes the courier's name and address instead of passingnull.Local Verification
The application was started locally via Docker Compose and verified:
/api/v1/endpoint groups route correctly (orders, consumers, restaurants, couriers)/swagger-ui.htmlwith all four service tags and annotated endpoints visible/v2/api-docsreturn correct OpenAPI JSON with all@ApiModel/@ApiModelPropertymetadata./gradlew testfor modified modules)Swagger UI — all services documented with
/api/v1/paths:Review & Testing Checklist for Human
/api/v1/prefix. OnlyOrderControllerTestwas updated to use the new paths; other controller tests (if any exist beyond this repo) may still reference old paths. Check for any integration tests, E2E tests, or external clients that depend on the old paths.GetOrderResponse.orderTotalchanged fromMoneyobject toString(via.asString()).GetRestaurantResponse.idchanged fromLong(boxed) tolong(primitive). Confirm these don't cause NPEs or serialization issues in production.GetRestaurantResponseclasses — There's now aGetRestaurantResponsein bothftgo-order-service-api(net.chrisrichardson.ftgo.orderservice.api.web) andftgo-restaurant-service-api(net.chrisrichardson.ftgo.restaurantservice.events). Verify this is intentional and doesn't cause confusion or import conflicts.getName(),getAddress(),getAvailable()getters to theCourierJPA entity. While the entity uses@Access(AccessType.FIELD)for Hibernate, Jackson may now serialize these fields if the entity is ever returned directly elsewhere in the codebase. Verify no other code paths returnCourierentities directly.security/snykfailed with 4 tests. These appear to be pre-existing dependency vulnerabilities (not introduced by this PR), but should be reviewed at Snyk dashboard.Test Plan
/api/v1/paths:POST /api/v1/consumers→ create consumerGET /api/v1/consumers/{consumerId}→ retrieve consumerPOST /api/v1/restaurants→ create restaurantGET /api/v1/restaurants/{restaurantId}→ retrieve restaurantPOST /api/v1/couriers→ create courierGET /api/v1/couriers/{courierId}→ retrieve courier (verify name, address, and availability are populated)POST /api/v1/orders→ create orderGET /api/v1/orders/{orderId}→ retrieve order/swagger-ui.htmlshows all endpoints with proper documentation./gradlew test -x :ftgo-end-to-end-tests-common:test -x :ftgo-end-to-end-tests:testNotes
CROSS-SERVICE-DEPENDENCIES.mddoc references some DTOs (CourierAvailability,RestaurantMenuDTO,MenuItemDTO) that don't exist yet — these are placeholders for Phase 2.ftgo-end-to-end-tests-common(missingeventuate-util-test:0.1.0.RELEASE).springfox-swagger2to API modules increases their dependency footprint, but Spring dependencies are excluded to keep them lightweight.Link to Devin run: https://app.devin.ai/sessions/2d5c7c1499c547859343dbe7a7bbf8bc
Requested by: @cogjack