Repository navigation
feat(order): orders computed from catalog prices - #6
Merged
Merged
Conversation
Order is the table-shaped class for the orders table (the class name is Order, the table is orders because ORDER is an SQL keyword) and OrderDto is the business shape the service exposes. The quantity is an int and the total a BigDecimal, never a double, because it is money. The two shapes stay separate classes.
The RowMapper reads one row into an Order with getInt for the quantity and getBigDecimal for the total, so the exact decimal survives the round trip. The entity mapper converts between Order and OrderDto. Neither queries the database or contains a business rule.
OrderDaoImpl reads a page filtered by an optional customer and an optional product, counts with the same filters, finds by id and inserts with INSERT ... RETURNING id. The WHERE clause is built from the filters that are present: only fixed column names are concatenated and every value is a bound parameter, added in placeholder order. There is no update or delete, because orders are immutable in this API.
…stomer services OrderServiceImpl reaches customers and products only through their service interfaces, never their DAOs. The injected beans are transactional proxies, so their calls join the transaction of create, and their ResourceNotFoundException propagates as a 404. The total is the product price times the quantity, computed exactly and compared with the column limit before any scaling. A quantity outside 1 to 1000000 is refused, and a customer or product deleted between the checks and the insert is reported as not found instead of a 500. The service returns dto types and pages through PageDto.
DaoConfig declares the DAO, and ServiceConfig imports it together with the catalog and customer service configurations, by fully qualified name since each module has a class with that simple name. The order module is therefore self-sufficient: the ProductService and CustomerService it injects come from those imported configs. The order service bean is typed by interface so its @transactional proxy applies.
OrderRequest requires the customer, the product and a quantity between 1 and 1000000, so a missing or absurd value is a 400 before it reaches the service. OrderResponse exposes the computed total, and OrderConverter maps to and from the core dto and builds the PageResponse. Payload classes stay distinct from core dto classes.
OrderController offers the paginated list filterable by customerId and productId, the detail and the creation, guarded by @PreAuthorize("hasPermission('ORDER', 'READ'|'WRITE')"). It only calls OrderService and answers with an ApiResponse, and a creation returns 201. There is no update or delete route, so those methods answer 405.
The order ServiceConfig is added to the WebMvcConfig import list by fully qualified name. It brings the DAO, and through its own imports the catalog and customer services it depends on. With this import every core module is now wired into the single application context.
The order DAO tests run against a real PostgreSQL, so the module declares the Testcontainers JUnit and PostgreSQL artifacts and the common test-jar holding the shared singleton container, all with test scope, like the other business modules.
The DAO test loads its own dedicated fixture with customers, products and orders. It checks page ordering, offset and an out-of-range page, each filter alone and both together, counts with the same filters, lookups, the exact NUMERIC total round trip including the column maximum, and the constraints owned by the schema: foreign keys to the customer and the product and the CHECK on the quantity.
The RowMapper is fed a mocked ResultSet with an int quantity and a BigDecimal total, and the entity mapper is checked with hand-built objects and no mocks.
…duct services The order module only sees service interfaces, so the tests mock CustomerService and ProductService. They cover exact totals (3 x 19.99 = 59.97, a zero price, half-up rounding), a total above the column limit refused before any insert, the quantity boundaries, null ids, and the order of the checks: an unknown customer or product propagates its 404 and nothing is inserted. A foreign key violation at insert becomes a not-found error, and the page bounds and filters are checked too.
The real security configuration and a real signed JWT are used with a mocked service. The tests check status and shape of the three endpoints, validation errors per field, the customer and product filters passed to the service, page and size violations, an out-of-range page keeping content as an empty array, the 404 and 422 mappings, and 405 for PUT, DELETE and PATCH. A client-supplied total or id is shown to be ignored.
A parameterized test asserts, for each endpoint, that no token or a revoked one gives 401, that a token holding every other permission or none gives 403, and that a token holding only the required ORDER permission succeeds.
Customers, categories and products are created through their real endpoints, then the tests prove that the total is computed from the product price at creation and stays after the price changes, the filters and pagination, unknown customers and products giving 404 with nothing stored, and invalid quantities giving 400. Deleting a customer or a product referenced by an order gives 422 with the exact message, then succeeds once the order is removed. A decimal quantity such as 1.5 is refused with 400 and stores nothing, which pins the Jackson fix.
By default Jackson accepts a floating point JSON number for an int field and silently truncates it, so a body with a quantity of 1.5 created an order for one unit instead of being refused. The shared ObjectMapper now disables DeserializationFeature.ACCEPT_FLOAT_AS_INT, so such a body is a malformed request and answers 400 through the existing handler. An integration test on the order endpoint pins the behaviour.
…ndencies The three order tasks are checked: the entity, dto, RowMapper, mapper, DAO, service and configuration of core/order, the controller and converter in ws with the WebMvcConfig import, and the tests at all five layers with a dedicated fixture for the DAO tests. The project tree said the order service depends on CategoryService as well, but computing a total and checking the references only needs ProductService and CustomerService, so the README now says so.
The exact product is already compared with the column maximum, which has two decimals, so rounding a value at or below it to two decimals can never exceed it. The second comparison after setScale and its duplicated message could never fire. The rounding itself stays, and the unit tests that pin the totals still pass.
8 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Branch
feature/order
Task checklist
core/order:Orderentity,OrderDto,OrderRowMapper,OrderMapper,OrderDao+ impl,OrderService+ impl (depends on the catalogProductServiceand the customerCustomerServiceinterfaces only): computestotal = quantity * product.unitPrice, checks the referenced customer/product existcore/order'sDaoConfig/ServiceConfig(importing the catalog and customer service configs)ws:OrderController,OrderConverterWebMvcConfigimportscore/order's configOrderDaowith its own fixture dataset, the total computation, the not-found cases for a badcustomerId/productIdCommit summary
Test plan
mvn clean verifyis green on the whole reactor