Skip to content

Add customer order API sample for db persist feature - #118

Open
dan-niles wants to merge 5 commits into
wso2:mainfrom
dan-niles:add-db-persist-sample
Open

Add customer order API sample for db persist feature#118
dan-niles wants to merge 5 commits into
wso2:mainfrom
dan-niles:add-db-persist-sample

Conversation

@dan-niles

Copy link
Copy Markdown
Contributor

Purpose

Adds an integration sample demonstrating the db persist feature.

@dan-niles
dan-niles requested a review from Copilot April 23, 2026 14:57
@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@dan-niles has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 51 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 16 minutes and 51 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6210acb3-6c26-4b54-9f9b-219017a40c85

📥 Commits

Reviewing files that changed from the base of the PR and between abd82a1 and 20fb8fc.

📒 Files selected for processing (4)
  • integrator-default-profile/samples/customer-order-api/Ballerina.toml
  • integrator-default-profile/samples/customer-order-api/README.md
  • integrator-default-profile/samples/customer-order-api/config.bal
  • integrator-default-profile/samples/customer-order-api/main.bal
📝 Walkthrough

Walkthrough

Adds a new customer-order API sample that uses Ballerina persist with a PostgreSQL datastore. Included are project manifests (Ballerina.toml, Dependencies.toml), configuration and connection files, persist model and generated client, SQL init and seed scripts, a Docker Compose for Postgres, an HTTP service exposing customer/product/order endpoints, and a README describing setup and example requests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Service as Ballerina Service
    participant DB as PostgreSQL Database

    Client->>Service: POST /api/v1/orders (NewOrder)
    Service->>Service: Validate payload (items present)
    loop for each item
        Service->>DB: SELECT product by id
        DB-->>Service: product (price, existence)
        Service->>Service: compute line total
    end
    Service->>DB: INSERT order (customerId, status, total, createdAt)
    DB-->>Service: orderId
    loop for each item
        Service->>DB: INSERT order_item (orderId, productId, quantity, unitPrice)
    end
    DB-->>Service: order items inserted
    Service-->>Client: 201 Created (order with items)
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. It addresses only the Purpose section minimally; most required sections (Goals, Approach, User stories, Release note, Documentation, etc.) are missing or unfilled. Complete the description by filling in Goals, Approach, Release note, Documentation, and other applicable sections from the template to provide comprehensive context for reviewers.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a customer order API sample that demonstrates the db persist feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (6)
integrator-default-profile/samples/customer-order-api/persist/db/model.bal (1)

16-18: Field naming: orderitems vs. OrderItem.

Reverse-navigation fields use lowercase orderitems (also on Product at line 62). Consider orderItems for consistency with Ballerina camelCase conventions and the rest of the record fields (customerId, unitPrice, createdAt).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integrator-default-profile/samples/customer-order-api/persist/db/model.bal`
around lines 16 - 18, Change the reverse-navigation field name from orderitems
to camelCase orderItems in the record where OrderItem[] orderitems is declared
(and likewise update the similar field on Product); update all references/usages
to the field (e.g., any code accessing .orderitems) to .orderItems and ensure
any related annotations or SQL relations still reference the same key symbols
(OrderItem, Product, customerId) so naming is consistent with Ballerina
camelCase conventions.
integrator-default-profile/samples/customer-order-api/docker-compose.yml (1)

6-11: Clarify that these are sample-only defaults.

The credentials and exposed port are fine for a local sample, but the README should explicitly call out that the defaults in this compose file are for local development only and must not be reused in any shared or non-local environment. Consider also supporting environment variable overrides (e.g., POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}) so users can override without editing the file.

Proposed change
     environment:
-      POSTGRES_USER: postgres
-      POSTGRES_PASSWORD: postgres
-      POSTGRES_DB: db_persist
+      POSTGRES_USER: ${POSTGRES_USER:-postgres}
+      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
+      POSTGRES_DB: ${POSTGRES_DB:-db_persist}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integrator-default-profile/samples/customer-order-api/docker-compose.yml`
around lines 6 - 11, The compose file exposes insecure sample defaults
(POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB and the "5432:5432" port mapping)
that should be documented as local-development only and made overrideable;
update the README to state these are sample-only defaults and must not be reused
in shared/non-local environments, and change the docker-compose environment
entries (POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB) to use environment
variable interpolation with sensible fallbacks (e.g.,
${POSTGRES_PASSWORD:-postgres}) so users can override without editing the file
and consider making the ports mapping optional or documented as for local use
only.
integrator-default-profile/samples/customer-order-api/db/init/01_schema.sql (1)

