Explorar o código

docs: Update docs relating to worker & job queue

Michael Bromley %!s(int64=4) %!d(string=hai) anos
pai
achega
8ed48245d6

+ 1 - 1
docs/content/docs/developer-guide/configuration.md

@@ -155,7 +155,7 @@ export const config: VendureConfig = {
 
 ### Connecting to the worker
 
-The Vendure worker is configured with [`VendureConfig.workerOptions`]({{< relref "worker-options" >}}). The worker is a [Nestjs microservice](https://docs.nestjs.com/microservices/basics) which runs in another process (and can be located on another server or in another container) from the main server.
+The Vendure worker is configured with [`VendureConfig.workerOptions`]. The worker is a [Nestjs microservice](https://docs.nestjs.com/microservices/basics) which runs in another process (and can be located on another server or in another container) from the main server.
 
 By default, the worker communication happens over TCP. If you want to run the worker in a separate container or physical server to the server, please see the [deployment guide]({{< relref "deployment" >}}#deploying-the-worker).
 

+ 14 - 8
docs/content/docs/developer-guide/job-queue/index.md

@@ -24,22 +24,28 @@ Some operations however will need to perform much longer-running tasks. For exam
 -   Updating the contents of Collections
 -   Sending transactional emails
 
-## Job Queue persistence
+## How does the Job Queue work?
 
-When a job is added to the queue, that fact must be persisted to some kind of storage. In Vendure, the storage mechanism is defined by the [JobQueueStrategy]({{< relref "job-queue-strategy" >}}).
+This diagram illustrates the job queue mechanism:
 
-By default, Vendure uses an [in-memory store]({{< relref "in-memory-job-queue-strategy" >}}) of the contents of each queue. While this has the advantage of requiring no external dependencies, it is not suitable for production because when the server is stopped, the entire queue will be lost and any pending jobs will never be processed.
+{{< figure src="./job_queue_sequence.png" >}}
 
-A better alternative is to use the [DefaultJobQueuePlugin]({{< relref "default-job-queue-plugin" >}}), which configures Vendure to use the [SqlJobQueueStrategy]({{< relref "sql-job-queue-strategy" >}}). This means that event if the Vendure server stops, pending jobs will be persisted and upon re-start, they will be processed.
+The server adds jobs to the queue. The worker then picks up these jobs from the queue and processes them in sequence, one by one (it is possible to increase job queue throughput by [running multiple workers]({{< relref "vendure-worker" >}}#multiple-workers)).
 
-It is also possible to implement your own JobQueueStrategy to enable other persistence mechanisms, e.g. Redis.
+### JobQueueStrategy
+
+The actual queue part is defined by the configured [JobQueueStrategy]({{< relref "job-queue-strategy" >}}).
+
+If no strategy is defined, Vendure uses an [in-memory store]({{< relref "in-memory-job-queue-strategy" >}}) of the contents of each queue. While this has the advantage of requiring no external dependencies, it is not suitable for production because when the server is stopped, the entire queue will be lost and any pending jobs will never be processed. Moreover, it cannot be used when running the worker as a separate process.
+
+A better alternative is to use the [DefaultJobQueuePlugin]({{< relref "default-job-queue-plugin" >}}) (which will be used in a standard `@vendure/create` installation), which configures Vendure to use the [SqlJobQueueStrategy]({{< relref "sql-job-queue-strategy" >}}). This strategy uses the database as a queue, and means that event if the Vendure server stops, pending jobs will be persisted and upon re-start, they will be processed.
+
+It is also possible to implement your own JobQueueStrategy to take advantage of other technologies. Examples include Redis, RabbitMQ, Google Cloud Pub Sub & Amazon SQS. It may make sense to implement a custom strategy based on one of these if the default database-based approach does not meet your performance requirements.
 
 ## Using Job Queues in a plugin
 
 If you create a [Vendure plugin]({{< relref "/docs/plugins" >}}) which involves some long-running tasks, you can also make use of the job queue. See the [JobQueue plugin example]({{< relref "using-job-queue-service" >}}) for a detailed annotated example.
 
 {{< alert "primary" >}}
-Note: The [JobQueueService]({{< relref "job-queue-service" >}}) combines well with the [WorkerService]({{< relref "worker-service" >}}).
-
-A real example of this can be seen in the [EmailPlugin source](https://github.com/vendure-ecommerce/vendure/blob/07e1958f1ad1766e6fd3dae80f526bb688c0288e/packages/email-plugin/src/plugin.ts#L201-L210)
+A real example of this can be seen in the [EmailPlugin source](https://github.com/vendure-ecommerce/vendure/blob/master/packages/email-plugin/src/plugin.ts)
 {{< /alert >}}

BIN=BIN
docs/content/docs/developer-guide/job-queue/job_queue_sequence.png


+ 1 - 5
docs/content/docs/developer-guide/overview/_index.md

@@ -13,7 +13,7 @@ Here is a simplified diagram of the Vendure application architecture:
 
 ## Entry Points
 
-As you can see in the diagram, there are two entry points into the application: [`bootstrap()`]({{< relref "bootstrap" >}}) and [`bootstrapWorker()`]({{< relref "bootstrap-worker" >}}), which start the main server and the [worker]({{< relref "vendure-worker" >}}) respectively.
+As you can see in the diagram, there are two entry points into the application: [`bootstrap()`]({{< relref "bootstrap" >}}) and [`bootstrapWorker()`]({{< relref "bootstrap-worker" >}}), which start the main server and the [worker]({{< relref "vendure-worker" >}}) respectively. Communication between server and worker(s) is done via the [Job Queue]({{< relref "/docs/developer-guide/job-queue" >}}).
 
 ## GraphQL APIs
 
@@ -26,10 +26,6 @@ There are 2 separate GraphQL APIs: shop and admin.
 
     [Admin API Documentation]({{< relref "/docs/graphql-api/admin" >}}).
 
-## ServiceModule
-
-This is an internal module which contains the bulk of the Vendure business logic for managing products, customers, orders, collections etc.
-
 ## Database
 
 Vendure supports multiple databases. Currently it is tested with MySQL/MariaDB, PostgreSQL, SQLite and SQL.js. Since Vendure uses [TypeORM](https://typeorm.io/#/) to manage data access, it can theoretically also work with CockroachDB, Microsoft SQL Server and MongoDB, though these are as yet untested.

BIN=BIN
docs/content/docs/developer-guide/overview/vendure_architecture.png


+ 0 - 1
docs/content/docs/developer-guide/updating-vendure.md

@@ -43,7 +43,6 @@ In practice, we aim to introduce breaking changes in **minor versions** and non-
 * Changes to the database schema
 * Changes to the GraphQL schema
 * Updates of major underlying libraries, such as upgrading NestJS to a new major version
-* Changes to certain core services which are often used in plugins, such as the JobQueue or WorkerService providers.
 
 Every release will be accompanied by an entry in the [changelog](https://github.com/vendure-ecommerce/vendure/blob/master/CHANGELOG.md), listing the changes in that release. And breaking changes are clearly listed under a **BREAKING CHANGE** heading.
 

+ 51 - 25
docs/content/docs/developer-guide/vendure-worker.md

@@ -5,44 +5,70 @@ showtoc: true
 
 # Vendure Worker
  
-The Vendure Worker is a process which is responsible for running computationally intensive or otherwise long-running tasks in the background. For example updating a search index or performing image transformations.
+The Vendure Worker is a process which is responsible for running computationally intensive or otherwise long-running tasks in the background. For example updating a search index or sending emails. Running such tasks in the background allows the server to stay responsive, since a response can be returned immediately without waiting for the slower tasks to complete. 
+
+Put another way, the Worker executes jobs registered with the [JobQueueService]({{< relref "job-queue-service" >}}).
 
 The worker is started by calling the [`bootstrapWorker()`]({{< relref "bootstrap-worker" >}}) function with the same configuration as is passed to the main server `bootstrap()`:
 
 ```TypeScript
-import { Transport } from '@nestjs/microservices';
-import { bootstrapWorker, VendureConfig } from '@vendure/core';
-
-const config: VendureConfig = {
-    // ...
-    workerOptions: {
-        transport: Transport.TCP,
-        options: {
-            host: 'localhost',
-            port: 4242,
-        },
-    },
-};
-
-bootstrapWorker(config).catch(err => {
+import { bootstrapWorker } from '@vendure/core';
+import { config } from './vendure-config';
+
+bootstrapWorker(config)
+  // We must explicitly start the job queue in order for this
+  // worker instance to start listening for and processing jobs.
+  .then(worker => worker.startJobQueue())
+  .catch(err => {
     console.log(err);
-});
+    process.exit(1);
+  });
 ```
 
 ## Underlying architecture
 
-Internally, the Worker is an instance of a [NestJS microservice](https://docs.nestjs.com/microservices/basics). By default the TCP protocol is used to send messages to and receive reponses from the Worker, but other transports may be used such as Redis or gRPC.
+The Worker is a [NestJS standalone application](https://docs.nestjs.com/standalone-applications). This means it is almost identical to the main server app, but does not have any network layer listening for requests. The server communicates with the worker via a "job queue" architecture. The exact implementation of the job queue is dependent on the configured `JobQueueStrategy` - see the [Job Queue guide]({{< relref "job-queue" >}}) for more details.
+
+## Multiple workers
+
+It is possible to run multiple workers in parallel, in order to better handle heavy loads. Using the [JobQueueOptions.activeQueues]({{< relref "job-queue-options" >}}#activequeues) configuration, it is even possible to have particular workers dedicated to one or more specific type of job.
+For example, if you application does video transcoding, you might want to set up a dedicated worker just for that task:
+
+```TypeScript
+import { bootstrapWorker, mergeConfig } from '@vendure/core';
+import { config } from './vendure-config';
+
+const videoWorkerConfig = mergeConfig(config, {
+  jobQueueOptions: {
+    activeQueues: ['transcode-video'],
+  }
+})
+
+bootstrapWorker(videoWorkerConfig)
+  .then(worker => worker.startJobQueue())
+  .catch(err => {
+    console.log(err);
+    process.exit(1);
+  });
+```
+
+## Running jobs on the main process
+
+It is possible to run jobs from the Job Queue on the main server. This is mainly used for testing and automated tasks, and is not advised for production use, since it negates the benefits of running long tasks off of the main process. To do so, you need to manually start the JobQueueService:
 
-## Running on the main process
+```TypeScript
+import { bootstrap, JobQueueService } from '@vendure/core';
+import { config } from './vendure-config';
 
-There is an option `runInMainProcess` which, if set to `true` will cause the Worker to be bootstrapped along with the main application, without the need for a separate process running `bootstrapWorker`. This is mainly used for testing and automated tasks, and is not advised for production use, since it negates the benefits of running long tasks off of the main process.
+bootstrap(config)
+  .then(app => app.get(JobQueueService).start())
+  .catch(err => {
+    console.log(err);
+    process.exit(1);
+  });
+```
 
 ## Running custom code on the worker
 
 If you are authoring a [Vendure plugin]({{< relref "/docs/plugins" >}}) to implement custom functionality, you can also make use of the worker process in order to handle long-running or computationally-demanding tasks. See the [Plugin Examples]({{< relref "plugin-examples" >}}#running-processes-on-the-worker) page for an example of this.
 
-{{< alert "primary" >}}
-Note: The [WorkerService]({{< relref "worker-service" >}}) combines well with the [JobQueueService]({{< relref "job-queue-service" >}}).
-
-A real example of this can be seen in the [EmailPlugin source](https://github.com/vendure-ecommerce/vendure/blob/07e1958f1ad1766e6fd3dae80f526bb688c0288e/packages/email-plugin/src/plugin.ts#L201-L210)
-{{< /alert >}}

+ 7 - 7
docs/content/docs/plugins/plugin-examples/adding-rest-endpoint.md

@@ -13,12 +13,12 @@ import { Ctx, ProductService, RequestContext } from '@vendure/core';
 
 @Controller('products')
 export class ProductsController {
-    constructor(private productService: ProductService) {}
+  constructor(private productService: ProductService) {}
 
-    @Get()
-    findAll(@Ctx() ctx: RequestContext) {
-        return this.productService.findAll(ctx);
-    }
+  @Get()
+  findAll(@Ctx() ctx: RequestContext) {
+    return this.productService.findAll(ctx);
+  }
 }
 ```
 ```TypeScript
@@ -27,8 +27,8 @@ import { PluginCommonModule, VendurePlugin } from '@vendure/core';
 import { ProductsController } from './products.controller';
 
 @VendurePlugin({
-    imports: [PluginCommonModule],
-    controllers: [ProductsController],
+  imports: [PluginCommonModule],
+  controllers: [ProductsController],
 })
 export class RestPlugin {}
 ```

+ 0 - 81
docs/content/docs/plugins/plugin-examples/running-on-worker-process.md

@@ -1,81 +0,0 @@
----
-title: "Running processes on the Worker"
-weight: 4
-showtoc: true
----
-
-# Running processes on the Worker
-
-This example shows how to set up a microservice running on the Worker process, as well as subscribing to events via the [EventBus]({{< relref "event-bus" >}}).
-
-Also see the docs for [WorkerService]({{< relref "worker-service" >}}).
-
-```TypeScript
-// order-processing.controller.ts
-import { asyncObservable, ID, Order, TransactionalConnection } from '@vendure/core';
-import { Controller } from '@nestjs/common';
-import { MessagePattern } from '@nestjs/microservices';
-import { ProcessOrderMessage } from './process-order-message';
-
-@Controller()
-class OrderProcessingController {
-
-  constructor(private connection: TransactionalConnection) {}
-
-  @MessagePattern(ProcessOrderMessage.pattern)
-  async processOrder({ orderId }: ProcessOrderMessage['data']) {
-    const order = await this.connection.getRepository(Order).findOne(orderId);
-    // ...do some expensive / slow computation
-    return true;
-  }
-
-}
-```
-* This controller will be executed as a microservice in the [Vendure worker process]({{< relref "vendure-worker" >}}). This makes it suitable for long-running or resource-intensive tasks that you do not want to interfere with the main process which is handling GraphQL API requests.
-* Messages are sent to the worker using [WorkerMessages]({{< relref "worker-message" >}}), each of which has a unique pattern and can include a payload of data sent from the main process.
-* The return value of the method should correspond to the return type of the WorkerMessage (the second generic argument, `boolean` in the case of `ProcessOrderMessage` - see next snippet)
-
-```TypeScript
-// process-order-message.ts
-import { ID, WorkerMessage } from '@vendure/core';
-
-export class ProcessOrderMessage extends WorkerMessage<{ orderId: ID }, boolean> {
-  static readonly pattern = 'ProcessOrder';
-}
-```
-
-The `ProcessOrderMessage` is sent in response to certain events:
-
-```TypeScript
-import { OnVendureBootstrap, OrderStateTransitionEvent, PluginCommonModule, 
-  VendurePlugin, WorkerService, EventBus } from '@vendure/core';
-import { OrderProcessingController } from './process-order.controller';
-import { ProcessOrderMessage } from './process-order-message';
-
-@VendurePlugin({
-  imports: [PluginCommonModule],
-  workers: [OrderProcessingController],
-})
-export class OrderAnalyticsPlugin implements OnVendureBootstrap {
-
-  constructor(
-    private workerService: WorkerService,
-    private eventBus: EventBus,
-  ) {}
-  
-  /**
-   * When the server bootstraps, set up a subscription for events 
-   * published whenever  an Order changes state. When an Order has 
-   * been fulfilled, we send a message to the controller running on
-   * the Worker process to let it process that order.
-   */
-  onVendureBootstrap() {
-    this.eventBus.ofType(OrderStateTransitionEvent).subscribe(event => {
-      if (event.toState === 'Delivered') {
-        this.workerService.send(new ProcessOrderMessage({ orderId: event.order.id })).subscribe();
-      }
-    });
-  }
-
-}
-```

+ 30 - 35
docs/content/docs/plugins/plugin-examples/using-job-queue-service.md

@@ -1,10 +1,10 @@
 ---
-title: "Using the JobQueueService"
+title: "Using the Job Queue"
 weight: 4
 showtoc: true
 ---
 
-# Using the JobQueueService
+# Using the Job Queue
 
 If your plugin involves long-running tasks, you can take advantage of the [job queue system]({{< relref "/docs/developer-guide/job-queue" >}}) that comes with Vendure. This example defines a mutation that can be used to transcode and link a video to a Product's customFields.
 
@@ -20,7 +20,7 @@ class ProductVideoResolver {
   constructor(private productVideoService: ProductVideoService) {}
 
   @Mutation()
-  addVideoToProduct(@Args() args: any) {
+  addVideoToProduct(@Args() args: { productId: ID; videoUrl: string; }) {
     return this.productVideoService.transcodeForProduct(
       args.productId, 
       args.videoUrl,
@@ -34,50 +34,45 @@ The resolver just defines how to handle the new `addVideoToProduct` mutation, de
 // product-video.service.ts
 import { Injectable, OnModuleInit } from '@nestjs/common';
 import { JobQueue, JobQueueService, ID, Product, TransactionalConnection } from '@vendure/core';
-import { transcode } from 'third-party-video-sdk'; 
-
-let jobQueue: JobQueue<{ productId: ID; videoUrl: string; }>;
+import { transcode } from 'third-party-video-sdk';
 
 @Injectable()
 class ProductVideoService implements OnModuleInit { 
+    
+  private jobQueue: JobQueue<{ productId: ID; videoUrl: string; }>;
   
-  constructor(private jobQueueService: JobQueueService, 
+  constructor(private jobQueueService: JobQueueService,
               private connection: TransactionalConnection) {}
 
   onModuleInit() {
-    // This check ensures that only a single JobQueue is created, even if this
-    // service gets instantiated more than once.
-    if (!jobQueue) {
-      jobQueue = this.jobQueueService.createQueue({
-        name: 'transcode-video',
-        concurrency: 5,
-        process: async job => {
-          // Here we define how each job in the queue will be processed.
-          // In this case we call out to some imaginary 3rd-party video
-          // transcoding API, which performs the work and then
-          // returns a new URL of the transcoded video, which we can then
-          // associate with the Product via the customFields.
-          try {
-            const result = await transcode(job.data.videoId);
-            await this.connection.getRepository(Product).save({
-              id: job.data.productId,
-              customFields: {
-                videoUrl: result.url,
-              },
-            });
-            job.complete(result);
-          } catch (e) {
-            job.fail(e);
-          }
-        },
-      });
-    }
+    this.jobQueue = this.jobQueueService.createQueue({
+      name: 'transcode-video',
+      process: async job => {
+        // Here we define how each job in the queue will be processed.
+        // In this case we call out to some imaginary 3rd-party video
+        // transcoding API, which performs the work and then
+        // returns a new URL of the transcoded video, which we can then
+        // associate with the Product via the customFields.
+        const result = await transcode(job.data.videoUrl);
+        await this.connection.getRepository(Product).save({
+          id: job.data.productId,
+          customFields: {
+            videoUrl: result.url,
+          },
+        });
+        // The value returned from the `process` function is stored as the "result"
+        // field of the job (for those JobQueueStrategies that support recording of results).
+        //  
+        // Any error thrown from this function will cause the job to fail.  
+        return result
+      },
+    });
   }
 
   transcodeForProduct(productId: ID, videoUrl: string) { 
     // Add a new job to the queue and immediately return the
     // job itself.
-    return jobQueue.add({ productId, videoUrl });
+    return this.jobQueue.add({ productId, videoUrl }, { retries: 2 });
   }
 }
 ```

+ 28 - 41
docs/content/docs/plugins/plugin-lifecycle/index.md

@@ -5,60 +5,47 @@ weight: 3
 
 # Plugin Lifecycle
 
-A VendurePlugin can make use of a number pre-defined "hooks", which allow the plugin to execute code at specific points during the lifecycle of the application. 
+Since a VendurePlugin is built on top of the Nestjs module system, any plugin (as well as any providers it defines) can make use of any of the [Nestjs lifecycle hooks](https://docs.nestjs.com/fundamentals/lifecycle-events):
 
-## Nestjs Lifecycle Hooks
+* onModuleInit
+* onApplicationBootstrap
+* onModuleDestroy
+* beforeApplicationShutdown
+* onApplicationShutdown
 
-Since a VendurePlugin is built on top of the Nestjs module system, any plugin can make use of any of the [Nestjs lifecycle hooks](https://docs.nestjs.com/fundamentals/lifecycle-events).
+Note that lifecycle hooks are run in _both_ the server and worker contexts.
 
-{{< alert "warning" >}}
-Note that since a Plugin will typically be instantiated in _both_ the main process _and_ the worker, any logic defined in a Nestjs lifecycle hook will be executed **twice**. 
+## Configure
 
-To run logic _only_ in the main process or worker process, use the Vendure-specific hooks below, or inject the [`ProcessContext`]({{< relref "process-context" >}}) provider.
-{{< /alert >}}
+Another hook which is not strictly a lifecycle hook, but which can be useful to know is the [`configure` method](https://docs.nestjs.com/middleware#applying-middleware) which is used by NestJS to apply middleware. This method is called _only_ for the server and _not_ for the worker, since middleware relates to the network stack, and the worker has no network part.
 
-## Vendure-specific hooks
-
-Vendure defines some additional hooks which allow you to target logic depending on the context (main process or worker), as well as letting you run code _prior_ to the bootstrap process.
-
-The available Vendure-specific lifecycle hooks are:
-
-* [`beforeVendureBootstrap`]({{< relref "plugin-lifecycle-methods" >}}#beforevendurebootstrap) (static method)
-* [`beforeVendureWorkerBootstrap`]({{< relref "plugin-lifecycle-methods" >}}#beforevendureworkerbootstrap) (static method)
-* [`onVendureBootstrap`]({{< relref "plugin-lifecycle-methods" >}}#onvendurebootstrap)
-* [`onVendureWorkerBootstrap`]({{< relref "plugin-lifecycle-methods" >}}#onvendureworkerbootstrap)
-* [`onVendureClose`]({{< relref "plugin-lifecycle-methods" >}}#onvendureclose)
-* [`onVendureWorkerClose`]({{< relref "plugin-lifecycle-methods" >}}#onvendureworkerclose)
-
-### Example 
+## Example
 
 ```TypeScript
-import { OnVendureBootstrap, VendurePlugin } from '@vendure/core';
+import { MiddlewareConsumer, NestModule, OnApplicationBootstrap } from '@nestjs/common';
+import { EventBus, PluginCommonModule, VendurePlugin } from '@vendure/core';
 
 @VendurePlugin({
-  // ...
+    imports: [PluginCommonModule]
 })
-export class MyPlugin implements OnVendureBootstrap {
+export class MyPlugin implements OnApplicationBootstrap, NestModule {
+
+  constructor(private eventBus: EventBus) {}
 
-  static beforeVendureBootstrap(app) {
-    // An example use-case for this hook is to add 
-    // global middleware as described in 
-    // https://docs.nestjs.com/middleware#global-middleware
-    app.use(/* ... */);
+  configure(consumer: MiddlewareConsumer) {
+    consumer
+      .apply(MyMiddleware)
+      .forRoutes('my-custom-route');
   }
-  
-  async onVendureBootstrap() {
-    // setup logic here. If retuning a Promise, 
-    // app initialization will wait until the Promise resolves.
+
+  async onApplicationBootstrap() {
     await myAsyncInitFunction();
-  }
 
+    this.eventBus
+      .ofType(OrderStateTransitionEvent)
+      .subscribe((event) => {
+        // do some action when this event fires
+      });
+  }
 }
 ```
-
-## Full Lifecycle Diagram
-
-This diagram illustrates how the Nestjs & Vendure lifecycle hooks relate to one another:
-
-{{< figure src="./plugin_lifecycle.png" >}}
-

+ 3 - 3
docs/data/build.json

@@ -1,4 +1,4 @@
 {
-  "version": "0.11.0",
-  "commit": "0cf71174"
-}
+  "version": "0.18.5",
+  "commit": "2073e7cb"
+}

+ 12 - 0
docs/diagrams/job-queue.puml

@@ -0,0 +1,12 @@
+@startuml
+!include theme.puml
+hide footbox
+title Job Queue Sequence
+
+"Vendure Server" -> "Job Queue": JobQueue.add() Job 1
+"Vendure Server" -> "Job Queue": JobQueue.add() Job 2
+"Job Queue" -> "Worker": process() Job 1
+...
+"Job Queue" <-- "Worker": Job 1 complete
+"Job Queue" -> "Worker": process() Job 2
+@enduml

+ 1 - 1
docs/diagrams/theme.puml

@@ -15,7 +15,7 @@ skinparam TitleFontSize 18
 skinparam Padding 3
 
 skinparam Default {
-  FontName  'Impact'
+  FontName  'Segoe UI'
   FontColor BLACK
   FontSize  14
   FontStyle plain

+ 13 - 17
docs/diagrams/vendure-architecture.puml

@@ -6,27 +6,24 @@ interface "." as bootstrap
 interface "." as bootstrapWorker
 
 
-package "@vendure/core" {
-    component VendureServer <<Vendure Server>> [
-        AppModule
-    ]
-    component VendureWorker <<Vendure Worker>> [
-        WorkerModule
-    ]
-    [ServiceModule] #efefef
-    [ApiModule] #efefef
-}
+component VendureServer
+component VendureWorker
 
 cloud "GraphQL API" {
     [Shop API] #ff88c1
     [Admin API] #ff88c1
 }
 
+cloud {
+  [Job Queue] #f3fff3
+}
+
 database "Database" {
 component dbTypes #f3fff3 [
 MySQL/MariaDB
 PostgreSQL
 SQLite
+...
 ]
 }
 
@@ -35,12 +32,11 @@ note top of bootstrap : bootstrap()
 note top of bootstrapWorker : bootstrapWorker()
 bootstrap -down- [VendureServer]
 bootstrapWorker -down- [VendureWorker]
-[VendureServer] ---> [ServiceModule]
-[VendureServer] --> [ApiModule]
-[VendureWorker] --> [ServiceModule]
-[VendureWorker] <.left.> [VendureServer]
-[ApiModule] <...> [Shop API]
-[ApiModule] <...> [Admin API]
-[ServiceModule] <..> dbTypes
+[VendureServer] <.right.> [Shop API]
+[VendureServer] <.> [Admin API]
+[VendureServer] <..> dbTypes
+[VendureWorker] <..> dbTypes
+[VendureServer] ..> [Job Queue]
+[Job Queue] .right.> [VendureWorker]
 
 @enduml