This repository demonstrates a backend architecture that handles asynchronous tasks, workflows, and job execution using TypeScript, Express.js, and TypeORM. The project showcases how to:
- Define and manage entities such as
Task
andWorkflow
. - Use a
WorkflowFactory
to create workflows from YAML configurations. - Implement a
TaskRunner
that executes jobs associated with tasks and manages task and workflow states. - Run tasks asynchronously using a background worker.
-
Entity Modeling with TypeORM
- Task Entity: Represents an individual unit of work with attributes like
taskType
,status
,progress
, and references to aWorkflow
. - Workflow Entity: Groups multiple tasks into a defined sequence or steps, allowing complex multi-step processes.
- Task Entity: Represents an individual unit of work with attributes like
-
Workflow Creation from YAML
- Use
WorkflowFactory
to load workflow definitions from a YAML file. - Dynamically create workflows and tasks without code changes by updating YAML files.
- Use
-
Asynchronous Task Execution
- A background worker (
taskWorker
) continuously polls forqueued
tasks. - The
TaskRunner
runs the appropriate job based on a task’staskType
.
- A background worker (
-
Robust Status Management
TaskRunner
updates the status of tasks (fromqueued
toin_progress
,completed
, orfailed
).- Workflow status is evaluated after each task completes, ensuring you know when the entire workflow is
completed
orfailed
.
-
Dependency Injection and Decoupling
TaskRunner
takes in only theTask
and determines the correct job internally.TaskRunner
handles task state transitions, leaving the background worker clean and focused on orchestration.
src
├─ models/
│ ├─ world_data.json # Contains world data for analysis
│
├─ models/
│ ├─ Result.ts # Defines the Result entity
│ ├─ Task.ts # Defines the Task entity
│ ├─ Workflow.ts # Defines the Workflow entity
│
├─ jobs/
│ ├─ Job.ts # Job interface
│ ├─ JobFactory.ts # getJobForTaskType function for mapping taskType to a Job
│ ├─ TaskRunner.ts # Handles job execution & task/workflow state transitions
│ ├─ DataAnalysisJob.ts (example)
│ ├─ EmailNotificationJob.ts (example)
│
├─ workflows/
│ ├─ WorkflowFactory.ts # Creates workflows & tasks from a YAML definition
│
├─ workers/
│ ├─ taskWorker.ts # Background worker that fetches queued tasks & runs them
│
├─ routes/
│ ├─ analysisRoutes.ts # POST /analysis endpoint to create workflows
│
├─ data-source.ts # TypeORM DataSource configuration
└─ index.ts # Express.js server initialization & starting the worker
- Node.js (LTS recommended)
- npm or yarn
- SQLite or another supported database
-
Clone the repository:
git clone https://github.com/yourusername/backend-coding-challenge.git cd backend-coding-challenge
-
Install dependencies:
npm install
-
Configure TypeORM:
- Edit
data-source.ts
to ensure theentities
array includesTask
andWorkflow
entities. - Confirm database settings (e.g. SQLite file path).
- Edit
-
Create or Update the Workflow YAML:
- Place a YAML file (e.g.
example_workflow.yml
) in aworkflows/
directory. - Define steps, for example:
name: "example_workflow" steps: - taskType: "analysis" stepNumber: 1 - taskType: "notification" stepNumber: 2
- Place a YAML file (e.g.
-
Compile TypeScript (optional if using
ts-node
):npx tsc
-
Start the server:
npm start
If using
ts-node
, this will start the Express.js server and the background worker after database initialization. -
Create a Workflow (e.g. via
/analysis
):curl -X POST http://localhost:3000/analysis \ -H "Content-Type: application/json" \ -d '{ "clientId": "client123", "geoJson": { "type": "Polygon", "coordinates": [ [ [ -63.624885020050996, -10.311050368263523 ], [ -63.624885020050996, -10.367865108370523 ], [ -63.61278302732815, -10.367865108370523 ], [ -63.61278302732815, -10.311050368263523 ], [ -63.624885020050996, -10.311050368263523 ] ] ] } }'
This will read the configured workflow YAML, create a workflow and tasks, and queue them for processing.
-
Check Logs:
- The worker picks up tasks from
queued
state. TaskRunner
runs the corresponding job (e.g., data analysis, email notification) and updates states.- Once tasks are done, the workflow is marked as
completed
.
- The worker picks up tasks from
The following tasks must be completed to enhance the backend system:
Objective:
Create a new job class to calculate the area of a polygon from the GeoJSON provided in the task.
- Create a new job file
PolygonAreaJob.ts
in thesrc/jobs/
directory. - Implement the
Job
interface in this new class. - Use
@turf/area
to calculate the polygon area from thegeoJson
field in the task. - Save the result in the
output
field of the task.
- The
output
should include the calculated area in square meters. - Ensure that the job handles invalid GeoJSON gracefully and marks the task as failed.
Objective:
Create a new job class to generate a report by aggregating the outputs of multiple tasks in the workflow.
- Create a new job file
ReportGenerationJob.ts
in thesrc/jobs/
directory. - Implement the
Job
interface in this new class. - Aggregate outputs from all preceding tasks in the workflow into a JSON report. For example:
{ "workflowId": "<workflow-id>", "tasks": [ { "taskId": "<task-1-id>", "type": "polygonArea", "output": "<area>" }, { "taskId": "<task-2-id>", "type": "dataAnalysis", "output": "<analysis result>" } ], "finalReport": "Aggregated data and results" }
- Save the report as the
output
of theReportGenerationJob
.
- Ensure the job runs only after all preceding tasks are complete.
- Handle cases where tasks fail, and include error information in the report.
Objective:
Modify the system to support workflows with tasks that depend on the outputs of earlier tasks.
- Update the
Task
entity to include adependency
field that references another task - Modify the
TaskRunner
to wait for dependent tasks to complete and pass their outputs as inputs to the current task. - Extend the workflow YAML format to specify task dependencies (e.g.,
dependsOn
). - Update the
WorkflowFactory
to parse dependencies and create tasks accordingly.
- Ensure dependent tasks do not execute until their dependencies are completed.
- Test workflows where tasks are chained through dependencies.
Objective:
Save the aggregated results of all tasks in the workflow as the finalResult
field of the Workflow
entity.
- Modify the
Workflow
entity to include afinalResult
field: - Aggregate the outputs of all tasks in the workflow after the last task completes.
- Save the aggregated results in the
finalResult
field.
- The
finalResult
must include outputs from all completed tasks. - Handle cases where tasks fail, and include failure information in the final result.
Objective:
Implement an API endpoint to retrieve the current status of a workflow.
- URL:
/workflow/:id/status
- Method:
GET
- Response Example:
{ "workflowId": "3433c76d-f226-4c91-afb5-7dfc7accab24", "status": "in_progress", "completedTasks": 3, "totalTasks": 5 }
- Include the number of completed tasks and the total number of tasks in the workflow.
- Return a
404
response if the workflow ID does not exist.
Objective:
Implement an API endpoint to retrieve the final results of a completed workflow.
- URL:
/workflow/:id/results
- Method:
GET
- Response Example:
{ "workflowId": "3433c76d-f226-4c91-afb5-7dfc7accab24", "status": "completed", "finalResult": "Aggregated workflow results go here" }
- Return the
finalResult
field of the workflow if it is completed. - Return a
404
response if the workflow ID does not exist. - Return a
400
response if the workflow is not yet completed.
-
Code Implementation:
- New jobs:
PolygonAreaJob
andReportGenerationJob
. - Enhanced workflow support for interdependent tasks.
- Workflow final results aggregation.
- New API endpoints for workflow status and results.
- New jobs:
-
Documentation:
- Update the README file to include instructions for testing the new features.
- Document the API endpoints with request and response examples.