Order service: extract Order bounded context from monolith (decoupled, JWT, EF/Npgsql)#58
Order service: extract Order bounded context from monolith (decoupled, JWT, EF/Npgsql)#58devin-ai-integration[bot] wants to merge 1 commit into
Conversation
…, JWT, EF/Npgsql)
🤖 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:
|
| await _db.SaveChangesAsync(); | ||
|
|
||
| var result = order.ToViewModel(); | ||
| return Created($"/{order.Id}", result); |
There was a problem hiding this comment.
🚩 Created response Location header will be wrong for gateway clients
The Create action returns Created($"/{order.Id}", result) at OrderController.cs:79, which sets the Location header to e.g. /{id}. Since the service sits behind a YARP gateway that strips /api/orders, clients accessing POST /api/orders will receive Location: /42 — resolving to http://gateway:5000/42 rather than http://gateway:5000/api/orders/42. This is a known trade-off of the prefix-stripping gateway pattern and cannot be easily fixed without the service knowing its external prefix. Other services (e.g. Notification at NotificationController.cs:101-104) similarly hardcode internal paths in responses. Consider whether this matters for your API consumers.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Intentional, and as the finding notes this is the known prefix-stripping gateway trade-off (same as Notification's hardcoded internal paths). The task pins absolute service-root routing with the gateway stripping /api/orders, and the service has no knowledge of its external prefix. Leaving as-is to stay consistent with sibling services and within task scope; happy to revisit if a configurable external-prefix convention is adopted across services.
| var orders = await _db.Orders | ||
| .AsNoTracking() | ||
| .Include(o => o.OrderDetails) | ||
| .ToListAsync(); |
There was a problem hiding this comment.
🚩 GetAll loads entire orders table without pagination
The GetAll endpoint (OrderController.cs:33-36) executes ToListAsync() over the full Orders table including all OrderDetails. Unlike the Notification service which accepts page and pageSize query parameters (NotificationController.cs:27), this endpoint has no pagination. With a large dataset this will cause high memory usage and slow responses. This is a design concern worth addressing before production use.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Valid design concern, but out of scope for this carve-out task, which pins the resource controller surface (GET / → list). No functional bug here. Pagination (matching Notification's page/pageSize) would be a sensible follow-up before production; leaving it out to keep this PR focused on the decoupling + auth + persistence gate.
Summary
Implements the standalone Order microservice (
src/Services/Order/) by carving the Order bounded context out of the QuickApp monolith. Scope is limited tosrc/Services/Order/**; the monolith, gatewayappsettings.json, anddocker-compose.ymlare untouched.Decoupling (id-only references to other contexts)
The Order context now references other services by ID only — all cross-context entities/navigations are dropped. Forbidden symbols (
Customer,Product,ApplicationUser,Cashier,CashierId) appear nowhere in the service (only the allowed int fieldsCustomerId/ProductIdremain):BaseEntity(Id + CreatedBy/UpdatedBy[MaxLength(40)]+ CreatedDate/UpdatedDate) is recreated insideOrder.Domain— no reference back to the monolith.Persistence (
OrderDbContext, Npgsql)Fluent config ported verbatim for the kept members:
No relationships to Customer/Product are configured (other services' data). Schema is created on boot via
EnsureCreated()wrapped in a transient-failure retry loop (12×5s) sincedepends_ondoes not wait for Postgres readiness. Connection string comes fromConnectionStrings:DefaultConnection.API surface
OrderControllerwith real EF persistence,[ProducesResponseType], and Swagger (Bearer security scheme wired so Swagger UI can authorize).OrderVMmirrors the monolith (Id,Discount,Comments) and additionally exposesCustomerIdand order-detail lines (ProductId,Quantity,UnitPrice,Discount). Manual entity↔DTO mapping (no AutoMapper dependency added).nameclaim on create/update.Auth — canonical JWT contract (HS256, from Identity PR #48)
Every endpoint is
[Authorize](unauth → 401). AddedMicrosoft.AspNetCore.Authentication.JwtBearer(10.*) and aJwtsection toappsettings.json(Key/Issuer/Audience).TokenValidationParametersvalidate issuer/audience/lifetime/signing-key with aSymmetricSecurityKey(UTF8 ofJwt:Key);MapInboundClaims=false, NameClaimTypename, RoleClaimTyperole.app.UseAuthentication()/UseAuthorization()added.Routing (pinned)
Gateway strips
/api/orders, so the resource controller uses absolute routes at the service root: externalGET /api/orders→GET /,GET /api/orders/{id}→GET /{id}. Health at/healthz.Scaffold bug fix
Order.API.csprojreferencedSharedvia..\..\Shared\...(wrong depth → would resolve toServices/Shared). TheShared.Contracts/Shared.Infrastructurereferences were unused, so they (and the matching DockerfileCOPYlines) are dropped, keeping the Dockerfile consistent.Validation gate (all passing)
cd src && dotnet build Services/Order/Order.API/Order.API.csproj -c Release→ succeeds.src/Services/Order→ zeroCustomer/Product/ApplicationUser/Cashier/CashierIdtype/nav refs (onlyCustomerId/ProductIdint fields).docker compose up postgres order-service api-gateway(only), through gatewayhttp://localhost:5000:GET /api/orders/healthz→ 200GET /api/orders(no token) → 401AppOrders&AppOrderDetailscreated inorderdb; columns confirm decoupling (CustomerId/ProductIdint FKs,numeric(18,2)money,Comments varchar(500); no Customer/Product/Cashier columns).POST /api/ordersreturns 201 and round-trips viaGET(200); auditCreatedBy= tokennameclaim.src/Services/Order/e2e-smoke.shadded — asserts health 200 + unauth 401 through the gateway, non-zero exit on failure.Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/779930dfbd594cafb3f264cf0b049d00
Requested by: @mbatchelor81