Skip to content

Order service: extract Order bounded context from monolith (decoupled, JWT, EF/Npgsql)#58

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1782448519-order-service
Open

Order service: extract Order bounded context from monolith (decoupled, JWT, EF/Npgsql)#58
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1782448519-order-service

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the standalone Order microservice (src/Services/Order/) by carving the Order bounded context out of the QuickApp monolith. Scope is limited to src/Services/Order/**; the monolith, gateway appsettings.json, and docker-compose.yml are 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 fields CustomerId/ProductId remain):

Order        : Discount, Comments, CustomerId (int), ICollection<OrderDetail> OrderDetails
               DROPPED: Customer nav, Cashier, CashierId
OrderDetail  : UnitPrice, Quantity, Discount, ProductId (int), OrderId (int), Order nav
               DROPPED: Product nav

BaseEntity (Id + CreatedBy/UpdatedBy [MaxLength(40)] + CreatedDate/UpdatedDate) is recreated inside Order.Domain — no reference back to the monolith.

Persistence (OrderDbContext, Npgsql)

Fluent config ported verbatim for the kept members:

Order.Comments        HasMaxLength(500)
Order.Discount        HasPrecision(18,2)        -> ToTable("AppOrders")
OrderDetail.UnitPrice HasPrecision(18,2)
OrderDetail.Discount  HasPrecision(18,2)        -> ToTable("AppOrderDetails")
OrderDetail -> Order  HasOne/WithMany(OrderDetails), FK OrderId, cascade delete

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) since depends_on does not wait for Postgres readiness. Connection string comes from ConnectionStrings:DefaultConnection.

API surface

  • Full CRUD OrderController with real EF persistence, [ProducesResponseType], and Swagger (Bearer security scheme wired so Swagger UI can authorize).
  • DTO OrderVM mirrors the monolith (Id, Discount, Comments) and additionally exposes CustomerId and order-detail lines (ProductId, Quantity, UnitPrice, Discount). Manual entity↔DTO mapping (no AutoMapper dependency added).
  • Audit fields stamped from the name claim on create/update.

Auth — canonical JWT contract (HS256, from Identity PR #48)

Every endpoint is [Authorize] (unauth → 401). Added Microsoft.AspNetCore.Authentication.JwtBearer (10.*) and a Jwt section to appsettings.json (Key/Issuer/Audience). TokenValidationParameters validate issuer/audience/lifetime/signing-key with a SymmetricSecurityKey (UTF8 of Jwt:Key); MapInboundClaims=false, NameClaimType name, RoleClaimType role. app.UseAuthentication()/UseAuthorization() added.

Routing (pinned)

Gateway strips /api/orders, so the resource controller uses absolute routes at the service root: external GET /api/ordersGET /, GET /api/orders/{id}GET /{id}. Health at /healthz.

Scaffold bug fix

Order.API.csproj referenced Shared via ..\..\Shared\... (wrong depth → would resolve to Services/Shared). The Shared.Contracts/Shared.Infrastructure references were unused, so they (and the matching Dockerfile COPY lines) are dropped, keeping the Dockerfile consistent.

Validation gate (all passing)

  • cd src && dotnet build Services/Order/Order.API/Order.API.csproj -c Releasesucceeds.
  • Forbidden-symbol grep over src/Services/Orderzero Customer/Product/ApplicationUser/Cashier/CashierId type/nav refs (only CustomerId/ProductId int fields).
  • docker compose up postgres order-service api-gateway (only), through gateway http://localhost:5000:
    • GET /api/orders/healthz200
    • GET /api/orders (no token) → 401
  • Tables AppOrders & AppOrderDetails created in orderdb; columns confirm decoupling (CustomerId/ProductId int FKs, numeric(18,2) money, Comments varchar(500); no Customer/Product/Cashier columns).
  • Extra happy-path check: a token minted per the canonical contract → POST /api/orders returns 201 and round-trips via GET (200); audit CreatedBy = token name claim.
  • src/Services/Order/e2e-smoke.sh added — 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


Open in Devin Review

@mbatchelor81 mbatchelor81 self-assigned this Jun 26, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

await _db.SaveChangesAsync();

var result = order.ToViewModel();
return Created($"/{order.Id}", result);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🚩 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment on lines +33 to +36
var orders = await _db.Orders
.AsNoTracking()
.Include(o => o.OrderDetails)
.ToListAsync();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🚩 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

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.

1 participant