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:

rows, _ := db.Query("SELECT id FROM reminders WHERE sent = 0")
ids := []int{}
for rows.Next() {
	var id int
	rows.Scan(&id)
	ids = append(ids, id)
}
rows.Close()
for _, id := range ids {
	fmt.Println("Reminder", id)
	db.Exec("UPDATE reminders SET sent = 1 WHERE id = ?", id)
}

It reads all the rows into a slice first and closes them, so the database is free for the updates. Then each UPDATE is saved the moment db.Exec runs, right after its row is done: 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 newApp, above return app, add POST /admin/run-jobs. It reads the waiting jobs, oldest first, into a slice of Job (the struct given at the top of the file) and closes the rows. Then, for each job, it sends the receipt with your helper sendReceipt and marks the job 'done'. Finally it answers {"ran": ...} with the number of jobs it took.


Assignment
Follow the Coding Tutorial to write the worker route POST /admin/run-jobs.


Hint
Take the jobs with WHERE status = 'waiting' ORDER BY id. Call rows.Close() before the second loop, not with defer: the updates write to the same table. The UPDATE goes inside the for loop over jobs; the writeJSON goes after it, with len(jobs).


STEP 1 OF 12
    Ctrl + Enter to check