24-30: Consider indexing order_items.productId.

Joins/filters on productId (e.g., sales-by-product queries) will do sequential scans. An index similar to the other FK-column indexes would be consistent and helpful.

Proposed addition
 CREATE INDEX IF NOT EXISTS idx_orders_customer_id ON orders("customerId");
 CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items("orderId");
+CREATE INDEX IF NOT EXISTS idx_order_items_product_id ON order_items("productId");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integrator-default-profile/samples/customer-order-api/db/init/01_schema.sql`
around lines 24 - 30, Add a non-unique index on order_items.productId to speed
joins and filters; locate the order_items table definition and add an index
creation for the "productId" column (e.g., create an index named like
order_items_productid_idx) so queries filtering or joining on
order_items."productId" avoid sequential scans.
integrator-default-profile/samples/customer-order-api/connections.bal (1)

1-3: Positional arguments on dbpersist:Client new.

Passing five strings/ints positionally (dbHost, dbPort, dbUser, dbPassword, dbDatabase) is easy to get wrong if the generated client's parameter order ever changes. Consider using named arguments for readability and resilience to regeneration:

Proposed change
-final dbpersist:Client customerDb = check new (dbHost, dbPort, dbUser, dbPassword, dbDatabase);
+final dbpersist:Client customerDb = check new (
+    host = dbHost,
+    port = dbPort,
+    user = dbUser,
+    password = dbPassword,
+    database = dbDatabase
+);

Please verify the actual parameter names exposed by the generated dbpersist:Client (they depend on the persist tool version) and adjust accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integrator-default-profile/samples/customer-order-api/connections.bal` around
lines 1 - 3, The client is being constructed with positional args which is
fragile; update the instantiation of dbpersist:Client (the `new` call that
assigns `customerDb`) to use named arguments instead of positional values—lookup
the generated parameter names for dbHost, dbPort, dbUser, dbPassword, dbDatabase
in the generated `dbpersist:Client` API and pass them as name: value pairs in
the `new` expression so the call becomes resilient to parameter-order changes
and more readable.
integrator-default-profile/samples/customer-order-api/main.bal (2)

86-99: Order and its items are inserted non-atomically.

