4 of 12
Expanded my thoughts, clarified direct answer
Ben Barkay
  • 568
  • 4
  • 5

The short, direct answer

Since the request is to execute the list of tasks, then if the task group has been executed (that is, regardless of execution result), then semantically speaking, the response status should be 200 OK because the request to execute the group succeeded. Otherwise, if there was a problem in fulfilling the request to execute the task group, then the response status should denote that error.


The long answer

You are experiencing this dilemma because of an incoherence between what you are doing and what HTTP was designed for. When a URI (Unified Resource Identifier) diverts from being an identifier for a resource, and looks more like a name of a function, you are likely doing something wrong.

The short and direct answer is the right one if you were posting tasks to a queue, however it is not entirely coherent with the implied approach that your implementation has toward its HTTP API.

With the above being said, and without courage to turn this answer into a long opinionated guide, the following is an example for a URI scheme that came to mind with the intention of sparking inspiration:

  • /tasks
  • GET responds with a list of all tasks, likely paginated via Query String
  • POST will add a single task to a queue, returning the task ID
  • /tasks/task/[id]
  • GET responds with the task's status, perhaps information about when it was queued and executed, its type and the rest of the properties
  • PUT if no task with that ID exists, then adds a single task with that ID, same request body as a POST to /tasks. If one exists, responds with appropriate status code
  • DELETE if the task was not already executed, then cancel it. If it was executed, responds with appropriate status code
  • /tasks/groups
  • GET responds with a list of all task groups, likely paginated via Query String
  • POST receives an object with an array of the same body that /tasks would, along with other group-specific properties (or just that array property if no other properties). Responds with an object that contains the ID of the group, along with an array of the IDs of the individual tasks.
  • /tasks/groups/group/[id]
  • GET responds with an object denoting the execution status for the entire group (i.e. whether execution finished for all tasks, or if there are still tasks executing), each individual task's status in an array, and more properties regarding that group if any (like when it was requested, when it was complete, etc). A request with ?awaitCompletion=true query string will respond when the group finishes execution.
  • PUT if no group with that ID exists, adds a group with that ID. Otherwise, responds with appropriate error status.
  • DELETE if there are still tasks executing in the group, cancels them.

Executing a task group and tracking progress:

  • POST /tasks/groups with the group of tasks to execute
  • GET /tasks/groups/group/[groupId] until response object completed property has value, showing individual task status (3 tasks completed out of 5, for example)
  • Denote completion with last request results (Executed 5 tasks, 1 task failed)

Requesting an execution for a task group and waiting for its completion:

  • POST /tasks/groups with the group of tasks to execute
  • GET /tasks/groups/group/[groupId]?awaitCompletion=true until responds with result that denotes completion (there's likely a timeout on how long this will wait)

Why it's hard to accept this

The above scheme requires that the HTTP server communicates with other services to execute tasks rather than executing them in the request handler. This isn't a shallow implementation, it's a change in direction.

You generally don't want to block very long in an HTTP request handler. Much like UI, you want to be responsive. In a timescale that is a few orders of magnitude slower because you are dealing with IO.

Ben Barkay
  • 568
  • 4
  • 5