Start learning free

LESSON 114 OF 173 · BACKEND: PAYMENTS AND OTHER SERVICES

A Worker

Practice

Since the last lesson, paying an order doesn't send the receipt. It adds a row to the jobs table, with the status 'waiting'. Somebody has to do those jobs: that is a worker. In a real shop it is a second program that takes the waiting jobs every few seconds. Yours is a route, POST /admin/run-jobs, so one request runs it.

A worker does three things: it takes the rows still to do, it does each one, and it marks each one done, so nothing is done twice. Here is the same shape on another table, reminders:

List<Map<String, Object>> rows = db.queryForList("SELECT * FROM reminders WHERE sent = 0");
for (Map<String, Object> row : rows) {
    System.out.println("Reminder for customer " + row.get("customer_id"));
    db.update("UPDATE reminders SET sent = 1 WHERE id = ?", row.get("id"));
}

The db.update sits inside the loop, after each row, and it is saved as soon as it runs: if the program stops halfway, the rows already done stay done. For your jobs, that means no customer gets the same email twice. The route is a POST, because it changes things.

The tests pay two orders, run the worker twice, then pay a third order and run it again. They read /check/sent (the emails the mail service received) and /check/jobs (the jobs table):

POST /orders/8/pay    →  201  {"amount": 80, "order_id": 8, "payment_id": "pay_1"}
POST /orders/10/pay   →  201  {"amount": 21, "order_id": 10, "payment_id": "pay_2"}
GET /check/sent       →  200  []
POST /admin/run-jobs  →  200  {"ran": 2}
GET /check/sent       →  200  [{"subject": "Your order 8 is paid", "to": "eva@example.com", …},
                               {"subject": "Your order 10 is paid", "to": "chloe@example.com", …}]
GET /check/jobs       →  200  [{"id": 1, "order_id": 8, "status": "done"},
                               {"id": 2, "order_id": 10, "status": "done"}]
POST /admin/run-jobs  →  200  {"ran": 0}
POST /orders/11/pay   →  201  {"amount": 18, "order_id": 11, "payment_id": "pay_3"}
POST /admin/run-jobs  →  200  {"ran": 1}
GET /check/sent       →  200  [… the same two, then {"subject": "Your order 11 is paid", "to": "ana@example.com", …}]

Your task: at the end of ShopController, add @PostMapping("/admin/run-jobs") with a method runJobs. It takes the waiting jobs, oldest first, sends each job's receipt with your helper sendReceipt, marks the job 'done', then returns Map.of("ran", ...) with the number of jobs it took.


Assignment
Follow the Coding Tutorial to write the worker method runJobs.


Hint
Take the jobs with WHERE status = 'waiting' ORDER BY id. job.get("order_id") is an Object: put (int) in front of it for sendReceipt. The UPDATE goes inside the for loop; the return goes after it, with jobs.size().


STEP 1 OF 7
    Ctrl + Enter to check