The order row is inserted first (lines 86–93) and the line items afterward (line 99). If the orderitems insert fails, the parent Order remains in the database with total set but no items, leaving partial data. The README already lists wrapping this flow in a persist transaction as an extension idea — given this is a reference sample, consider demonstrating that pattern directly so readers see the recommended shape from the start.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integrator-default-profile/samples/customer-order-api/main.bal` around lines
86 - 99, The current flow inserts the order via customerDb->/orders.post into
orderIds and then inserts items via customerDb->/orderitems.post separately,
which can leave a partial order if the second insert fails; update the code to
perform both inserts inside a single persist transaction (wrap the orders.post
and orderitems.post calls in a persist block or equivalent transactional API),
set itemInserts[i].orderId = orderId after obtaining orderId within that
transaction, and ensure the transaction rolls back on any error so the order and
items are committed atomically; target the customerDb->/orders.post,
customerDb->/orderitems.post calls and the orderIds/orderId/itemInserts
variables when making this change.

70-76: Inconsistent error mapping for product lookup failures.

When the product lookup returns a generic persist:Error (line 75), it is returned as-is and surfaces as a 500, while a NotFoundError is mapped to http:BadRequest. That's reasonable, but check would be more idiomatic here and equivalent:

Proposed simplification
-            dbpersist:Product|persist:Error product = customerDb->/products/[item.productId].get();
-            if product is persist:NotFoundError {
-                return <http:BadRequest>{body: string `product ${item.productId} not found`};
-            }
-            if product is persist:Error {
-                return product;
-            }
+            dbpersist:Product|persist:Error productResult = customerDb->/products/[item.productId].get();
+            if productResult is persist:NotFoundError {
+                return <http:BadRequest>{body: string `product ${item.productId} not found`};
+            }
+            dbpersist:Product product = check productResult;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integrator-default-profile/samples/customer-order-api/main.bal` around lines
70 - 76, The product lookup currently returns a persist:NotFoundError mapped to
http:BadRequest but returns other persist:Error values directly; instead, first
capture the lookup result from customerDb->/products/[item.productId].get() into
a var, handle persist:NotFoundError by returning http:BadRequest, and then use
check to propagate any other persist:Error while assigning the dbpersist:Product
(e.g., var res = customerDb->/products/[item.productId].get(); if res is
persist:NotFoundError return <http:BadRequest>{...}; dbpersist:Product product =
check res;). This keeps the NotFound mapping and uses check for idiomatic error
propagation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@integrator-default-profile/samples/customer-order-api/Ballerina.toml`:
- Around line 1-6: The package metadata uses a personal org value and a second
conflicting org entry; update the package [package] section so the org key is
set to "wso2" (replacing "danniles") and remove the duplicate/conflicting org
entry elsewhere in the file so there is a single, consistent org value; verify
the keys in the package block (org, name, version, distribution, title) remain
intact and valid after the change.

In `@integrator-default-profile/samples/customer-order-api/db/init/02_seed.sql`:
- Around line 19-24: The seed for order_items inserts productId=1 with
unitPrice=24.99 which mismatches the products seed price for product 1 (Wireless
Mouse) at 25.99; either add a comment explaining this is an intentional
historical/promotional price or change the unitPrice in the order_items row for
productId=1 to 25.99 and then update the corresponding order total in the orders
seed (order with id 3) to reflect the corrected sum (39.95 + 25.99). Ensure you
update the rows that reference order_items, productId=1, the products seed for
product 1, and the orders row for order id 3 (orders.total) consistently.

In `@integrator-default-profile/samples/customer-order-api/main.bal`:
- Around line 61-99: Validate each NewOrderItem.quantity before using it: inside
the foreach NewOrderItem item in payload.items loop in the resource function
post orders, check that item.quantity is > 0 and if not immediately return an
http:BadRequest with a clear message (e.g., include item.productId and offending
quantity). Do this validation before calling
customerDb->/products/[item.productId].get() and before adding to total or
pushing into itemInserts so negative or zero quantities never affect total or DB
inserts.

In `@integrator-default-profile/samples/customer-order-api/persist/db/model.bal`:
- Around line 5-19: The model fields that have DB defaults should be made
optional so inserts can omit them; update the persist record types: in Order
change status to string? , total to decimal? and createdAt to time:Utc? ; in
Product change stock to int? ; in Customer change createdAt to time:Utc? ;
locate these fields in the Order, Product, and Customer record type declarations
(e.g., the Order record in model.bal) and adjust the type annotations to
nullable/optional so the generated client will not force callers to supply
DB-defaulted values.

In `@integrator-default-profile/samples/customer-order-api/README.md`:
- Around line 36-39: The README config example uses a hardcoded org header
([danniles.customer_order_api]); change it to a generic placeholder like
[<org>.customer_order_api] throughout the sample so readers know to substitute
their own org name, and update any adjacent references in the same README or
Ballerina.toml examples to match the placeholder convention (look for
occurrences of "danniles.customer_order_api" and the TOML section shown).

---

Nitpick comments:
In `@integrator-default-profile/samples/customer-order-api/connections.bal`:
- Around line 1-3: The client is being constructed with positional args which is
fragile; update the instantiation of dbpersist:Client (the `new` call that
assigns `customerDb`) to use named arguments instead of positional values—lookup
the generated parameter names for dbHost, dbPort, dbUser, dbPassword, dbDatabase
in the generated `dbpersist:Client` API and pass them as name: value pairs in
the `new` expression so the call becomes resilient to parameter-order changes
and more readable.

In `@integrator-default-profile/samples/customer-order-api/db/init/01_schema.sql`:
- Around line 24-30: Add a non-unique index on order_items.productId to speed
joins and filters; locate the order_items table definition and add an index
creation for the "productId" column (e.g., create an index named like
order_items_productid_idx) so queries filtering or joining on
order_items."productId" avoid sequential scans.

In `@integrator-default-profile/samples/customer-order-api/docker-compose.yml`:
- Around line 6-11: The compose file exposes insecure sample defaults
(POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB and the "5432:5432" port mapping)
that should be documented as local-development only and made overrideable;
update the README to state these are sample-only defaults and must not be reused
in shared/non-local environments, and change the docker-compose environment
entries (POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB) to use environment
variable interpolation with sensible fallbacks (e.g.,
${POSTGRES_PASSWORD:-postgres}) so users can override without editing the file
and consider making the ports mapping optional or documented as for local use
only.

In `@integrator-default-profile/samples/customer-order-api/main.bal`:
- Around line 86-99: The current flow inserts the order via
customerDb->/orders.post into orderIds and then inserts items via
customerDb->/orderitems.post separately, which can leave a partial order if the
second insert fails; update the code to perform both inserts inside a single
persist transaction (wrap the orders.post and orderitems.post calls in a persist
block or equivalent transactional API), set itemInserts[i].orderId = orderId
after obtaining orderId within that transaction, and ensure the transaction
rolls back on any error so the order and items are committed atomically; target
the customerDb->/orders.post, customerDb->/orderitems.post calls and the
orderIds/orderId/itemInserts variables when making this change.
- Around line 70-76: The product lookup currently returns a
persist:NotFoundError mapped to http:BadRequest but returns other persist:Error
values directly; instead, first capture the lookup result from
customerDb->/products/[item.productId].get() into a var, handle
persist:NotFoundError by returning http:BadRequest, and then use check to
propagate any other persist:Error while assigning the dbpersist:Product (e.g.,
var res = customerDb->/products/[item.productId].get(); if res is
persist:NotFoundError return <http:BadRequest>{...}; dbpersist:Product product =
check res;). This keeps the NotFound mapping and uses check for idiomatic error
propagation.

In `@integrator-default-profile/samples/customer-order-api/persist/db/model.bal`:
- Around line 16-18: Change the reverse-navigation field name from orderitems to
camelCase orderItems in the record where OrderItem[] orderitems is declared (and
likewise update the similar field on Product); update all references/usages to
the field (e.g., any code accessing .orderitems) to .orderItems and ensure any
related annotations or SQL relations still reference the same key symbols
(OrderItem, Product, customerId) so naming is consistent with Ballerina
camelCase conventions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30420b8e-a8e5-41c2-8682-998dcdd8f95a

📥 Commits

Reviewing files that changed from the base of the PR and between 4d025cf and 9a01b77.

📒 Files selected for processing (10)
  • integrator-default-profile/samples/customer-order-api/Ballerina.toml
  • integrator-default-profile/samples/customer-order-api/Dependencies.toml
  • integrator-default-profile/samples/customer-order-api/README.md
  • integrator-default-profile/samples/customer-order-api/config.bal
  • integrator-default-profile/samples/customer-order-api/connections.bal
  • integrator-default-profile/samples/customer-order-api/db/init/01_schema.sql
  • integrator-default-profile/samples/customer-order-api/db/init/02_seed.sql
  • integrator-default-profile/samples/customer-order-api/docker-compose.yml
  • integrator-default-profile/samples/customer-order-api/main.bal
  • integrator-default-profile/samples/customer-order-api/persist/db/model.bal

Comment thread integrator-default-profile/samples/customer-order-api/db/init/02_seed.sql Outdated
Comment thread integrator-default-profile/samples/customer-order-api/main.bal Outdated
Comment thread integrator-default-profile/samples/customer-order-api/README.md

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new standalone Ballerina integration sample (customer-order-api) that demonstrates using a generated Persist SQL client (PostgreSQL) from an HTTP REST service, backed by a small e-commerce schema and runnable via Docker Compose.

Changes:

  • Introduces a Persist entity model (customers/products/orders/order_items) and Persist tool configuration for client generation.
  • Adds a Ballerina HTTP service exposing basic customer/product reads and customer CRUD plus order creation.
  • Adds local Postgres setup (docker-compose + schema + seed data) and end-user README instructions.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
integrator-default-profile/samples/customer-order-api/README.md Documents how to run the sample and call the REST API endpoints.
integrator-default-profile/samples/customer-order-api/persist/db/model.bal Defines Persist entities and relations for the e-commerce schema.
integrator-default-profile/samples/customer-order-api/main.bal Implements the REST API and uses the generated Persist client for CRUD and inserts.
integrator-default-profile/samples/customer-order-api/docker-compose.yml Provides local Postgres container configuration for the sample.
integrator-default-profile/samples/customer-order-api/Dependencies.toml Locks dependency/tool versions for reproducible builds.
integrator-default-profile/samples/customer-order-api/db/init/01_schema.sql Creates the Postgres schema used by the sample.
integrator-default-profile/samples/customer-order-api/db/init/02_seed.sql Seeds initial customers/products/orders/order_items data.
integrator-default-profile/samples/customer-order-api/connections.bal Instantiates the generated Persist client using configurable DB params.
integrator-default-profile/samples/customer-order-api/config.bal Declares configurable DB connection parameters for the sample.
integrator-default-profile/samples/customer-order-api/Ballerina.toml Declares the package and Persist tool configuration for client generation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread integrator-default-profile/samples/customer-order-api/README.md Outdated
Comment thread integrator-default-profile/samples/customer-order-api/README.md Outdated
Comment thread integrator-default-profile/samples/customer-order-api/main.bal Outdated
Comment thread integrator-default-profile/samples/customer-order-api/main.bal
Comment thread integrator-default-profile/samples/customer-order-api/main.bal Outdated
Comment thread integrator-default-profile/samples/customer-order-api/Ballerina.toml Outdated
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.

2 participants