Skip to content

Refactor/api consistency and rename#19

Open
jimmy-guzman wants to merge 10 commits into
mainfrom
refactor/api-consistency-and-rename
Open

Refactor/api consistency and rename#19
jimmy-guzman wants to merge 10 commits into
mainfrom
refactor/api-consistency-and-rename

Conversation

@jimmy-guzman

@jimmy-guzman jimmy-guzman commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Standardize API contracts & rename

Summary

Aligns the two services around consistent naming, REST-idiomatic paths, and uniform error handling. The catalog service is renamed from retail-data-services to product-api. Field names, identifiers, and types now match across the service boundary, so cart-service and product-api agree on the wire format.

The work splits into nine commits. Commits 1 through 8 land while the directory is still retail-data-services to keep each change reviewable. Commit 9 is the atomic rename that touches all structural identifiers at once.

Why

retail-data-services was published while this repo was private, so its image is still private. The repo is public now and both images need to be public. That means republishing either way, so the rename and contract fixes go in the same change rather than locking in the old name on a fresh public artifact.

The two services developed independently and never aligned. tcin, product_id, and item_id all referred to the same product identifier with no boundary rule. Image and price fields used different names on each side of the call. merch_class was an Integer upstream and a String in cart. Error handling threw bare RuntimeException and leaned on Optional.empty() to produce 404s. The service name said nothing about its domain.

Changes

Rename retail-data-services to product-api. Directory, Gradle subproject, Java package root (com.target.retail.data.services to com.target.retail.product), JAR artifact, Docker service name (data to product-api), OpenAPI title and description, and the spec filename all move.

REST-idiomatic paths. Service names leave the URL path and live in the hostname. Version moves into @RequestMapping("/v1") on the controllers so it shows up in code and Swagger. /retail_data_services/v1/items/{id} becomes /v1/items/{id}. /cart/v1/carts becomes /v1/carts. Product-api config drops the context-path.

Field name alignment. The frontend case studies call cart, so cart's names are the ones that surface in those exercises. Upstream adopts cart's shorter names rather than the other way around. Image fields become primary and alternate. Price fields become regular and sale.

Identifier standardization. tcin and product_id become item_id everywhere: models, DTOs, path variables, CSV headers, service methods, and client URIs.

merch_class type fix. Cart-service stops casting the upstream Integer to String and carries it as Integer end to end.

next_page sentinel. PaginatedResponse.calculateNextPage returned 0 when no further pages existed, which collides with a valid page number. It now returns null.

Typed exceptions. Both services get domain exceptions (ItemNotFoundException, PriceNotFoundException, AvailabilityNotFoundException upstream; CartNotFoundException, CartLineItemNotFoundException in cart; InducedFailureException in both) and a GlobalExceptionHandler that maps them to consistent status codes with a shared ErrorResponse shape. This drops the pre-check guard pattern in CartController and the CustomErrorController in product-api.

Smaller fixes. AddItemRequest gets @JsonNaming(SnakeCaseStrategy) to match UpdateItemRequest. Product-api Behaviors switches from Map<String, InducedBehavior> with .name() lookups to a type-safe Map<BehaviorType, InducedBehavior>.

Error response shape

{ "status": 404, "message": "No cart found with id 123" }
Exception Status
Not-found exceptions (both services) 404
InducedFailureException 503
DataException 500
RuntimeException catch-all 500

services

Signed-off-by: jimmy-guzman <hi@jimmy.codes>
Signed-off-by: jimmy-guzman <hi@jimmy.codes>
GlobalExceptionHandler in both services

Signed-off-by: jimmy-guzman <hi@jimmy.codes>
Signed-off-by: jimmy-guzman <hi@jimmy.codes>
product-api Behaviors

Signed-off-by: jimmy-guzman <hi@jimmy.codes>
REST-idiomatic paths

Signed-off-by: jimmy-guzman <hi@jimmy.codes>
@jimmy-guzman jimmy-guzman marked this pull request as ready for review June 10, 2026 13:30
@jimmy-guzman

jimmy-guzman commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author
  • Rename other docs like README.md

Signed-off-by: jimmy-guzman <hi@jimmy.codes>
@jimmy-guzman jimmy-guzman force-pushed the refactor/api-consistency-and-rename branch from e514f8d to 26dfa53 Compare June 10, 2026 13:48
@@ -6,8 +6,6 @@ spring:

server:
port: 8080
servlet:
context-path: /retail_data_services/v1/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the intent — service identity belongs in the hostname, not the path. That's a sound principle in a proper service mesh. However, in this repo's context (local dev, docker-compose, case study exercises), multiple services will likely share a single host. Both product-api and cart-service are now rooted at /v1, which creates ambiguity and potential routing conflicts without a differentiating prefix.

I'd suggest restoring a products prefix — either via context-path: /products in application.yml or a top-level @RequestMapping("/products/v1") on the controllers. This keeps the version visible in code while making the routes unambiguous and more self-documenting for learners reading a URL in a tutorial.
Use the same pattern for both /carts and /products - context-path or @RequestMapping.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I debated this as well, i'll go with

  • /products/v1 and /cart/v1 as @RequestMapping rather than context-path so Swagger/api-docs/actuator stays at root

@@ -26,8 +24,6 @@ springdoc:
path: api-docs
swagger-ui:
enabled: true
config-url: /retail_data_services/v1/api-docs/swagger-config
url: /retail_data_services/v1/api-docs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to confirm swagger ui still works w/o these config values.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They do but will need brought back when #19 (comment) is addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants