docs: create docs workspace (#5174)

* docs: migrate ui docs to docs universe

* created yarn workspace

* added eslint and tsconfig configurations

* fix eslint configurations

* fixed eslint configurations

* shared tailwind configurations

* added shared ui package

* added more shared components

* migrating more components

* made details components shared

* move InlineCode component

* moved InputText

* moved Loading component

* Moved Modal component

* moved Select components

* Moved Tooltip component

* moved Search components

* moved ColorMode provider

* Moved Notification components and providers

* used icons package

* use UI colors in api-reference

* moved Navbar component

* used Navbar and Search in UI docs

* added Feedback to UI docs

* general enhancements

* fix color mode

* added copy colors file from ui-preset

* added features and enhancements to UI docs

* move Sidebar component and provider

* general fixes and preparations for deployment

* update docusaurus version

* adjusted versions

* fix output directory

* remove rootDirectory property

* fix yarn.lock

* moved code component

* added vale for all docs MD and MDX

* fix tests

* fix vale error

* fix deployment errors

* change ignore commands

* add output directory

* fix docs test

* general fixes

* content fixes

* fix announcement script

* added changeset

* fix vale checks

* added nofilter option

* fix vale error
This commit is contained in:
Shahed Nasser
2023-09-21 20:57:15 +03:00
committed by GitHub
parent 19c5d5ba36
commit fa7c94b4cc
3209 changed files with 32188 additions and 31018 deletions
@@ -0,0 +1,27 @@
---
description: "Learn about Medusa's architecture and get a general overview of how all different tools work together."
---
# Medusa Architecture Overview
In this document, you'll get an overview of Medusa's architecture to better understand how all resources and tools work together.
## Architecture Overview
Medusa's core package `@medusajs/medusa` is a Node.js backend built on top of [Express](https://expressjs.com/). It combines all the [Commerce Modules](../../modules/overview.mdx) that Medusa provides. Commerce Modules are ecommerce features that can be used as building blocks in an ecommerce ecosystem. Product is an example of a Commerce Module.
![Medusa Core Architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1677607702/Medusa%20Docs/Diagrams/medusa-architecture-3_e385zk.jpg)
The backend connects to a database, such as [PostgreSQL](https://www.postgresql.org/), to store the ecommerce stores data. The tables in that database are represented by [Entities](../entities/overview.mdx), built on top of [Typeorm](https://typeorm.io/). Entities can also be reflected in the database using [Migrations](../entities/migrations/overview.mdx).
The retrieval, manipulation, and other utility methods related to that entity are created inside a [Service](../services/overview.mdx). Services are TypeScript or JavaScript classes that, along with other resources, can be accessed throughout the Medusa backend through [dependency injection](./dependency-injection.md).
The backend does not have any tightly-coupled frontend. Instead, it exposes [Endpoints](../endpoints/overview.mdx) which are REST APIs that frontends such as an admin or a storefront can use to communicate with the backend. Endpoints are [Express routes](https://expressjs.com/en/guide/routing.html).
Medusa also uses an [Events Architecture](../events/index.mdx) to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as [Redis](https://redis.io/).
Events can be handled using [Subscribers](../events/subscribers.mdx). Subscribers are TypeScript or JavaScript classes that add their methods as handlers for specific events. These handler methods are only executed when an event is triggered.
You can create any of the resources in the backends architecture, such as entities, endpoints, services, and more, as part of your custom development without directly modifying the backend itself. The Medusa backend uses [loaders](../loaders/overview.mdx) to load the backends resources, as well as your custom resources and resources in [Plugins](../plugins/overview.mdx).
You can package your customizations into Plugins to reuse them in different Medusa backends or publish them for others to use. You can also install existing plugins into your Medusa backend.
@@ -0,0 +1,735 @@
---
description: 'Learn what the dependency container is and how to use it in Medusa. Learn also what dependency injection is, and what the resources registered and their names are.'
---
# Dependency Container and Injection
In this document, youll learn what the dependency container is and how you can use it in Medusa with dependency injection.
## Introduction
### What is Dependency Injection
Dependency Injection is the act of delivering the required resources to a class. These resources are the classs dependencies. This is usually done by passing (or injecting) the dependencies in the constructor of the class.
Generally, all resources are registered in a container. Then, whenever a class depends on one of these resources, the system retrieves the resources from the container and injects them into the classs constructor.
### Medusas Dependency Container
Medusa uses a dependency container to register essential resources of the backend. You can then access these resources in classes and endpoints using the dependency container.
For example, if you create a custom service, you can access any other service registered in Medusa in your services constructor. That includes Medusas core services, services defined in plugins, or other services that you create on your backend.
You can load more than services in your Medusa backend. You can load the Entity Manager, logger instance, and much more.
### MedusaContainer
To manage dependency injections, Medusa uses [Awilix](https://github.com/jeffijoe/awilix). Awilix is an NPM package that implements dependency injection in Node.js projects.
When you run the Medusa backend, a container of the type `MedusaContainer` is created. This type extends the [AwilixContainer](https://github.com/jeffijoe/awilix#the-awilixcontainer-object) object.
The backend then registers all important resources in the container, which makes them accessible in classes and endpoints.
---
## Registered Resources
The Medusa backend scans the core Medusa package, plugins, and your files in the `dist` directory and registers the following resources:
:::tip
The Lifetime column indicates the lifetime of a service. Other resources that aren't services don't have a lifetime, which is indicated with the `-` in the column. You can learn about what a lifetime is in the [Create a Service](../services/create-service.mdx) documentation.
:::
<table class="reference-table table-col-4">
<thead>
<tr>
<th>
Resource
</th>
<th>
Description
</th>
<th>
Registration Name
</th>
<th>
Lifetime
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Configurations
</td>
<td>
The configurations that are exported from `medusa-config.js`.
</td>
<td>
`configModule`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Services
</td>
<td>
Services that extend the `TransactionBaseService` class.
</td>
<td>
Each service is registered under its camel-case name. For example, the `ProductService` is registered as `productService`.
</td>
<td>
Core services by default have the `SINGLETON` lifetime. However, some have a different lifetime which is indicated in this table. Custom services, including services in plugins, by default have the `SCOPED` lifetime, unless defined differently within the custom service.
</td>
</tr>
<tr>
<td>
Entity Manager
</td>
<td>
An instance of Typeorms Entity Manager.
</td>
<td>
`manager`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Logger
</td>
<td>
An instance of Medusa CLIs logger. You can use it to log messages to the terminal.
</td>
<td>
`logger`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Single Payment Processor
</td>
<td>
An instance of every payment processor that extends the `AbstractPaymentService` or the `AbstractPaymentProcessor` classes.
</td>
<td>
Every payment processor is registered under two names:
- Its camel-case name of the processor. For example, the `StripeProviderService` is registered as `stripeProviderService`.
- `pp_` followed by its identifier. For example, the `StripeProviderService` is registered as `pp_stripe`.
</td>
<td>
By default, it's `SINGLETON` unless defined differently within the payment processor service.
</td>
</tr>
<tr>
<td>
All Payment Processors
</td>
<td>
An array of all payment processor that extend the `AbstractPaymentService` or `AbstractPaymentProcessor` class.
</td>
<td>
`paymentProviders`
</td>
<td>
`paymentProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
</td>
</tr>
<tr>
<td>
Single Fulfillment Provider
</td>
<td>
An instance of every fulfillment provider that extends the `FulfillmentService` class.
</td>
<td>
Every fulfillment provider is registered under two names:
- Its camel-case name. For example, the `WebshipperFulfillmentService` is registered as `webshipperFulfillmentService`.
- `fp_` followed by its identifier. For example, the `WebshipperFulfillmentService` is registered as `fp_webshipper`.
</td>
<td>
By default, it's `SINGLETON` unless defined differently within the fulfillemnt provider service.
</td>
</tr>
<tr>
<td>
All Fulfillment Providers
</td>
<td>
An array of all fulfillment providers that extend the `FulfillmentService` class.
</td>
<td>
`fulfillmentProviders`
</td>
<td>
`fulfillmentProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
</td>
</tr>
<tr>
<td>
Single Notification Provider
</td>
<td>
An instance of every notification provider that extends the `AbstractNotificationService` or the `BaseNotificationService` classes.
</td>
<td>
Every notification provider is registered under two names:
- Its camel-case name. For example, the `SendGridService` is registered as `sendGridService`.
- `noti_` followed by its identifier. For example, the `SendGridService` is registered as `noti_sendgrid`.
</td>
<td>
By default, it's `SINGLETON` unless defined differently within the notification provider service.
</td>
</tr>
<tr>
<td>
All Notification Providers
</td>
<td>
An array of all notification providers that extend the `AbstractNotificationService` or the `BaseNotificationService` classes.
</td>
<td>
`notificationProviders`
</td>
<td>
`notificationProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
</td>
</tr>
<tr>
<td>
File Service
</td>
<td>
An instance of the class that extends the `FileService` class, if any.
</td>
<td>
The file service is registered under two names:
- Its camel-case name. For example, the `MinioService` is registered as `minioService`.
- `fileService`
</td>
<td>
By default, it's `SINGLETON` unless defined differently within the file service.
</td>
</tr>
<tr>
<td>
Search Service
</td>
<td>
An instance of the class that extends the `AbstractSearchService` or the `SearchService` classes, if any.
</td>
<td>
The search service is registered under two names:
- Its camel-case name. For example, the `AlgoliaService` is registered as `algoliaService`.
- `searchService`
</td>
<td>
By default, it's `SINGLETON` unless defined differently within the search service.
</td>
</tr>
<tr>
<td>
Single Tax Provider
</td>
<td>
An instance of every tax provider that extends the `AbstractTaxService` class.
</td>
<td>
The tax provider is registered under two names:
- Its camel-case name.
- `tp_` followed by its identifier.
</td>
<td>
By default, it's `SINGLETON` unless defined differently within the tax provider service.
</td>
</tr>
<tr>
<td>
All Tax Providers
</td>
<td>
An array of every tax provider that extends the `AbstractTaxService` class.
</td>
<td>
`taxProviders`
</td>
<td>
`taxProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
</td>
</tr>
<tr>
<td>
Oauth Services
</td>
<td>
An instance of every service that extends the `OauthService` class.
</td>
<td>
Each Oauth Service is registered under its camel-case name followed by `Oauth`.
</td>
<td>
By default, it's `SINGLETON` unless defined differently within the Oauth service.
</td>
</tr>
<tr>
<td>
Feature Flag Router
</td>
<td>
An instance of the `FlagRouter`. This can be used to list feature flags, set a feature flags value, or check if theyre enabled.
</td>
<td>
`featureFlagRouter`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Redis
</td>
<td>
An instance of the Redis client. If Redis is not configured, a fake Redis client is registered.
</td>
<td>
`redisClient`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Single Entity
</td>
<td>
An instance of every entity.
</td>
<td>
Each entity is registered under its camel-case name followed by Model. For example, the `CustomerGroup` entity is stored under `customerGroupModel`.
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
All Entities
</td>
<td>
An array of all database entities that is passed to Typeorm when connecting to the database.
</td>
<td>
`db_entities`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Repositories
</td>
<td>
An instance of each repository.
</td>
<td>
Each repository is registered under its camel-case name. For example, `CustomerGroupRepository` is stored under `customerGroupRepository`.
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Single Batch Job Strategy
</td>
<td>
An instance of every class extending the `AbstractBatchJobStrategy` class.
</td>
<td>
Each batch job strategy is registered under three names:
- Its camel-case name. For example, `ProductImportStrategy` is registered as `productImportStrategy`.
- `batch_` followed by its identifier. For example, the `ProductImportStrategy` is registered under `batch_product-import-strategy`.
- `batchType_` followed by its batch job type. For example, the `ProductImportStrategy` is registered under `batchType_product-import`.
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
All Batch Job Strategies
</td>
<td>
An array of all classes extending the `AbstractBatchJobStrategy` abstract class.
</td>
<td>
`batchJobStrategies`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Tax Calculation Strategy
</td>
<td>
An instance of the class implementing the `ITaxCalculationStrategy` interface.
</td>
<td>
`taxCalculationStrategy`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Cart Completion Strategy
</td>
<td>
An instance of the class extending the `AbstractCartCompletionStrategy` class.
</td>
<td>
`cartCompletionStrategy`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Price Selection Strategy
</td>
<td>
An instance of the class implementing the `IPriceSelectionStrategy` interface.
</td>
<td>
`priceSelectionStrategy`
</td>
<td>
\-
</td>
</tr>
<tr>
<td>
Strategies
</td>
<td>
An instance of strategies that arent of the specific types mentioned above and that are under the `strategies` directory.
</td>
<td>
Its camel-case name.
</td>
<td>
\-
</td>
</tr>
</tbody>
</table>
---
## Resolve Resources
This section covers how to resolve resources from the dependency container to use them in endpoints and classes in general.
### In Endpoints
To resolve resources, such as services, in endpoints, use the `req.scope.resolve` method. The method receives the registration name of the resource as a parameter.
For example:
```ts
const logger = req.scope.resolve("logger")
```
Please note that in endpoints some resources, such as repositories, are not available. Refer to the [repositories](../entities/repositories.md) documentation to learn how you can load them.
### In Classes
In classes such as services, strategies, or subscribers, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.
For example:
```ts
import { OrderService } from "@medusajs/medusa"
class OrderSubscriber {
protected orderService: OrderService
constructor({ orderService }) {
this.orderService = orderService
}
}
```
---
## See Also
- [Create services](../services/create-service.mdx)
- [Create subscribers](../events/create-subscriber.md)
@@ -0,0 +1,194 @@
---
description: 'Learn how to perform local development in the Medusa monorepo. This includes how to use the dev CLI tool and perform unit, integration, and plugin tests.'
---
# Local Development of Medusa Backend and Monorepo
In this document, youll learn how to customize Medusas core and run tests.
## Overview
As an open-source platform, Medusas core can be completely customized.
Whether you want to implement something differently, introduce a new feature as part of Medusas core or any of the other packages, or contribute to Medusa, this guide helps you learn how to run Medusas integration tests, as well as test your own Medusa core in a local backend.
### Medusa Repository Overview
[Medusas repository on GitHub](https://github.com/medusajs/medusa) includes all packages related to Medusa under the [`packages` directory](https://github.com/medusajs/medusa/tree/master/packages). This includes the [core Medusa package](https://github.com/medusajs/medusa/tree/master/packages/medusa), the [JS Client](https://github.com/medusajs/medusa/tree/master/packages/medusa-js), the CLI tools, and much more.
All the packages are part of a [Yarn workspace](https://classic.yarnpkg.com/lang/en/docs/workspaces/). So, when you run a command in the root of the project, such as `yarn build`, it goes through all registered packages in the workspace under the `packages` directory and runs the `build` command in each of those packages.
---
## Prerequisites
### Yarn
When using and developing with the Medusa repository, its highly recommended that you use [Yarn](https://yarnpkg.com/getting-started/install) to avoid any errors or issues.
### Fork and Clone Medusas Repository
To customize Medusas core or contribute to it, you must first [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) and then [clone](https://docs.github.com/en/get-started/quickstart/fork-a-repo#cloning-your-forked-repository) the [GitHub repository](https://github.com/medusajs/medusa).
### Install Dependencies and Build Packages
In the directory of the forked GitHub repository, run the following commands to install necessary dependencies then build all packages in the repository:
```bash
yarn install
yarn build
```
### Medusas Dev CLI tool
Medusa provides a CLI tool to be used for development. This tool facilitates testing your local installment and changes to Medusas core without having to publish the changes to NPM.
To install Medusas dev CLI tool:
```bash npm2yarn
npm install medusa-dev-cli -g
```
### Set the Location of the Medusa Repository
In the directory of your forked GitHub repository, run the following command to specify to the dev CLI tool the location of your Medusa repository:
```bash
medusa-dev --set-path-to-repo `pwd`
```
---
## Run Tests in the Repository
In this section, youll learn how to run tests in the Medusa repository. This is helpful after you customize any of Medusas packages and want to make sure everything is still working as expected.
### Set System Environment Variables
Before you can run the tests, make sure you set the following system environment variables:
```bash
DB_HOST=<YOUR_DB_HOST>
DB_USERNAME=<YOUR_DB_USERNAME>
DB_PASSWORD=<YOUR_PASSWORD>
```
### Run Unit Tests
To run unit tests in all packages in the Medusa repository, run the following command in the root directory of the repository:
```bash
yarn test
```
This runs the `test` script defined in the `package.json` file of each package under the `packages` directory.
Alternatively, if you want to run the unit tests in a specific package, you can run the `test` command in the directory of that package.
For example, to run the unit tests of the Medusa core:
```bash
cd packages/medusa
yarn test
```
### Run API Integration Tests
API integration tests are used to test out Medusas core endpoints.
To run the API integration tests, run the following command in the root directory of the repository:
```bash
yarn test:integration:api
```
### Run Plugin Integration Tests
Plugin integration tests are used to test out Medusas official plugins, which are also stored in the `packages` directory in the repository.
To run the plugin integration tests, run the following command in the root directory of the repository:
```bash
yarn test:integration:plugins
```
---
## Test in a Local Backend
Using Medusas dev CLI tool, you can test any changes you make to Medusas packages in a local backend installation. This eliminates the need to publish these packages on NPM publicly to be able to use them.
Medusas dev CLI tool scans and finds the Medusa packages used in your Medusa backend. Then, it copies the files of these packages from the `packages` directory in the Medusa repository into the `node_modules` directory of your Medusa backend.
:::info
Medusas Dev CLI tool uses the [path you specified earlier](#set-the-location-of-the-medusa-repository) to copy the files of the packages.
:::
### Copy Files to Local Backend
To test in a local backend:
1. Change to the directory of the backend you want to test your changes in:
```bash
cd medusa-backend
```
2\. Run the following command to copy the files from the `packages` directory of your Medusa repository into `node_modules`:
```bash
medusa-dev
```
By default, Medusas dev CLI runs in watch mode. So, it copies the files when you first run it. Then, whenever you make changes in the `dist` directory of the packages in the Medusa repository, it copies the changed files again.
### Watch and Compile Changes
While the above command is running, it's recommended to run the `watch` command inside the directory of every package you're making changes to.
The combination of these two commands running at the same time will compile the package into the `dist` directory of the package, then copy the compiled changes into your local backend.
For example, if you're making changes in the `medusa` package, run the following command inside the directory of the `medusa` package:
```bash title=packages/medusa
yarn watch
```
Make sure the `medusa-dev` command is also running to copy the changes automatically.
Alternatively, you can manually run the `build` command every time you want to compile the changes:
```bash title=packages/medusa
yarn build
```
### CLI Options
Here are some options you can use to customize how Medusas dev CLI tool works:
- `--scan-once` or `-s`: Copies files only one time then stops processing. If you make any changes after running the command with this option, you have to run the command again.
```bash
medusa-dev -s
```
- `--quiet` or `-q`: Disables showing any output.
```bash
medusa-dev -q
```
- `--packages`: Only copies specified packages. It accepts at least one package name. Package names are separated by a space.
```bash
medusa-dev --packages @medusajs/medusa-cli medusa-file-minio
```
---
## See Also
- [Create a Plugin](../plugins/create.mdx)
- [Contribution Guidelines](https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md)
@@ -0,0 +1,156 @@
---
description: 'Learn about the Transaction Orchestrator used in the core Medusa package. The transaction orchestrator (TO) offers an effective way of managing transactions within an increasingly complex environment.'
---
# Transaction Orchestrator
In this document, youll learn about the Transaction Orchestrator used in the core Medusa package.
## Introduction
The transaction orchestrator (TO) offers an effective way of managing transactions within an increasingly complex environment. It supports Medusas modularity and composability by handling transactions from different modules rather than one whole system.
Medusas core package uses the transaction orchestrator to enhance the control and management of transactions and workflows across multiple services or modules. It simplifies creating and executing distributed transactions.
The transaction orchestrator supervises transaction flows and guarantees that successful transactions are executed fully or entirely rolled back in case of failure. With clearly defined steps to Invoke and Compensate actions, the transaction orchestrator follows the separation of concerns principle, providing you with improved control over transactions and workflows.
---
## Why Medusa Uses the Transaction Orchestrator
The transaction orchestrator is a necessity for modular or distributed systems in scenarios where a given workflow involves different modules for several reasons:
1. **Data consistency:** In a distributed system, maintaining data consistency across different databases becomes challenging. A transaction orchestrator ensures that if any part of the transaction fails, all the previous steps are rolled back (compensated), keeping the data consistent across all involved databases.
2. **Coordination:** The transaction orchestrator acts as a coordinator between different modules and their respective servers. It also manages the order of execution and communication between modules, ensuring that each transaction step is executed correctly and at the right time.
3. **Simplifying complex workflows:** In a distributed environment, transactions can become complex due to the need to coordinate between different services and databases. A transaction orchestrator simplifies this process by abstracting away the complexities of managing distributed transactions, allowing developers to focus on implementing the business logic.
4. **Scalability:** As a system grows, managing transactions across multiple services becomes increasingly difficult. A transaction orchestrator helps with scalability by providing a robust framework for managing distributed transactions, making it easier to maintain and expand the system.
In addition to the above reasons, there are reasons more relevant in the context of digital commerce which makes it an important addition to Medusas toolbox. These reasons are:
- **Composable architectures:** A transaction orchestrator supports composable architectures, allowing developers to easily combine and reuse modules as needed. This enables the creation of highly customizable commerce applications, tailored to specific business requirements.
- **Adoption in legacy systems:** The transaction orchestrator can also facilitate the gradual transition of legacy systems to more modern, distributed architectures. This makes it easier for businesses to adopt and integrate new technologies without having to rebuild their entire infrastructure from scratch.
- **Unlocking infrastructure technologies:** With a transaction orchestrator, developers can leverage advanced infrastructure technologies, such as serverless and edge computing. This can lead to improved performance, reduced latency, and increased reliability for commerce applications, resulting in better user experiences and higher customer satisfaction.
---
## Example of Using the Transaction Orchestrator
To better illustrate how the transaction orchestrator works, heres an example of how its used in the multi-warehouse feature that coordinates the flow to create a product variant, creating an inventory item and finally linking both together:
```ts
const createVariantFlow: TransactionStepsDefinition = {
next: {
action: "createVariantStep",
saveResponse: true,
next: {
action: "createInventoryItemStep",
saveResponse: true,
next: {
action: "attachInventoryItemStep",
noCompensation: true,
},
},
},
}
```
The actions are handled by a single function called by the transaction orchestrator:
```ts
async function transactionHandler(
actionId: string,
type: TransactionHandlerType,
payload: TransactionPayload
) {
const command = {
createVariantStep: {
invoke: async (data: CreateProductVariantInput) => {
return await createProductVariant(data) // omitted
},
compensate: async (
data: CreateProductVariantInput,
{ invoke }
) => {
await removeProductVariant(
invoke.createVariantStep
) // omitted
},
},
createInventoryItemStep: {
invoke: async (
data: CreateProductVariantInput,
{ invoke }
) => {
return await createInventoryItem(
invoke.createVariantStep
) // omitted
},
compensate: async (
data: CreateProductVariantInput,
{ invoke }
) => {
await removeInventoryItem(
invoke.createInventoryItemStep
) // omitted
},
},
attachInventoryItemStep: {
invoke: async (
data: CreateProductVariantInput,
{ invoke }
) => {
return await attachInventoryItem( // omitted
invoke.createVariantStep,
invoke.createInventoryItemStep
)
},
},
}
return command[actionId][type](payload.data, payload.context)
}
```
Note that the implementation of each function was omitted to keep the example short; however, their names are self-explanatory.
Finally, the transaction orchestrator is instantiated and a new transaction initialized:
```ts
const strategy = new TransactionOrchestrator(
"create-variant-with-inventory", // transaction name
createVariantFlow // transaction steps definition
)
const transaction = await strategy.beginTransaction(
ulid(), // unique id
transactionHandler, // handler
createProductVariantInput // input
)
await strategy.resume(transaction)
```
---
## Achieving Cleaner Code with the Transaction Orchestrator
Utilizing a transaction orchestrator results in cleaner code and single-responsibility functions compared to manually managing distributed transactions. This is due to several factors:
1. **Abstraction:** A transaction orchestrator abstracts the complexity of managing distributed transactions by providing a standardized framework for defining transaction steps, their corresponding compensation actions, and the flow of execution.
2. **Separation of concerns:** By clearly delineating the responsibilities of each function, the transaction orchestrator enforces the separation of concerns. Each function is responsible for either the "Invoke" (execution) or "Compensate" (rollback) action, ensuring they perform a single, specific task. This makes the code more readable, maintainable, and testable.
3. **Modularity:** By using a transaction orchestrator, the code becomes more modular, as each step in the transaction is encapsulated within its own function. This allows developers to easily modify, add, or remove transaction steps without affecting the overall structure of the transaction.
4. **Error Handling:** When handling distributed transactions manually, the code can become convoluted due to intricate error handling logic. The transaction orchestrator simplifies this by automatically managing errors and retries, allowing developers to create cleaner code without the need to address error handling for each step.
5. **Reusability:** The transaction orchestrator allows you to define reusable functions for both the "Invoke" and "Compensate" actions, which can be easily reused across different transaction scenarios. This reduces code duplication and ensures that changes to a specific action only need to be made in one place.
---
## Handling Complex Workflows with the Transaction Orchestrator
The Transaction Orchestrator enables developers to create complex workflows for synchronous and long-running tasks that may take a while to receive a response (asynchronous). These workflows can be organized in a way that may not necessarily be transactional, but can still be effectively orchestrated.
There are several scenarios where asynchronous workflows with long-running steps can be applied using the transaction orchestrator:
1. **Order fulfillment:** In a commerce system, a workflow might involve creating an order, reserving inventory, charging the customer, generating shipping labels, and updating the shipping status. Some of these steps, such as generating shipping labels or charging the customer, could take a considerable amount of time due to external dependencies like payment gateways and shipping services.
2. **Fraud detection and prevention:** fraud detection and prevention workflows might involve analyzing customer data, order patterns, and payment information to identify potential fraudulent activities. These workflows may include time-consuming tasks like querying external fraud detection APIs or applying machine learning models to analyze data.
3. **Returns and refunds processing:** In a returns and refunds workflows, several steps may involve long-running tasks, such as receiving returned products, inspecting their condition, updating inventory, and processing refunds. Some of these steps may take a considerable amount of time, especially when dealing with external payment gateways or waiting for products to be returned and inspected.
In all these examples, workflows are valuable for handling intricate, multistep processes that include tasks taking considerable time or involving external dependencies without immediate responses.