Convert monolith to microservices with Spring Cloud Gateway and Eureka#265
Convert monolith to microservices with Spring Cloud Gateway and Eureka#265devin-ai-integration[bot] wants to merge 3 commits intoworkshop-lionel-dotnet-to-javafrom
Conversation
- Decompose monolithic server into 4 domain microservices: Product Service (8081), Customer Service (8082), Order Service (8083), Inventory Service (8084) - Add Eureka Server (8761) for service discovery - Add Spring Cloud Gateway (8080) for API routing - Inter-service communication via load-balanced WebClient - Each service has its own H2 database - Spring Boot Actuator health endpoints on all services - CORS configuration on API Gateway - Angular proxy config for development (port 4200 -> Gateway 8080) - Updated README with microservices architecture docs - Parent POM for multi-module Maven build
🤖 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:
|
…ar UI - Add null check for product price before type branching in OrderService - Add customerName column to CustomerOrder, populated during order creation - Add productName column to InventoryItem, populated during data seeding - Update Angular order-list to use o.customerName instead of o.customer?.name - Update Angular inventory-list to use i.productName instead of i.product?.name - Update Angular product-list to fetch inventory data and merge stock by productId
| - id: product-service-swagger | ||
| uri: lb://product-service | ||
| predicates: | ||
| - Path=/product-service/v3/api-docs/** | ||
|
|
||
| - id: customer-service-swagger | ||
| uri: lb://customer-service | ||
| predicates: | ||
| - Path=/customer-service/v3/api-docs/** | ||
|
|
||
| - id: order-service-swagger | ||
| uri: lb://order-service | ||
| predicates: | ||
| - Path=/order-service/v3/api-docs/** | ||
|
|
||
| - id: inventory-service-swagger | ||
| uri: lb://inventory-service | ||
| predicates: | ||
| - Path=/inventory-service/v3/api-docs/** |
There was a problem hiding this comment.
🔴 Gateway swagger routes forward full path without stripping service-name prefix, causing 404s
The explicit swagger gateway routes (e.g., product-service-swagger with predicate Path=/product-service/v3/api-docs/**) forward the full request path including the service-name prefix to the downstream service. The product-service's API docs are at /v3/api-docs (product-service/src/main/resources/application.properties:23), not /product-service/v3/api-docs. Without a StripPrefix=1 filter, the downstream service receives the unstripped path and returns 404. Worse, since discovery.locator.enabled: true is configured (line 8), the discovery locator would auto-generate working routes that correctly strip the prefix — but the explicit swagger routes take precedence over discovery-locator routes, effectively breaking what would otherwise work automatically. All four swagger routes (lines 31-49) are affected.
| - id: product-service-swagger | |
| uri: lb://product-service | |
| predicates: | |
| - Path=/product-service/v3/api-docs/** | |
| - id: customer-service-swagger | |
| uri: lb://customer-service | |
| predicates: | |
| - Path=/customer-service/v3/api-docs/** | |
| - id: order-service-swagger | |
| uri: lb://order-service | |
| predicates: | |
| - Path=/order-service/v3/api-docs/** | |
| - id: inventory-service-swagger | |
| uri: lb://inventory-service | |
| predicates: | |
| - Path=/inventory-service/v3/api-docs/** | |
| - id: product-service-swagger | |
| uri: lb://product-service | |
| predicates: | |
| - Path=/product-service/v3/api-docs/** | |
| filters: | |
| - StripPrefix=1 | |
| - id: customer-service-swagger | |
| uri: lb://customer-service | |
| predicates: | |
| - Path=/customer-service/v3/api-docs/** | |
| filters: | |
| - StripPrefix=1 | |
| - id: order-service-swagger | |
| uri: lb://order-service | |
| predicates: | |
| - Path=/order-service/v3/api-docs/** | |
| filters: | |
| - StripPrefix=1 | |
| - id: inventory-service-swagger | |
| uri: lb://inventory-service | |
| predicates: | |
| - Path=/inventory-service/v3/api-docs/** | |
| filters: | |
| - StripPrefix=1 |
Was this helpful? React with 👍 or 👎 to provide feedback.
| forkJoin([ | ||
| this.http.get<any[]>('/api/products'), | ||
| this.http.get<any[]>('/api/inventory') | ||
| ]).subscribe(([products, inventory]) => { | ||
| const stockMap = new Map(inventory.map(i => [i.productId, i.quantityOnHand])); | ||
| this.products = products.map(p => ({ ...p, stock: stockMap.get(p.id) })); | ||
| }); |
There was a problem hiding this comment.
🔴 forkJoin in ProductListComponent causes complete page failure when inventory-service is down
The ProductListComponent uses forkJoin to fetch products AND inventory in parallel. forkJoin requires ALL observables to complete successfully — if the inventory-service is unreachable or returns an error, the entire forkJoin observable errors out, this.products stays empty, and the products page shows nothing. In a microservices architecture, services can independently go down, so a failure in the inventory-service should not prevent displaying the product catalog. The old monolith had inventory embedded in the product response so this wasn't an issue.
Prompt for agents
The ProductListComponent uses forkJoin to fetch products and inventory simultaneously. forkJoin fails entirely if any inner observable errors, which means if the inventory-service is down the products page shows nothing.
The fix should make the inventory fetch resilient so that products are still displayed even if inventory data is unavailable. Options include:
1. Use catchError on the inventory observable to return an empty array on failure, so forkJoin still completes: this.http.get('/api/inventory').pipe(catchError(() => of([])))
2. Fetch products first and then optionally enhance with inventory data using a separate subscription.
3. Use combineLatest with startWith or a similar pattern that doesn't block on both sources.
The key file is client-app/src/app/modules/products/product-list.component.ts, specifically the ngOnInit method around lines 25-33.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Decomposes the monolithic
server/Spring Boot application into 6 independent microservices with Spring Cloud infrastructure:/api/*to downstream servicesInter-service communication (Order → Customer/Product, Inventory → Product) uses load-balanced
WebClientvia Eureka. Angular frontend proxies to the gateway viaproxy.conf.jsonduring development (ng serveon port 4200).The old
server/directory and all its contents are removed. A multi-module Maven parent POM coordinates the build.Updates since last revision
angular.json: added requiredbuildTargetproperty to theservearchitect song serveworks correctly.OrderService.createOrderNPE: added null check onproduct.get("price")before type-branching, so a missing price field throws a clear error instead of a NullPointerException.customerNamecolumn toCustomerOrderentity, populated from the customer-service response during order creation. Template now readso.customerNameinstead ofo.customer?.name.productNamecolumn toInventoryItementity, populated in the DataSeeder. Template now readsi.productNameinstead ofi.product?.name.ProductListComponentnow usesforkJointo fetch both/api/productsand/api/inventoryin parallel, merging stock quantities byproductId. Template readsp.stockinstead ofp.inventory?.quantityOnHand.Review & Testing Checklist for Human
OrderService.createOrder()verified stock levels and decrementedquantityOnHandatomically. The new order-service only validates customer/product existence via REST calls — it does not call inventory-service to check or deduct stock. This is a functional regression.OrderServiceTest.java(empty orders, inventory deduction on order creation, insufficient stock check). Zero test classes exist in any of the new microservices. Determine if this is acceptable or if tests should be added before merge.catch (Exception e) { return null; }). A downstream service being unavailable surfaces as a generic "Customer not found" / "Product not found" error, not a meaningful service-unavailability message. No circuit breaker or retry logic is present.ProductListComponentusesforkJoinwhich requires both the products and inventory HTTP calls to succeed. If the inventory-service is unavailable, the products table won't render at all instead of gracefully showing "N/A" for stock.CustomerOrder.customerNameis stored at order-creation time (acceptable — captures point-in-time name).InventoryItem.productNameis hardcoded in the DataSeeder to match product-service seed data; if product names change in product-service, inventory will display stale names until reseeded.productIdvalues 1–5 assuming they match auto-generated IDs in the separate product-service H2 database. Since these are independent databases, IDs could diverge after restarts or data changes, causing inventory records to reference nonexistent products.ng servethe Angular app. Verify: (1) Eureka dashboard at :8761 shows all services registered, (2) Products page loads with stock numbers, (3) Customers page loads, (4) Inventory page shows product names, (5) Order creation via Swagger succeeds with correct total and customer name, (6) Orders page displays customer names on created orders.Notes
environment.tssetsapiUrl: 'http://localhost:8080'but no Angular component actually referencesenvironment.apiUrl— all HTTP calls use relative paths (/api/products). The proxy config is what actually routes requests during dev.*) with credentials enabled — review for security implications if this moves beyond local dev.jdbc:h2:file:./data/productdb), so data directories are created relative to wherever each service'smvn spring-boot:runis executed.Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/140ec763ab474ba295e64f13c216d195
Requested by: @lionel-john_infosys