Skip to content

feat(order): implement Order microservice with CRUD + JWT auth#61

Open
devin-ai-integration[bot] wants to merge 5 commits into
mainfrom
devin/order-service
Open

feat(order): implement Order microservice with CRUD + JWT auth#61
devin-ai-integration[bot] wants to merge 5 commits into
mainfrom
devin/order-service

Conversation

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor

Summary

Extracts the Order bounded context from the monolith into a fully self-contained microservice with:

Domain (Order.Domain/Entities/):

  • BaseEntity (Id, audit fields), Order (Discount, Comments, CashierId, CustomerId + OrderDetails collection), OrderDetail (UnitPrice, Quantity, Discount, ProductId, OrderId + Order nav)
  • Zero cross-context navigation properties — only plain FK IDs (CustomerId int, CashierId string?, ProductId int)

Persistence (Order.Infrastructure/Data/OrderDbContext.cs):

  • ORM config ported verbatim from monolith: tables AppOrders/AppOrderDetails, decimal(18,2) columns, HasMaxLength(500) on Comments, HasMaxLength(40) on audit fields, cascade delete on Order→OrderDetails
  • EnsureCreated() with retry loop for container startup ordering

API (Order.API/):

  • Full CRUD controller at api/order: GET all, GET by id, POST (201), PUT, DELETE (cascade)
  • JWT Bearer auth on all endpoints (class-level [Authorize]), healthz remains public
  • DTOs: OrderDto, CreateOrderDto, UpdateOrderDto, OrderDetailDto, CreateOrderDetailDto

Gateway: Pre-existing route /api/orders/{**catch-all} → strips prefix → http://order-service:5003/ — verified working end-to-end.

E2E smoke test (tests/order-smoke-test.sh): 7 assertions covering healthz→200, no-token→401, POST→201, GET→200 both direct and through gateway. All pass.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/b96f48501811424ab731f21d9e98ab45
Requested by: @mbatchelor81


Open in Devin Review

- Add BaseEntity with Id, audit fields (CreatedBy, UpdatedBy, CreatedDate, UpdatedDate)
- Add Order entity with Discount, Comments, CashierId (string FK), CustomerId (int FK)
- Add OrderDetail entity with UnitPrice, Quantity, Discount, ProductId (int FK), OrderId (int FK)
- Remove .gitkeep from Entities/ and Interfaces/ directories
- Add Microsoft.AspNetCore.Authentication.JwtBearer package
- Configure JWT validation (issuer, audience, signing key) in Program.cs
- Add authentication/authorization middleware pipeline
- Create OrderDto, CreateOrderDto, UpdateOrderDto, OrderDetailDto, CreateOrderDetailDto
- Replace scaffold controller with full CRUD (GetAll, GetById, Create, Update, Delete)
- All controller endpoints protected with class-level [Authorize]
- healthz endpoint remains public via MapHealthChecks
@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 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +56 to +64
var order = new Order.Domain.Entities.Order
{
Discount = dto.Discount,
Comments = dto.Comments,
CashierId = dto.CashierId,
CustomerId = dto.CustomerId,
CreatedDate = DateTime.UtcNow,
UpdatedDate = DateTime.UtcNow
};

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.

🚩 Audit fields (CreatedBy/UpdatedBy) are never populated despite being defined on all entities

The BaseEntity class (Order.Domain/Entities/BaseEntity.cs:6-7) defines CreatedBy and UpdatedBy audit properties, and the DbContext configures max lengths for them (OrderDbContext.cs:25-26, OrderDbContext.cs:40-41). However, the Create action (OrderController.cs:56-64) and Update action (OrderController.cs:98-103) never set these fields, even though the [Authorize] attribute guarantees an authenticated user identity is available via HttpContext.User. These columns will always be NULL in the database, making the audit trail incomplete.

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.

Acknowledged. The audit fields (CreatedBy/UpdatedBy) are intentionally left unpopulated in this initial extraction — the monolith's audit logic lives in its shared ApplicationDbContext.SaveChangesAsync override, which is out of scope for this carve-out. Populating them from HttpContext.User claims would be a follow-up enhancement once the identity claim structure is finalized across services.

Comment on lines +86 to +107
[ProducesResponseType(typeof(OrderDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateOrderDto dto)
{
// TODO: Implement — migrate logic from monolith
return Ok(new { service = "Order", id });
var order = await _context.Orders
.Include(o => o.OrderDetails)
.FirstOrDefaultAsync(o => o.Id == id);

if (order is null)
return NotFound();

order.Discount = dto.Discount;
order.Comments = dto.Comments;
order.CashierId = dto.CashierId;
order.CustomerId = dto.CustomerId;
order.UpdatedDate = DateTime.UtcNow;

await _context.SaveChangesAsync();

return Ok(MapToDto(order));
}

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.

🚩 PUT endpoint only updates header fields, not order details — partial update semantics

The Update action uses [HttpPut] (OrderController.cs:86) but only modifies order-level fields (Discount, Comments, CashierId, CustomerId). The UpdateOrderDto (OrderDto.cs:22-28) deliberately omits OrderDetails, so existing line items are preserved unchanged. This is a partial update, which is conventionally a PATCH operation in REST APIs. If callers expect standard PUT semantics (full resource replacement), order details would never be removable or modifiable through this endpoint. The .Include(o => o.OrderDetails) at line 92 is correctly present to support the MapToDto response serialization.

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.

By design — UpdateOrderDto was deliberately scoped to header-level fields per the spec. Line item management (add/remove/update details on an existing order) can be added as a separate endpoint or expanded in a follow-up. The .Include on line 92 is for response serialization only.

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