95. Design System for Managing Workflows

Design System for Managing Workflows
Design and implement an interface for submitting and managing workflows. A workflow consists of one or more tasks and can run either sequentially or in parallel. The system must also support passing data between workflows, where the output of one workflow becomes the input of another connected workflow. Each task works only on List<String>: it takes a list of strings as input, applies its configured simple string-list operations in order, and returns another List<String> as output.

Task Definition Format

Available tasks are provided to the constructor as task-definition strings. Each task definition uses the format:

taskId-functionality1,functionality2,...

Example:

validate-removeEmptyStrings,trimEachAndRemoveEmpty
refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically

Required Class

WorkflowManager(List<String> availableTaskDefinitions)
  • Each entry in availableTaskDefinitions defines one task id and its ordered functionalities.
  • All constructor inputs are guaranteed valid.
  • The following is a valid example:
WorkflowManager(List.of( "validate-removeEmptyStrings,trimEachAndRemoveEmpty", "clean-lowercase,sortByLength", "refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically", "email-uppercase", "sms-reverseList" ))

Supported Functionalities

removeEmptyStrings

  • Removes strings that are exactly empty or contain only spaces.
  • List.of("cat", "", "dog", " ") -> List.of("cat", "dog")

trimEachAndRemoveEmpty

  • Trims every string, then removes strings that become empty after trimming.
  • List.of(" cat ", " ", "dog") -> List.of("cat", "dog")

removeDuplicateStringsIgnoreCaseKeepFirst

  • Keeps only the first occurrence of each string ignoring case.
  • List.of("cat", "dog", "Cat", "DOG") -> List.of("cat", "dog")

sortStringsLexicographically

  • Sorts the list in lexicographical order.
  • List.of("banana", "apple", "cat") -> List.of("apple", "banana", "cat")

sortByLength

  • Sorts strings by length in ascending order. If lengths are equal, keep their existing relative order.
  • List.of("elephant", "cat", "dog") -> List.of("cat", "dog", "elephant")

reverseList

  • Reverses the order of the list.
  • List.of("a", "b", "c") -> List.of("c", "b", "a")

lowercase

  • Converts every string in the list to lowercase.
  • List.of("Cat", "DOG") -> List.of("cat", "dog")

uppercase

  • Converts every string in the list to uppercase.
  • List.of("Cat", "dog") -> List.of("CAT", "DOG")

Execution Rules

  • Inside one task, functionalities are applied in the exact order given in its task definition.
  • In a sequential workflow, the output of one task becomes the input of the next task.
  • In a parallel workflow, every task receives the same original input list of that workflow.
  • For a parallel workflow, task outputs are merged by concatenating the output lists in the same task order used in the workflow definition.
  • If workflow A is connected to workflow B, then the final output of A becomes the input of B.
  • Each workflow may have at most one direct downstream and at most one direct upstream workflow.
  • If a workflow chain is executed, executeWorkflow returns the final output of the last workflow in that chain.

Top Methods

boolean submitWorkflow(String workflowId, List<String> taskIds, boolean runInParallel)

  • Creates a new workflow with the given task ids and execution mode.
  • workflowId must be unique.
  • Each task id in taskIds must exist in availableTaskDefinitions.
  • 1 ≤ taskIds.size()
  • Return true if the workflow is created successfully, otherwise return false.

boolean connectWorkflows(String sourceWorkflowId, String targetWorkflowId)

  • Connects one workflow to another.
  • The final output of sourceWorkflowId becomes the input of targetWorkflowId.
  • Both workflows must already exist.
  • sourceWorkflowId and targetWorkflowId must be different.
  • The connection must not create a cycle.
  • sourceWorkflowId must not already have a direct downstream workflow.
  • targetWorkflowId must not already have a direct upstream workflow.
  • Therefore, workflows can form only a linear chain. Branching and merging are not supported.
  • Return true if the connection is added successfully, otherwise return false.

List<String> executeWorkflow(String workflowId, List<String> inputData)

  • Executes the given workflow using the provided input list.
  • workflowId must exist.
  • Every task in the workflow must apply its internal functionalities in order.
  • If the workflow is sequential, task outputs flow one by one through the workflow.
  • If the workflow is parallel, all tasks use the same input list and their output lists are concatenated in task order.
  • If a downstream workflow is connected, its execution starts automatically with the current workflow's final output.
  • Return the final List<String> produced by the last workflow in the chain.

