Scalable Workflow Architecture: Multi-UI Support
Hey guys! Today, we're diving deep into implementing a scalable workflow architecture that supports multiple user interfaces. This is all about making our lives easier by decoupling core logic from the UI layer. Let's break it down and see how we can make this happen!
Context and Vision
The main goal here is to support both a Command-Line Interface (@stackcode/cli
) and a native VS Code Extension UI (@stackcode/vscode
). To achieve this, we need a scalable architecture. The vision is to create a generalized pattern where any command can be driven by multiple, distinct user interfaces without duplicating the same logic. Think of it as having one brain that can talk to many faces!
The key is to decouple the core business logic and command orchestration from the UI layer. Currently, the UI is tightly coupled with the inquirer
terminal library, which isn't ideal for our multi-UI vision. We want the core logic to be independent, so it can be used by any UI, whether it's a CLI, a VS Code extension, or even a web interface in the future.
To achieve this, we will introduce a Workflow Layer within the @stackcode/core
package. This layer will act as the central processing unit, handling all the heavy lifting while the UI layers simply serve as input/output interfaces. This approach ensures that changes to the UI don't affect the underlying logic, and vice versa.
The New Architecture: A Workflow Layer
We're introducing a Workflow Layer within the @stackcode/core
package. This layer will be the heart of our new architecture, making sure everything runs smoothly regardless of the UI being used. For each user-facing command (e.g., init
, commit
), we'll create a corresponding workflow function in core
(e.g., runInitWorkflow
, runCommitWorkflow
).
These workflow functions are the central "brains". They'll accept a simple options object and be responsible for orchestrating all the low-level logic (scaffolding, git operations, etc.). This means all the complex stuff happens here, neatly tucked away from the UI.
The UI packages (@stackcode/cli
and @stackcode/vscode
) become simple "frontends." Their only job is to collect input from the user (via inquirer
or VS Code APIs) and call the appropriate workflow function in @stackcode/core
. This makes the UI packages lightweight and focused on what they do best: providing a user-friendly interface.
Imagine it like this: the UI is the waiter taking your order, and the workflow function is the chef preparing your meal. The waiter (UI) takes your order (input) and passes it to the chef (workflow function), who then cooks the meal (processes the logic) and sends it back to you. The waiter doesn't need to know how to cook; they just need to know how to take orders and deliver food.
This Issue's Scope: Implementing the Pattern for the init
Command
For this particular issue, we're focusing on implementing this pattern for the init
command. Think of this as our proof-of-concept to ensure the architecture works as expected. We'll start by refactoring the init
command to use the new Workflow Layer, and then we'll expand it to other commands later.
Here’s what we’re aiming for:
- A new
workflows.ts
module is created in@stackcode/core
. This module will house all our workflow functions. - A
runInitWorkflow(options)
function is implemented in this module, containing the step-by-step logic currently in thecli/init.ts
handler. This function will be the new brain for theinit
command. - The
@stackcode/cli
init
command is refactored to be a thin UI layer that only gathers input and callsrunInitWorkflow
. This makes the CLI a simple frontend, focused on collecting user input. - The
@stackcode/vscode
extension uses native VS Code APIs (showInputBox
,showQuickPick
) to gather input for theinit
command. This allows the VS Code extension to provide a native, integrated experience. - The extension then calls the same
runInitWorkflow
function to execute the process, reporting progress via VS Code notifications. This ensures that both the CLI and the VS Code extension use the same core logic.
Acceptance Criteria
To make sure we're on the right track, here are the acceptance criteria we'll be using:
- [ ] A new
workflows.ts
module is created in@stackcode/core
. - [ ] A
runInitWorkflow(options)
function is implemented in this module, containing the step-by-step logic currently in thecli/init.ts
handler. - [ ] The
@stackcode/cli
init
command is refactored to be a thin UI layer that only gathers input and callsrunInitWorkflow
. - [ ] The
@stackcode/vscode
extension uses native VS Code APIs (showInputBox
,showQuickPick
) to gather input for theinit
command. - [ ] The extension then calls the same
runInitWorkflow
function to execute the process, reporting progress via VS Code notifications.
Let's walk through each of these criteria in a bit more detail.
Creating the workflows.ts
Module
First up, we need to create a new workflows.ts
module inside the @stackcode/core
package. This module will be the home for all our workflow functions. Think of it as the central command center where all the action happens. To create this, simply navigate to the @stackcode/core
directory and create a new file named workflows.ts
. Make sure it's properly integrated into the project's build process so it can be imported and used by other modules.
Implementing the runInitWorkflow(options)
Function
Next, we need to implement the runInitWorkflow(options)
function within the workflows.ts
module. This function is where the magic happens. It encapsulates all the step-by-step logic that's currently in the cli/init.ts
handler. This includes things like:
- Validating input options
- Creating necessary directories
- Initializing configuration files
- Setting up Git repositories
- Installing dependencies
The function should accept an options
object as input, which contains all the necessary parameters for the init
command. This allows us to pass in different configurations depending on the UI being used. For example, the CLI might pass in command-line arguments, while the VS Code extension might pass in values obtained from input boxes and quick picks.
Refactoring the @stackcode/cli
init
Command
Now, let's refactor the @stackcode/cli
init
command to be a thin UI layer. This means we need to strip out all the business logic and leave only the code that's responsible for gathering input from the user. The refactored command should:
- Prompt the user for necessary input using
inquirer
or similar tools - Create an
options
object containing the user's input - Call the
runInitWorkflow(options)
function in@stackcode/core
- Handle any errors or exceptions that might occur
By doing this, we're making the CLI a simple frontend that's focused on collecting user input and passing it to the core logic. This makes the CLI more maintainable and easier to test.
Using VS Code APIs in the @stackcode/vscode
Extension
For the @stackcode/vscode
extension, we want to provide a native, integrated experience. This means we need to use VS Code APIs like showInputBox
and showQuickPick
to gather input from the user. These APIs allow us to create input fields and dropdown menus that look and feel like they're part of VS Code.
The extension should:
- Use
showInputBox
to prompt the user for text-based input, such as project names and descriptions - Use
showQuickPick
to present the user with a list of options, such as project templates and programming languages - Create an
options
object containing the user's input - Call the
runInitWorkflow(options)
function in@stackcode/core
- Report progress to the user via VS Code notifications
Executing the Process and Reporting Progress
Finally, we need to make sure that both the CLI and the VS Code extension call the same runInitWorkflow
function to execute the process. This ensures that the same core logic is used regardless of the UI being used. Additionally, the VS Code extension should report progress to the user via VS Code notifications.
This can be done using the vscode.window.withProgress
API, which allows us to display a progress bar in the VS Code status bar. This provides the user with feedback on the progress of the init
command and helps them understand what's happening behind the scenes.
By following these steps, we can ensure that our init
command is scalable, maintainable, and easy to use across multiple UIs. And remember, this is just the beginning. Once we've successfully implemented this pattern for the init
command, we can expand it to other commands as well.
This new architecture not only supports multiple UIs but also enhances the maintainability and scalability of our codebase. By decoupling the UI layer from the core logic, we create a more modular and flexible system. This makes it easier to add new features, fix bugs, and adapt to changing requirements. Plus, it opens the door for supporting even more UIs in the future, such as web interfaces or mobile apps.