String getWorkflowDetails(String workflowId)

  • Returns a string representation of the workflow configuration.
  • The response must include workflow id, execution mode, task order, and directly connected downstream workflow if present.
  • If the workflow does not exist, return "WORKFLOW_NOT_FOUND".
  • The returned string must exactly follow this format:
    workflowId=<workflowId>, mode=<SEQUENTIAL|PARALLEL>, tasks=<task1->task2->...>, downstream=<downstreamWorkflowId or empty string>

Output Format Example

  • workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream=WF-NOTIFY
  • workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream

Constraints

  • 1 ≤ availableTaskDefinitions.size() ≤ 10^4
  • 1 ≤ taskIds.size() ≤ 50 for one workflow
  • 0 ≤ inputData.size() ≤ 10^4
  • Each task id and workflow id is a non-empty string.
  • Task ids are unique across availableTaskDefinitions.
  • Each task definition contains at least one functionality.
  • A workflow cannot be submitted more than once with the same id.
  • Self-connections are invalid.
  • Cyclic workflow connections are invalid.
  • All operations should be handled in memory.

Example 1

    WorkflowManager(
    availableTaskDefinitions=List.of(
    "validate-removeEmptyStrings,trimEachAndRemoveEmpty",
    "clean-lowercase,sortByLength",
    "refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically",
    "email-uppercase",
    "sms-reverseList"
    )
    )

submitWorkflow(workflowId="WF-MAIN", taskIds=List.of("validate", "clean", "refine"), runInParallel=false) -> true

getWorkflowDetails(workflowId="WF-MAIN") -> "workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream="

executeWorkflow(workflowId="WF-MAIN", inputData=List.of(" Cat ", "", "dog", "cat", " ")) -> List.of("cat", "dog")

Explanation:
  • validate: first remove exact empty strings / space-only strings, then trim each remaining string and remove any that become empty. So List.of(" Cat ", "", "dog", "cat", " ") -> List.of("Cat", "dog", "cat").
  • clean: lowercase then stable sort by length. So List.of("Cat", "dog", "cat") -> List.of("cat", "dog", "cat").
  • refine: remove duplicates ignoring case while keeping the first occurrence, then sort lexicographically. So List.of("cat", "dog", "cat") -> List.of("cat", "dog").

Example 2

    WorkflowManager(
    availableTaskDefinitions=List.of(
    "validate-removeEmptyStrings,trimEachAndRemoveEmpty",
    "clean-lowercase,sortByLength",
    "refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically",
    "email-uppercase",
    "sms-reverseList"
    )
    )
  

submitWorkflow(workflowId="WF-MAIN", taskIds=List.of("validate", "clean", "refine"), runInParallel=false) -> true

submitWorkflow(workflowId="WF-NOTIFY", taskIds=List.of("email", "sms"), runInParallel=true) -> true

connectWorkflows(sourceWorkflowId="WF-MAIN", targetWorkflowId="WF-NOTIFY") -> true

getWorkflowDetails(workflowId="WF-MAIN") -> "workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream=WF-NOTIFY"

getWorkflowDetails(workflowId="WF-NOTIFY") -> "workflowId=WF-NOTIFY, mode=PARALLEL, tasks=email->sms, downstream="

executeWorkflow(workflowId="WF-MAIN", inputData=List.of(" Cat ", "", "dog", "cat", " ")) -> List.of("CAT", "DOG", "dog", "cat")

Explanation:
  • WF-MAIN executes first and produces List.of("cat", "dog").
  • Because WF-MAIN is connected to WF-NOTIFY, that output becomes the input of WF-NOTIFY.
  • WF-NOTIFY is a parallel workflow, so both tasks receive the same input List.of("cat", "dog").
  • email: uppercase, so List.of("cat", "dog") -> List.of("CAT", "DOG").
  • sms: reverse list, so List.of("cat", "dog") -> List.of("dog", "cat").
  • The parallel outputs are concatenated in task order: first email, then sms. Final result: List.of("CAT", "DOG", "dog", "cat").




Please use Laptop/Desktop or any other large screen to add/edit code.