diff --git a/.github/workflows/PR-SAP.yml b/.github/workflows/PR-SAP.yml index a8768e7855..ac83cae599 100644 --- a/.github/workflows/PR-SAP.yml +++ b/.github/workflows/PR-SAP.yml @@ -18,7 +18,7 @@ jobs: run: | git config --global credential.helper "cache --timeout=3600" echo -e "url=https://user:${GH_TOKEN}@github.com\n" | git credential approve - echo -e "url=https://user:${GH_TOKEN_PARENT}@github.tools.sap\n" | git credential approve + echo -e "url=https://user:${GH_TOKEN_TOOLS_DOCS}@github.tools.sap\n" | git credential approve git clone --depth 1 --no-single-branch https://github.tools.sap/cap/docs docs cd docs git checkout $GITHUB_HEAD_REF || git checkout main @@ -27,7 +27,7 @@ jobs: git checkout $GITHUB_HEAD_REF env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_TOKEN_PARENT: ${{ secrets.GH_TOKEN_PARENT }} + GH_TOKEN_TOOLS_DOCS: ${{ secrets.GH_TOKEN_TOOLS_DOCS }} - name: Use Node.js uses: actions/setup-node@v4 with: @@ -44,6 +44,7 @@ jobs: working-directory: docs env: NODE_OPTIONS: "--max-old-space-size=6144" + VITE_CAPIRE_CI_HOST: "github.com" - name: Find broken anchor links working-directory: docs run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d8f6960af4..1770a1baa9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: run: | git config --global credential.helper "cache --timeout=3600" echo -e "url=https://user:${GH_TOKEN}@github.com\n" | git credential approve - echo -e "url=https://user:${GH_TOKEN_PARENT}@github.tools.sap\n" | git credential approve + echo -e "url=https://user:${GH_TOKEN_TOOLS_DOCS}@github.tools.sap\n" | git credential approve git clone --depth 1 --no-single-branch https://github.tools.sap/cap/docs docs cd docs git checkout $GITHUB_HEAD_REF || git checkout main @@ -28,7 +28,7 @@ jobs: git checkout $GITHUB_HEAD_REF env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_TOKEN_PARENT: ${{ secrets.GH_TOKEN_PARENT }} + GH_TOKEN_TOOLS_DOCS: ${{ secrets.GH_TOKEN_TOOLS_DOCS }} - name: Use Node.js uses: actions/setup-node@v4 with: diff --git a/.vitepress/config.js b/.vitepress/config.js index fb557c0481..2bff0cd425 100644 --- a/.vitepress/config.js +++ b/.vitepress/config.js @@ -70,7 +70,7 @@ const config = defineConfig({ vite: { build: { - chunkSizeWarningLimit: 5000, // chunk for local search index dominates + chunkSizeWarningLimit: 6000, // chunk for local search index dominates }, css: { preprocessorOptions: { @@ -106,8 +106,8 @@ config.rewrites = rewrites // Add custom capire info to the theme config config.themeConfig.capire = { versions: { - java_services: '3.9.0', - java_cds4j: '3.9.0' + java_services: '3.10.1', + java_cds4j: '3.10.1' }, gotoLinks: [], maven_host_base: 'https://repo1.maven.org/maven2' diff --git a/.vitepress/theme/styles.scss b/.vitepress/theme/styles.scss index 757e9db260..64d5365ab9 100644 --- a/.vitepress/theme/styles.scss +++ b/.vitepress/theme/styles.scss @@ -144,9 +144,7 @@ main { table { tr, th, td { - // &:nth-child(2n) { - background-color: transparent !important; - // } + background-color: var(--vp-c-bg); } td, th { border-color: #bbb; .dark & { border-color: #555 }; @@ -520,11 +518,6 @@ a.learn-more, p.learn-more, .learn-more { .VPBadge { white-space: nowrap; } -/* remove transparency in the odd rows (when it scrolls over outline) */ -tr:nth-child(odd) { - background-color: var(--vp-c-bg); -} - html.java { & .node { display: none; @@ -550,6 +543,10 @@ html.node { table:focus { min-width: fit-content; } + tr { // make wide rows go over outline, not below it + z-index: 1; + position: relative; + } [class*='language-'] pre { overflow: hidden !important; } diff --git a/advanced/fiori.md b/advanced/fiori.md index 2cc613ace0..ce24e29cd5 100644 --- a/advanced/fiori.md +++ b/advanced/fiori.md @@ -25,63 +25,35 @@ This guide explains how to add one or more SAP Fiori elements apps to a CAP proj For entities exposed via OData V4 there is a _Fiori preview_ link on the index page. It dynamically serves an SAP Fiori Elements list page that allows you to quickly see the effect of annotation changes without having to create a UI application first. -
- ::: details Be aware that this is **not meant for production**. +
+ The preview not meant as a replacement for a proper SAP Fiori Elements (UI5) application. It is only active locally where the [development profile](../node.js/cds-env#profiles) is enabled. -To also enable it in cloud deployments, for test or demo purposes maybe, add the following configuration: - -::: code-group - -```json [package.json] -{ - "cds": { - "features": { - "fiori_preview": true - } - } -} -``` -```json [.cdsrc.json] -{ - "features": { - "fiori_preview": true - } -} -``` - -::: +To also enable it in cloud deployments, for test or demo purposes maybe, set cds.fiori.preview:true.
-::: details Be aware that this is **not meant for production**. - The preview not meant as a replacement for a proper SAP Fiori Elements (UI5) application. It is active by default, but disabled automatically in case the [production profile](../java/developing-applications/configuring#production-profile) is enabled. -To also enable it in cloud deployments, for test or demo purposes maybe, add the following configuration: +To also enable it in cloud deployments, for test or demo purposes maybe, set cds.index-page.enabled:true. + +
-::: code-group -```yaml [srv/src/main/resources/application.yaml] -cds: - index-page: - enabled: true -``` ::: -
## Adding Fiori Apps As showcased in [cap/samples](https://github.com/sap-samples/cloud-cap-samples/tree/main/fiori/app), SAP Fiori apps should be added as sub folders to the `app/` of a CAP project. Each sub folder constitutes an individual SAP Fiori application, with [local annotations](#fiori-annotations), _manifest.json_, etc. So, a typical folder layout would look like this: -| Folder/Sub Folder | Description | +| Folder/Sub Folder | Description | |----------------------------|--------------------------------------| | `app/` | All SAP Fiori apps should go in here | |     `browse/` | SAP Fiori app for end users | @@ -97,9 +69,12 @@ Links to Fiori applications created in the `app/` folder are automatically added ### Using SAP Fiori Tools -The SAP Fiori tools provide advanced support for adding SAP Fiori apps to existing CAP projects as well as a wealth of productivity tools, for example for adding SAP Fiori annotations, or graphical modeling and editing. They can be used locally in [Visual Studio Code (VS Code)](https://marketplace.visualstudio.com/items?itemName=SAPSE.sap-ux-fiori-tools-extension-pack) or in [SAP Business Application Studio](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/b0110400b44748d7b844bb5977a657fa.html). +The SAP Fiori tools provide advanced support for [adding SAP Fiori apps](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/db44d45051794d778f1dd50def0fa267.html) to existing CAP projects as well as a wealth of productivity tools, for example for adding SAP Fiori annotations, or graphical modeling and editing. They can be used locally in [Visual Studio Code (VS Code)](https://marketplace.visualstudio.com/items?itemName=SAPSE.sap-ux-fiori-tools-extension-pack) or in [SAP Business Application Studio](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/b0110400b44748d7b844bb5977a657fa.html). + -[Learn more about **how to install SAP Fiori tools**.](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/2d8b1cb11f6541e5ab16f05461c64201.html){.learn-more} +### Using [`cds add`](../tools/cds-cli#sample) + +Use `cds add sample` to add Fiori sample code to an existing project, or create a new one with `cds init --add sample`. ### From [cap/samples](https://github.com/sap-samples/cloud-cap-samples) @@ -152,14 +127,14 @@ While CDS in principle allows you to add such annotations everywhere in your mod fiori-service.cds #> annotating ../srv/admin-service.cds ./browse fiori-service.cds #> annotating ../srv/cat-service.cds - index.cds + services.cds #> imports ./admin/fiori-service and ./browse/fiori-service ./srv #> all service definitions should stay clean in here: admin-service.cds cat-service.cds ... ``` -[See this also in **cap/samples/fiori**.](https://github.com/sap-samples/cloud-cap-samples/tree/main/fiori/app){.learn-more} +[See this also in **cap/samples/fiori**.](https://github.com/SAP-samples/cloud-cap-samples/blob/6fa2aaee34e862337c5bc5a413817355ab283437/fiori/app/services.cds){.learn-more} **Reasoning:** This recommendation essentially follows the best practices and guiding principles of [Conceptual Modeling](../guides/domain-modeling#domain-driven-design) and [Separation of Concerns](../guides/domain-modeling#separation-of-concerns). @@ -187,7 +162,7 @@ The CDS OData Language Server provides a list of context-sensitive suggestions b #### Using Code Completion -To trigger code completion, choose + (macOS) or Ctrl + (other platforms). The list of suggested values is displayed. +To trigger code completion, choose CtrlSpace. The list of suggested values is displayed. > Note: You can filter the list of suggested values by typing more characters. @@ -322,7 +297,7 @@ You can navigate to the referenced annotation using the [Peek Definition](#peek- #### Peek Definition { #peek-definition} Peek Definition lets you preview and update the referenced annotation without switching away from the code that you're writing. It's triggered when your cursor is inside the referenced annotation value. -- Using a keyboard: choose + F12 (macOS) or Alt + F12 (other platforms) +- Using a keyboard: choose F12 (macOS) or AltF12 (other platforms) - Using a mouse: right-click and select **Peek Definition** If an annotation is defined in multiple sources, all these sources are listed. You can select which one you want to view or update. Annotation layering isn't considered. @@ -333,7 +308,7 @@ Go To Definition lets you navigate to the source of the referenced annotation an Place your cursor inside the path referencing the annotation term segment or translatable string value, and trigger Go to Definition: -- Using a keyboard: choose F12 in VS Code, or Ctrl + F12 in SAP Business Application Studio +- Using a keyboard: choose F12 in VS Code, or CtrlF12 in SAP Business Application Studio - Using a mouse: right-click and select **Go To Definition** - Using a keyboard and mouse: + mouse click (macOS) or Ctrl + mouse click (other platforms) @@ -353,7 +328,7 @@ The annotation language server provides quick information for annotation terms, To view the quick info for an annotation term, record type, or property used in the annotation file, hover your mouse over it. The accompanying documentation is displayed in a hover window, if provided in the respective OData vocabularies. -To view the quick info for each suggestion in the code completion list, either pressing + (macOS) or Ctrl + (other platforms), or click the *info* icon. The accompanying documentation for the suggestion expands to the side. The expanded documentation stays open and updates as you navigate the list. You can close this by pressing + / Ctrl + again or by clicking on the close icon. +To view the quick info for each suggestion in the code completion list, either pressing CtrlSpace, or click the *info* icon. The accompanying documentation for the suggestion expands to the side. The expanded documentation stays open and updates as you navigate the list. #### Internationalization Support @@ -426,14 +401,15 @@ To enable draft for an entity exposed by a service, simply annotate it with `@od annotate AdminService.Books with @odata.draft.enabled; ``` -[See it live in **cap/samples**.](https://github.com/sap-samples/cloud-cap-samples/tree/main/fiori/app/admin-books/fiori-service.cds#L51){.learn-more} +[See it live in **cap/samples**.](https://github.com/SAP-samples/cloud-cap-samples/blob/6fa2aaee34e862337c5bc5a413817355ab283437/fiori/app/admin-books/fiori-service.cds#L94){.learn-more} ::: warning You can't project from draft-enabled entities, as annotations are propagated. Either _enable_ the draft for the projection and not the original entity or _disable_ the draft on the projection using `@odata.draft.enabled: null`. ::: ### Difference between Compositions and Associations -Be aware that you must not modify associated entities through drafts. Only compositions will get a "Create" button in SAP Fiori elements UIs because they are stored as part of the same draft entity. + +Be aware that you cannot modify _associated_ entities through drafts. Only _compositions_ will get a "Create" button in SAP Fiori elements UIs because they are stored as part of the same draft entity. ### Enabling Draft for [Localized Data](../guides/localized-data) {#draft-for-localized-data} @@ -452,13 +428,14 @@ Adding the annotation `@fiori.draft.enabled` won't work if the corresponding `_t ::: ![An SAP Fiori UI showing how a book is edited in the bookshop sample and that the translations tab is used for non-standard languages.](../assets/draft-for-localized-data.png){style="margin:0"} -[See it live in **cap/samples**.](https://github.com/sap-samples/cloud-cap-samples/tree/main/fiori/app/admin-books/fiori-service.cds#L50){.learn-more} -If you're editing data in multiple languages, the _General_ tab in the example above is reserved for the default language (often "en"). Any change to other languages has to be done in the _Translations_ tab, where a corresponding language can be chosen [from a drop-down menu](https://github.com/SAP-samples/cloud-cap-samples/blob/14ac3efaa13fc025f621b4eed369d03f1ca48341/fiori/app/admin-books/fiori-service.cds#L70) as illustrated above. This also applies if you use the URL parameter `sap-language` on the draft page. +[See it live in **cap/samples**.](https://github.com/SAP-samples/cloud-cap-samples/blob/6fa2aaee34e862337c5bc5a413817355ab283437/fiori/app/admin-books/fiori-service.cds#L93){.learn-more} + +If you're editing data in multiple languages, the _General_ tab in the example above is reserved for the default language (often "en"). Any change to other languages has to be done in the _Translations_ tab, where a corresponding language can be chosen [from a drop-down menu](https://github.com/SAP-samples/cloud-cap-samples/blob/6fa2aaee34e862337c5bc5a413817355ab283437/fiori/app/admin-books/fiori-service.cds#L116) as illustrated above. This also applies if you use the URL parameter `sap-language` on the draft page. ### Validating Drafts -You can add [custom handlers](../guides/providing-services#custom-logic) to add specific validations, as usual. In addition, for a draft, you can register handlers to the `PATCH` events to validate input per field, during the edit session, as follows. +You can add [custom handlers](../guides/providing-services#custom-logic) to add specific validations, as usual. In addition, for a draft, you can register handlers to the respective `UPDATE` events to validate input per field, during the edit session, as follows. ##### ... in Java @@ -626,7 +603,7 @@ We're going to look at three things. 1. Dynamically define the buttons status on the UI -First you need to define an action, like in the [_travel-service.cds_ file](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/srv/travel-service.cds#L11). +First you need to define an action, like in the [_travel-service.cds_ file](https://github.com/SAP-samples/cap-sflight/blob/42ee666e40f9dba1176f8263b512c10d23f07907/srv/travel-service.cds#L11). ```cds entity Travel as projection on my.Travel actions { @@ -637,16 +614,15 @@ entity Travel as projection on my.Travel actions { }; ``` -To define what the action actually is doing, you need to write some custom code. See the [_travel-service.js_](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/srv/travel-service.js#L126) file for example: +To define what the action actually is doing, you need to write some custom code. See the [_travel-service.ts_](https://github.com/SAP-samples/cap-sflight/blob/42ee666e40f9dba1176f8263b512c10d23f07907/srv/travel-service.ts#L86) file for example: ```js -this.on('acceptTravel', req => UPDATE(req._target).with({TravelStatus_code:'A'})) +this.on('acceptTravel', req => UPDATE(req.subject).with({TravelStatus_code:'A'})) ``` -> Note: `req._target` is a workaround that has been [introduced in SFlight](https://github.com/SAP-samples/cap-sflight/blob/685867de9e6a91d61276671e4af7354029c70ac8/srv/workarounds.js#L52). In the future, there might be an official API for it. -Create the buttons, to bring this action onto the UI and make it actionable for the user. There are two buttons: On the overview and in the detail screen. Both are defined in the [_layouts.cds_](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/app/travel_processor/layouts.cds) file. +Create the buttons, to bring this action onto the UI and make it actionable for the user. There are two buttons: On the overview and in the detail screen. Both are defined in the [_layouts.cds_](https://github.com/SAP-samples/cap-sflight/blob/42ee666e40f9dba1176f8263b512c10d23f07907/app/travel_processor/layouts.cds) file. -For the overview of all travels, use the [`@UI.LineItem` annotation](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/app/travel_processor/layouts.cds#L40-L41). +For the overview of all travels, use the [`@UI.LineItem` annotation](https://github.com/SAP-samples/cap-sflight/blob/42ee666e40f9dba1176f8263b512c10d23f07907/app/travel_processor/layouts.cds#L40-L41). ```cds annotate TravelService.Travel with @UI : { @@ -658,7 +634,7 @@ LineItem : [ }; ``` -For the detail screen of a travel, use the [`@UI.Identification` annotation](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/app/travel_processor/layouts.cds#L9-L10). +For the detail screen of a travel, use the [`@UI.Identification` annotation](https://github.com/SAP-samples/cap-sflight/blob/42ee666e40f9dba1176f8263b512c10d23f07907/app/travel_processor/layouts.cds#L9-L10). ```cds annotate TravelService.Travel with @UI : { @@ -670,7 +646,7 @@ annotate TravelService.Travel with @UI : { }; ``` -Now, the buttons are there and connected to the action. The missing piece is to define the availability of the buttons dynamically. Annotate the `Travel` entity in the `TravelService` service accordingly in the [_field-control.cds_](https://github.com/SAP-samples/cap-sflight/blob/8f65dc8b7985bc22584d2a9f94335f110c0450ea/app/travel_processor/field-control.cds#L20-L32) file. +Now, the buttons are there and connected to the action. The missing piece is to define the availability of the buttons dynamically. Annotate the `Travel` entity in the `TravelService` service accordingly in the [_field-control.cds_](https://github.com/SAP-samples/cap-sflight/blob/42ee666e40f9dba1176f8263b512c10d23f07907/app/travel_processor/field-control.cds#L23-L36) file. ```cds annotate TravelService.Travel with actions { @@ -683,7 +659,7 @@ annotate TravelService.Travel with actions { This annotation uses [dynamic expressions](../advanced/odata#dynamic-expressions) to control the buttons for each action. And the status of a travel on the UI is updated, triggered by the `@Common.SideEffects.TargetProperties` annotation. :::info More complex calculation -If you have the need for a more complex calculation, then the interesting parts in SFLIGHT are [virtual fields in _field-control.cds_](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/app/travel_processor/field-control.cds#L10-L16) (also lines 37-44) and [custom code in _travel-service.js_](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/srv/travel-service.js#L13-L22). +If you have the need for a more complex calculation, then the interesting parts in (an older version of) SFLIGHT are [virtual fields in _field-control.cds_](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/app/travel_processor/field-control.cds#L10-L16) (also lines 37-44) and [custom code in _travel-service.js_](https://github.com/SAP-samples/cap-sflight/blob/dfc7827da843ace0ea126f76fc78a6591b325c67/srv/travel-service.js#L13-L22). ::: diff --git a/advanced/hana.md b/advanced/hana.md index b000cb1172..7d0d9a36d6 100644 --- a/advanced/hana.md +++ b/advanced/hana.md @@ -50,7 +50,7 @@ Steps to match the signature of a database object in a facade entity: * For a view, table function, or calculation view with parameters, check that the parameter names and types match, too. Functions with table-like input parameters are not supported. -> Note: If a field of that entity is defined as `not null` and you want to disable its runtime check, you can add [`@assert.notNull: false`](../guides/providing-services#assert-notnull). This is important if you want to use, for example [SAP HANA history tables](https://help.sap.com/docs/SAP_HANA_PLATFORM/6b94445c94ae495c83a19646e7c3fd56/d0b2c5142a19405fb912f71782cd0a84.html). +> Note: If a field of that entity is defined as `not null` and you want to disable its runtime check, you can add `@assert.notNull: false`. This is important if you want to use, for example [SAP HANA history tables](https://help.sap.com/docs/SAP_HANA_PLATFORM/6b94445c94ae495c83a19646e7c3fd56/d0b2c5142a19405fb912f71782cd0a84.html). As a result, the database name is defined by the name of the entity or its elements, after applying the SQL name mapping. diff --git a/advanced/odata.md b/advanced/odata.md index 1710576f6d..69960f00af 100644 --- a/advanced/odata.md +++ b/advanced/odata.md @@ -104,7 +104,7 @@ Content-Type: application/json PATCH requests with delta payload are executed using batch delete and [upsert](../java/working-with-cql/query-api#bulk-upsert) statements, and are more efficient than OData [batch requests](https://docs.oasis-open.org/odata/odata/v4.01/csprd02/part1-protocol/odata-v4.01-csprd02-part1-protocol.html#sec_BatchRequests). -Use PATCH on entity collections for uploading mass data using a dedicated service, which is secured using [role-based authorization](../java/security#role-based-auth). Delta updates must be explicitly enabled by annotating the entity with +Use PATCH on entity collections for uploading mass data using a dedicated service, which is secured using [role-based authorization](../guides/security/authorization#requires). Delta updates must be explicitly enabled by annotating the entity with ```cds @Capabilities.UpdateRestrictions.DeltaUpdateSupported @@ -1060,14 +1060,13 @@ If the `groupby` transformation only includes a subset of the entity keys, the r | `aggregate` | aggregate values | | | | `compute` | add computed properties to the result set | | | | `expand` | expand navigation properties | | | -| `concat` | append additional aggregation to the result | (1) | | -| `skip` / `top` | paginate | (1) | | -| `orderby` | sort the input set | (1) | | +| `concat` | append additional aggregation to the result | | | +| `skip` / `top` | paginate | | | +| `orderby` | sort the input set | | | | `topcount`/`bottomcount` | retain highest/lowest _n_ values | | | | `toppercent`/`bottompercent` | retain highest/lowest _p_% values | | | | `topsum`/`bottomsum` | retain _n_ values limited by sum | | | -- (1) Supported with experimental feature `cds.features.odata_new_parser = true` #### `concat` @@ -1139,6 +1138,71 @@ GET SalesOrganizations?$apply= /ancestors(..., ID, filter(contains(Name, 'New York')), keep start) ``` +#### Modeling Recursive Hierarchies + +Recursive hierarchies are parent-child hierarchies, where each entity references its parent and through that defines the hierarchical structure. A common example is a company organization structure or HR reporting, where each employee entity references another employee a as direct report or manager. + +##### Domain Model + +The simplest domain model looks as follows: + +```cds +entity Employee : Hierarchy { + key ID : UUID; + parent : Association to Employee; + fullName : String; +} + +aspect Hierarchy { + virtual LimitedDescendantCount : Integer64; + virtual DistanceFromRoot : Integer64; + virtual DrillState : String; + virtual LimitedRank : Integer64; +} +``` + +The entity `Employee` has the element `ID`, which identifies the hierarchy node. The `parent` association references the same entity, which establishes the parent-child relationship. + +##### Virtual Elements of a Hierarchy + +The `Hierarchy` aspect defines a set of virtual elements, automatically calculated by the backend at runtime, to describe the state of the hierarchy. This information is requested by the UI to correctly render the hierarchy in a *TreeTable* during user interaction. + +##### Service Model + +The following service defines the projection on the domain model. + +```cds +service HRService { + entity HREmployee as projection on Employee; +} +``` + + +##### OData v4 Annotations for Fiori + +To link the backend and Fiori UI, the projected service entity must be enriched with the following annotations. + +```cds +annotate HRService.HREmployee with @Aggregation.RecursiveHierarchy #EmployeeHierarchy: { + $Type: 'Aggregation.RecursiveHierarchyType', + NodeProperty: ID, + ParentNavigationProperty: parent +}; +``` + +Here the `EmployeeHierarchy` specifies a hierarchy qualifier, `NodeProperty` (identifying the hierarchy node) is linked to `ID` of the entity `HREmployee`, and the `ParentNavigationProperty` is linked to a corresponding `parent` association. + +```cds +annotate HRService.HREmployee with @Hierarchy.RecursiveHierarchy #EmployeeHierarchy: { + $Type: 'Hierarchy.RecursiveHierarchyType', + LimitedDescendantCount: LimitedDescendantCount, + DistanceFromRoot: DistanceFromRoot, + DrillState: DrillState, + LimitedRank: LimitedRank +}; +``` + +Here the same qualifier `EmployeeHierarchy` is referenced to list the names of the [virtual elements of the hierarchy](#virtual-elements-of-a-hierarchy). ### Aggregation Methods diff --git a/advanced/publishing-apis/asyncapi.md b/advanced/publishing-apis/asyncapi.md index 68a2360831..5dd28cce7c 100644 --- a/advanced/publishing-apis/asyncapi.md +++ b/advanced/publishing-apis/asyncapi.md @@ -110,6 +110,39 @@ service CatalogService { } ``` +## Extensions { #extensions} + +`@AsyncAPI.Extensions` can be used to provide arbitrary extensions. +If a specific annotation exists for a given extension, it takes precedence over the definition using @AsyncAPI.Extensions. +For example, if both `@AsyncAPI.ShortText` and `@AsyncAPI.Extensions: { ![sap-shortText]: 'baz' }` are provided, the value from `@AsyncAPI.ShortText` will override the one defined in @AsyncAPI.Extensions. + +For example: + +```cds +@AsyncAPI.Extensions : { + ![foo-bar] : 'baz', + ![sap-shortText] : 'Service Base 1' +} + +service CatalogService { + @AsyncAPI.Extensions : { + ![sap-event-source] : '/{region}/sap.app.test' + } + event SampleEntity.Changed.v1 : projection on CatalogService.SampleEntity; +} +``` + +The `@AsyncAPI.Extensions` annotation can be applied at both the service level and the event level. + +Since the AsyncAPI specification requires all extensions to be prefixed with `x-`, the compiler will automatically add this prefix. Therefore, do not include the `x-` prefix when specifying extensions in `@AsyncAPI.Extensions`. + +### Behavior with `--merged` flag + +When the `--merged` CLI flag is used: + +- Extensions defined via `@AsyncAPI.Extensions` on `services` are **ignored**. +- Extensions defined via `@AsyncAPI.Extensions` on `events` remain effective and are applied as expected. + ## Type Mapping { #mapping} CDS Type to AsyncAPI Mapping @@ -120,15 +153,15 @@ CDS Type to AsyncAPI Mapping | `Boolean` | `{ "type": "boolean" }` | | `Integer` | `{ "type": "integer" }` | | `Integer64` | `{ "type": "string", "format": "int64" }` | -| `Decimal`, `{precision, scale}` | `{ "type": "string", "format": "decimal", "formatPrecision": , "formatScale": }` | -| `Decimal`, without scale | `{ "type": "string", "format": "decimal", "formatPrecision": }` | +| `Decimal`, `{precision, scale}` | `{ "type": "string", "format": "decimal", "x-sap-precision": , "x-sap-scale": }` | +| `Decimal`, without scale | `{ "type": "string", "format": "decimal", "x-sap-precision": }` | | `Decimal`, without precision and scale | `{ "type": "string", "format": "decimal" }` | | `Double` | `{ "type": "number" }` | | `Date` | `{ "type": "string", "format": "date" }` | | `Time` | `{ "type": "string", "format": "partial-time" }` | | `DateTime` | `{ "type": "string", "format": "date-time" }` | | `Timestamp` | `{ "type": "string", "format": "date-time" }` | -| `String` | `{ "type": "string", "maxLength": length }` | -| `Binary` | `{ "type": "string", "maxLength": length }` | +| `String`, `{maxLength}` | `{ "type": "string", "maxLength": length }` | +| `Binary`, `{maxLength}` | `{ "type": "string", "maxLength": length }` | | `LargeBinary` | `{ "type": "string" }` | | `LargeString` | `{ "type": "string" }` | diff --git a/cds/annotations.md b/cds/annotations.md index 25acce2561..5903310a28 100644 --- a/cds/annotations.md +++ b/cds/annotations.md @@ -28,8 +28,6 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ | Annotation | Description | |---------------|------------------------------------------------------------------| -| `@readonly` | see [Input Validation](../guides/providing-services#readonly) | -| `@insertonly` | see [Generic Handlers](../guides/providing-services) | | `@restrict` | see [Authorization](../guides/security/authorization#restrict-annotation) | | `@requires` | see [Authorization](../guides/security/authorization#requires) | @@ -40,12 +38,9 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ |---------------------|----------------------------------------------------------------------| | `@readonly ` | see [Input Validation](../guides/providing-services#readonly) | | `@mandatory` | see [Input Validation](../guides/providing-services#mandatory) | -| `@assert.unique` | see [Input Validation](../guides/providing-services#assert-unique) | -| `@assert.integrity` | see [Input Validation](../guides/databases#database-constraints) | | `@assert.target` | see [Input Validation](../guides/providing-services#assert-target) | | `@assert.format` | see [Input Validation](../guides/providing-services#assert-format) | | `@assert.range` | see [Input Validation](../guides/providing-services#assert-range) | -| `@assert.notNull` | see [Input Validation](../guides/providing-services#assert-notnull) | @@ -102,4 +97,3 @@ Intrinsically supported OData Annotations: | `@Core.IsMediaType` | see [Media Data](../guides/providing-services#serving-media-data) | | `@Core.IsUrl` | see [Media Data](../guides/providing-services#serving-media-data) | | `@Capabilities...` | see [Fiori](../advanced/fiori) | -| `@Common.FieldControl` | see [Input Validation](../guides/providing-services#common-fieldcontrol) | diff --git a/cds/cdl.md b/cds/cdl.md index cee2e5532d..36141c2387 100644 --- a/cds/cdl.md +++ b/cds/cdl.md @@ -302,20 +302,9 @@ entity Employees { ``` The text of a doc comment is stored in CSN in the property `doc`. -When generating OData EDM(X), it appears as value for the annotation `@Core.Description`. - -When generating output for deployment to SAP HANA, the first paragraph of a doc comment is translated to the HANA `COMMENT` feature for tables, table columns, and for views (but not for view columns): - -```sql -CREATE TABLE Employees ( - ID INTEGER, - name NVARCHAR(...) COMMENT 'I am the description for "name"' -) COMMENT 'I am the description for "Employee"' -``` - -::: tip -Propagation of doc comments can be stopped via an empty one: `/** */`. -::: +Doc comments are not propagated. For example, a doc comment defined for an entity +isn't automatically copied to projections of this entity. +When generating OData EDM(X), doc comments are translated to the annotation `@Core.Description`. In CAP Node.js, doc comments need to be switched on when calling the compiler: @@ -331,13 +320,20 @@ cds.compile(..., { docs: true }) ::: -::: tip Doc comments are enabled by default in CAP Java. -In CAP Java, doc comments are automatically enabled by the [CDS Maven Plugin](../java/developing-applications/building#cds-maven-plugin). In generated interfaces they are [converted to corresponding Javadoc comments](../java/assets/cds-maven-plugin-site/generate-mojo.html#documentation). +::: tip Doc comments are automatically enabled in CAP Java. +In CAP Java, doc comments are automatically enabled by the [CDS Maven Plugin](../java/developing-applications/building#cds-maven-plugin). +In generated interfaces they are [converted to corresponding Javadoc comments](../java/assets/cds-maven-plugin-site/generate-mojo.html#documentation). ::: +When generating output for deployment to SAP HANA, the first paragraph of a doc comment is translated +to the HANA `COMMENT` feature for tables, table columns, and for views (but not for view columns): - - +```sql +CREATE TABLE Employees ( + ID INTEGER, + name NVARCHAR(...) COMMENT 'I am the description for "name"' +) COMMENT 'I am the description for "Employee"' +``` ## Entities & Type Definitions @@ -808,6 +804,21 @@ By using a cast, annotations and other properties are inherited from the provide
+### Virtual elements in views + +Virtual elements can be defined in views or projections like this: +```cds +entity SomeView as select from Employee { + // ..., + virtual virt1 : String(22), + virtual virt2 // virtual element without type +} +``` +These virtual elements have no relation to the query source `Employee` but are new fields +in the view. Virtual elements in views or projections are handled as described in the +section on [virtual elements in entities](#virtual-elements). + +
### Views with Parameters diff --git a/cds/cql.md b/cds/cql.md index a115bd71f5..aaa949c74d 100644 --- a/cds/cql.md +++ b/cds/cql.md @@ -399,7 +399,7 @@ SQL casts and CDL casts. The former produces SQL casts when rendered into SQL, w SELECT cast (foo+1 as Decimal) as bar from Foo; -- standard SQL SELECT from Foo { foo+1 as bar : Decimal }; -- CDL-style ``` -[learn more about CDL type definitions](./cdl#types){.learn-more} +[Learn more about CDL type definitions](./cdl#types){.learn-more} Use SQL casts when you actually want a cast in SQL. CDL casts are useful for expressions such as `foo+1` as the compiler does not deduce types. For the OData backend, by specifying a type, the compiler will also assign the correct EDM type in the generated EDM(X) files. diff --git a/get-started/attic/grow-as-you-go.-md b/get-started/attic/grow-as-you-go.-md index 7e80d92b96..c664f03a03 100644 --- a/get-started/attic/grow-as-you-go.-md +++ b/get-started/attic/grow-as-you-go.-md @@ -15,10 +15,10 @@ As your project evolves, you would gradually add new features, for example as ou While we used SQLite in-memory databases and mocked authentication during development, we would use SAP HANA Cloud and a combination of App Router, IAS and/or XSUAA in production. We can quickly do so as follows: ```sh -cds add hana,approuter,xsuaa --for production +cds add hana,approuter,xsuaa ``` -This adds respective packages and configuration to your project. The content of your project, that is, models or code, doesn't change and doesn't have to be touched. The option `--for production` controls that these service variants are only used when in production profile, that is, when the app is deployed to the cloud. Locally you continue to develop in airplane mode. +This adds respective packages and configuration to your project with the `[production]` profile. The content of your project, that is, models or code, doesn't change and doesn't have to be touched. The profile controls that these service variants are only used when in production, that is, when the app is deployed to the cloud. Locally you continue to develop in airplane mode. @@ -39,7 +39,7 @@ cds add mta If you are creating a SaaS application you also need to add support for tenant subscriptions and tenant upgrades. When a tenant subscribes, new database containers have to be bootstrapped along with other resources, like message channels. CAP provides the so-called MTX services which do that automatically in a sidecar micro service. You can add all required packages and configurations by: ```sh -cds add multitenancy --for production +cds add multitenancy ``` [Learn more about multitenancy.](../guides/multitenancy/){.learn-more} diff --git a/get-started/learning-sources.md b/get-started/learning-sources.md index 65c2fc9e58..243e595c34 100644 --- a/get-started/learning-sources.md +++ b/get-started/learning-sources.md @@ -71,6 +71,12 @@ In here, we collected several interesting sample projects for you. Not all of th display:inline; margin:0 0.2em; padding-bottom:5px; + }main .vp-doc a.github img { + content: url(../assets/logos/github.svg); + height:3em; + display:inline; + margin:0 0.2em; + padding-bottom:5px; } main .vp-doc a:has(> img):hover { opacity: 0.7; @@ -148,7 +154,7 @@ By using BTP services and the SAP Cloud Application Programming Model (CAP), you The repository includes the “Poetry Slam Manager” application as a ready-to-run example. It also provides tutorials on how to build the application from scratch using an incremental development approach. Based on this sample application, you find the bill of materials and a sizing example. This addresses the question "Which BTP resources do I need to subscribe to and in what quantities?" and serves as a basis for cost calculation. - + ## Open Source Projects diff --git a/get-started/troubleshooting.md b/get-started/troubleshooting.md index 9e4c539091..1d71cd2005 100644 --- a/get-started/troubleshooting.md +++ b/get-started/troubleshooting.md @@ -374,7 +374,7 @@ sqlite> If you want to test further, use _.help_ command to see all available commands in _sqlite3_. -In case you want a visual interface tool to work with SQLite, you can use [SQLTools](https://marketplace.visualstudio.com/items?itemName=mtxr.sqltools). It's available as an extension for VS Code and integrated in SAP Business Application Studio. +In case you want a visual interface tool to work with SQLite, you can use [SQLite Viewer](https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer). It's available as an extension for VS Code and integrated in SAP Business Application Studio. ## SAP HANA { #hana} diff --git a/guides/databases-hana.md b/guides/databases-hana.md index e81a4a192f..e20266956d 100644 --- a/guides/databases-hana.md +++ b/guides/databases-hana.md @@ -508,6 +508,12 @@ Examples: We recommend keeping _.hdbtable_ deployment for entities where you expect low data volume. Every _.hdbmigrationtable_ artifact becomes part of your versioned source code, creating a new migration version on every model change/build cycle. In turn, each such migration can require manual resolution. You can switch large-volume tables to _.hdbmigrationtable_ at any time, keeping in mind that the existing _.hdbtable_ design-time artifact needs to be undeployed. + +When choosing to use _.hdbmigrationtable_ for an entity with +[localized elements](../guides/localized-data#localized-data) or [compositions of aspects](../cds/cdl#managed-compositions), +the generated `.texts` and composition child entities are automatically handled via _.hdbmigrationtable_, too. +If this is not desired, annotate these generated entities with `@cds.persistence.journal: false`. + ::: tip Sticking to _.hdbtable_ for the actual application development phase avoids lots of initial migration versions that would need to be applied to the database schema. ::: @@ -659,48 +665,23 @@ All limitations for the SAP HANA Cloud database can be found in the [SAP Help Po ### Native Associations -For SAP HANA, CDS associations are by default reflected in the respective database tables and views -by _Native HANA Associations_ (HANA SQL clause `WITH ASSOCIATIONS`). - -CAP no longer needs these native associations (provided you use the new database -service _@cap-js/hana_ for the CAP Node.js stack). - -Unless you explicitly use them in other native HANA objects, we recommend -switching off the generation of native HANA associations, as they increase deploy times: +In previous CAP releases, CDS associations were by default reflected in SAP HANA +database tables and views by _Native HANA Associations_ (HANA SQL clause `WITH ASSOCIATIONS`). +But the presence of such native associations significantly increases (re-)deploy times: They need to be validated in the HDI deployment, and they can introduce indirect dependencies between other objects, which can trigger other unnecessary revalidations -or even unnecessary drop/create of indexes. By switching them off, all this effort is saved. +or even unnecessary drop/create of indexes. -::: code-group +As CAP doesn't need these native associations, by default no native HANA associations +are created anymore starting with CAP 9. -```json [package.json] -{ - "cds": { - "sql": { - "native_hana_associations": false - } - } -} -``` - -```json [cdsrc.json] -{ - "sql": { - "native_hana_associations": false - } -} -``` - -::: - - -For new projects, `cds add hana` automatically adds this configuration. +In the unlikely case that you need native HANA associations because you explicitly use them +in other native HANA objects or in custom code, you can switch them back on with cds.sql.native_hana_associations = true. ::: warning Initial full table migration -Be aware, that the first deployment after this **configuration change may take longer**. +Be aware that the first deployment after this **configuration change may take longer**. -For each entity with associations, the respective database object will be touched +For each entity with associations, the respective database object is touched (DROP/CREATE for views, full table migration via shadow table and data copy for tables). -This is also the reason why we haven't changed the default so far. -Subsequent deployments will benefit, however. + ::: diff --git a/guides/databases-sqlite.md b/guides/databases-sqlite.md index 185aced81e..b93ea6b89a 100644 --- a/guides/databases-sqlite.md +++ b/guides/databases-sqlite.md @@ -650,7 +650,7 @@ ID;title;author.ID;currency.code // [!code --] As mentioned in [Using Lean Draft](#using-lean-draft), we eliminated all draft handling from new database service implementations, and instead implemented draft in a modular, non-intrusive, and optimized way — called *'Lean Draft'*. -When using the new service, the new `cds.fiori.lean_draft` mode is automatically switched on. You may additionally switch on cds.fiori.draft_compat:true in case you run into problems. +When using the new service, the new `cds.fiori.lean_draft` mode is automatically switched on. More detailed documentation for that is coming. diff --git a/guides/databases.md b/guides/databases.md index 5e2979bf34..b110c785a3 100644 --- a/guides/databases.md +++ b/guides/databases.md @@ -22,13 +22,13 @@ impl-variants: true
-### Migrating to New Database Services? {.node} +### Migrating to the `@cap-js/` Database Services? {.node} -With CDS 8, the new database services for SQLite, PostgreSQL, and SAP HANA are now generally available. It's highly recommended to migrate. You can find instructions in the [migration guide](databases-sqlite#migration). Although the guide is written in the context of the new SQLite Service, the same hints apply to PostgreSQL and SAP HANA. +With CDS 8, the [`@cap-js`](https://github.com/cap-js/cds-dbs) database services for SQLite, PostgreSQL, and SAP HANA are generally available. It's highly recommended to migrate. You can find instructions in the [migration guide](databases-sqlite#migration). Although the guide is written in the context of the SQLite Service, the same hints apply to PostgreSQL and SAP HANA. ### Adding Database Packages {.node} -Following are cds-plugin packages for CAP Node.js runtime that support respective databases: +Following are cds-plugin packages for CAP Node.js runtime that support the respective databases: | Database | Package | Remarks | | ------------------------------ | ------------------------------------------------------------ | ---------------------------------- | @@ -37,7 +37,7 @@ Following are cds-plugin packages for CAP Node.js runtime that support respectiv | **[PostgreSQL](databases-postgres)** | [`@cap-js/postgres`](https://www.npmjs.com/package/@cap-js/postgres) | maintained by community + CAP team | -> Follow the links above to find specific information for each. +> Follow the preceding links to find specific information for each. In general, all you need to do is to install one of the database packages, as follows: @@ -345,7 +345,7 @@ Select.from(AUTHOR) ### Standard Operators {.node} -The database services guarantee identical behavior of these operators: +The database services guarantee the identical behavior of these operators: * `==`, `=` — with `=` null being translated to `is null` * `!=`, `<>` — with `!=` translated to `IS NOT` in SQLite, or to `IS DISTINCT FROM` in standard SQL, or to an equivalent polyfill in SAP HANA @@ -353,72 +353,11 @@ The database services guarantee identical behavior of these operators: In particular, the translation of `!=` to `IS NOT` in SQLite — or to `IS DISTINCT FROM` in standard SQL, or to an equivalent polyfill in SAP HANA — greatly improves the portability of your code. -::: warning Runtime Only -The operator mappings are available for runtime queries only, but not in CDS files. -::: - - -### Functions Mappings for Runtime Queries {.node} - -A specified set of standard functions is supported in a **database-agnostic**, hence portable way, and translated to database-specific variants or polyfills. -Note that these functions are only supported within runtime queries, but not in CDS files. -This set of functions are by large the same as specified in OData: - -* `concat(x,y,...)` — concatenates the given strings or numbers -* `trim(x)` — removes leading and trailing whitespaces -* `contains(x,y)` — checks whether `y` is contained in `x`, may be fuzzy -* `startswith(x,y)` — checks whether `y` starts with `x` -* `endswith(x,y)` — checks whether `y` ends with `x` -* `matchespattern(x,y)` — checks whether `x` matches regex `y` -* `substring(x,i,n?)` 1 — - Extracts a substring from `x` starting at index `i` (0-based) with optional length `n`. - * **`i`**: Positive starts at `i`, negative starts `i` before the end. - * **`n`**: Positive extracts `n` items; omitted extracts to the end; negative is invalid. -* `indexof(x,y)` 1 — returns the index of the first occurrence of `y` in `x` -* `length(x)` — returns the length of string `x` -* `tolower(x)` — returns all-lowercased `x` -* `toupper(x)` — returns all-uppercased `x` -* `ceiling(x)` — rounds the input numeric parameter up to the nearest numeric value -* `floor(x)` — rounds the input numeric parameter down to the nearest numeric value -* `round(x)` — rounds the input numeric parameter to the nearest numeric value. - The mid-point between two integers is rounded away from zero, i.e. 0.5 is rounded to 1 and ‑0.5 is rounded to -1. -* `year(x)` `month(x)`, `day(x)`, `hour(x)`, `minute(x)`, `second(x)` — - returns parts of a datetime for a given `cds.DateTime` / `cds.Date` / `cds.Time` -* `time(x)`, `date(x)` - returns a string representing the `time` / `date` for a given `cds.DateTime` / `cds.Date` / `cds.Time` -* `fractionalseconds(x)` - returns a a `Decimal` representing the fractions of a second for a given `cds.Timestamp` -* `maxdatetime()` - returns the latest possible point in time: `'9999-12-31T23:59:59.999Z'` -* `mindatetime()` — returns the earliest possible point in time: `'0001-01-01T00:00:00.000Z'` -* `totalseconds(x)` — returns the duration of the value in total seconds, including fractional seconds. The [OData spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_totalseconds) defines the input as EDM.Duration: `P12DT23H59M59.999999999999S` -* `now()` — returns the current datetime -* `min(x)` `max(x)` `sum(x)` `average(x)` `count(x)`, `countdistinct(x)` — aggregate functions -* `search(xs,y)` — checks whether `y` is contained in any of `xs`, may be fuzzy → [see Searching Data](../guides/providing-services#searching-data) -* `session_context(v)` — with standard variable names → [see Session Variables](#session-variables) -> 1 These functions work zero-based. E.g., `substring('abcdef', 1, 3)` returns 'bcd' - -> You have to write these functions exactly as given; all-uppercase usages aren't supported. - -In addition to the standard functions, which all `@cap-js` database services support, `@cap-js/sqlite` and `@cap-js/postgres` also support these common SAP HANA functions, to further increase the scope for portable testing: - -* `years_between` — Computes the number of years between two specified dates. -* `months_between` — Computes the number of months between two specified dates. -* `days_between` — Computes the number of days between two specified dates. -* `seconds_between` — Computes the number of seconds between two specified dates. -* `nano100_between` — Computes the time difference between two dates to the precision of 0.1 microseconds. - -The database service implementation translates these to the best-possible native SQL functions, thus enhancing the extent of **portable** queries. -With open source and the new database service architecture, we also have methods in place to enhance this list by custom implementation. - -> For the SAP HANA functions, both usages are allowed: all-lowercase as given above, as well as all-uppercase. - -::: warning Runtime Only -The function mappings are available for runtime queries only, but not in CDS files. -::: - ### Session Variables {.node} -The API shown below, which includes the function `session_context()` and specific pseudo variable names, is supported by **all** new database services, that is, *SQLite*, *PostgreSQL* and *SAP HANA*. -This allows you to write respective code once and run it on all these databases: +The API shown after this, which includes the function `session_context()` and specific pseudo variable names, is supported by **all** new database services, that is, *SQLite*, *PostgreSQL* and *SAP HANA*. +This allows you to write the respective code once and run it on all these databases: ```sql SELECT session_context('$user.id') @@ -454,7 +393,7 @@ db.queryForList("SELECT from sqlite_schema where name like ?", name); ### Reading `LargeBinary` / BLOB {.node} -Formerly, `LargeBinary` elements (or BLOBs) were always returned as any other data type. Now, they are skipped from `SELECT *` queries. Yet, you can still enforce reading BLOBs by explicitly selecting them. Then the BLOB properties are returned as readable streams. +Formerly, `LargeBinary` elements (or BLOBs) were always returned as any other data type. Now, they're skipped from `SELECT *` queries. Yet, you can still enforce reading BLOBs by explicitly selecting them. Then the BLOB properties are returned as readable streams. ```js SELECT.from(Books) //> [{ ID, title, ..., image1, image2 }] // [!code --] @@ -481,7 +420,7 @@ You can also do this manually with the CLI command `cds compile --to `. When you've created a CAP Java application with `cds init --java` or with CAP Java's [Maven archetype](../java/developing-applications/building#the-maven-archetype), the Maven build invokes the CDS compiler to generate a `schema.sql` file for your target database. In the `default` profile (development mode), an in-memory database is [initialized by Spring](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-initialization) and the schema is bootstrapped from the `schema.sql` file. -[Learn more about adding an inital database schema.](../java/cqn-services/persistence-services#initial-database-schema){.learn-more} +[Learn more about adding an initial database schema.](../java/cqn-services/persistence-services#initial-database-schema){.learn-more}
@@ -791,7 +730,7 @@ The following rules apply: - If you refer to a column name in the annotation, you need to take care of a potential name mapping yourself, for example, for structured elements. -- Annotation `@sql.prepend` is only supported for entities translating to tables. It can't be used with views nor with elements. +- Annotation `@sql.prepend` is only supported for entities translating to tables. It can't be used with views or with elements. - For SAP HANA tables, there's an implicit `@sql.prepend: 'COLUMN'` that is overwritten by an explicitly provided `@sql.prepend`. * Both `@sql.prepend` and `@sql.append` are disallowed in SaaS extension projects. @@ -823,7 +762,7 @@ ROW TABLE E ( [Learn more about Columnar and Row-Based Data Storage](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-administration-guide/columnar-and-row-based-data-storage){.learn-more} ### Reserved Words -The CDS compiler and CAP runtimes provide smart quoting for reserved words in SQLite and in SAP HANA so that they can still be used in most situations. But in general reserved words cannot be used as identifiers. The list of reserved words varies per database. +The CDS compiler and CAP runtimes provide smart quoting for reserved words in SQLite and in SAP HANA so that they can still be used in most situations. But in general reserved words can't be used as identifiers. The list of reserved words varies per database. Find here a collection of resources on selected databases and their reference documentation: @@ -841,6 +780,46 @@ Find here a collection of resources on selected databases and their reference do ## Database Constraints +### Not Null + +You can specify that a column's value must not be `NULL` by adding the [`not null` constraint](../cds/cdl#null-values) to the element, for example: + +```cds +entity Books { + key ID: Integer; + title: String not null; +} +``` + + +### Unique + +Annotate an entity with `@assert.unique.`, specifying one or more element combinations to enforce uniqueness checks on all CREATE and UPDATE operations. For example: + +```cds +@assert.unique: { + locale: [ parent, locale ], + timeslice: [ parent, validFrom ], +} +entity LocalizedTemporalData { + key record_ID : UUID; // technical primary key + parent : Association to Data; + locale : String; + validFrom : Date; + validTo : Date; +} +``` +{.indent} + +The value of the annotation is an array of paths referring to elements in the entity. These elements may be of a scalar type, structs, or managed associations. Individual foreign keys or unmanaged associations are not supported. + +If structured elements are specified, the unique constraint will contain all columns stemming from it. If the path points to a managed association, the unique constraint will contain all foreign key columns stemming from it. +::: tip +You don't need to specify `@assert.unique` constraints for the primary key elements of an entity as these are automatically secured by a SQL `PRIMARY KEY` constraint. +::: + +### Foreign Keys + The information about foreign key relations contained in the associations of CDS models can be used to generate foreign key constraints on the database tables. Within CAP, referential consistency is established only at commit. The ["deferred" concept for foreign key constraints](https://www.sqlite.org/foreignkeys.html) in SQL databases allows the constraints to be checked and enforced at the time of the [COMMIT statement within a transaction](https://www.sqlite.org/lang_transaction.html) rather than immediately when the data is modified, providing more flexibility in maintaining data integrity. Enable generation of foreign key constraints on the database with: @@ -940,7 +919,166 @@ Instead, they protect the integrity of your data in the database layer against p → Use [`@assert.target`](providing-services#assert-target) for corresponding input validations. ::: +## Standard Database Functions +{ #functions-mappings-for-runtime-queries } + +A specified set of standard functions - inspired by [OData](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_StringandCollectionFunctions) and [SAP HANA](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/alphabetical-list-of-functions?locale=en-US) - is supported in a **database-agnostic**, hence portable way, and translated to the best-possible native SQL functions or polyfills during runtime (currently only Node.js) and for your CDL files. + + +### OData standard functions + +The `@sap/cds-compiler` and all CAP Node.js database services come with out of the box support for common OData functions. + +::: warning Case Sensitivity +The OData function mappings are case-sensitive and must be written as in the list below. +::: + +Assuming you have the following entity definition: + +```cds +entity V as select from Books { + startswith(title, 'Raven') as lowerCase, // mapped to native SQL equivalent + startsWith(title, 'Raven') as camelCase, // passed as-is +} +``` + + +Then you compile the SAP HANA artifacts: + +`$ cds compile -2 sql --dialect hana` + + +This is the result: + +```sql +CREATE VIEW V AS SELECT + (CASE WHEN locate(title, 'Raven') = 1 THEN TRUE ELSE FALSE END) AS lowerCase, + -- the below will most likely fail on SAP HANA + startsWith(title, 'Raven') AS camelCase +FROM Books; +``` + +💡 If you want to use a DB native function or a UDF (User-Defined Function) instead of the OData function mappings, you can +do that by using a different casing than the OData function names as defined in the list below. +For example, `startsWith` instead of `startswith` will be passed as-is to the database. + +#### String Functions + +- `concat(x, y, ...)` + Concatenates the given strings or numbers. + +- `trim(x)` + Removes leading and trailing whitespaces. + +- `contains(x, y)` + Checks whether `y` is contained in `x` (case-sensitive). + +- `startswith(x, y)` + Checks whether `y` starts with `x` (case-sensitive). + +- `endswith(x, y)` + Checks whether `y` ends with `x` (case-sensitive). + +- `matchespattern(x, y)` + Checks whether `x` matches the regular expression `y`. + +- `indexof(x, y)` 1 + Returns the index of the first occurrence of `y` in `x` (case-sensitive). + +- `substring(x, i, n?)` 1 + Extracts a substring from `x` starting at index `i` (0-based) with an optional length `n`. + + | Parameter | Positive | Negative | Omitted + | --- | --- | --- | -- | + | `i` | starts at index `i` | starts `i` positions before the end | + | `n` | extracts `n` characters | invalid | extracts until the end of the string + +- `length(x)` + Returns the length of the string `x`. + +- `tolower(x)` + Converts all characters in `x` to lowercase. + +- `toupper(x)` + Converts all characters in `x` to uppercase. + +> 1 These functions work zero-based. For example, `substring('abcdef', 1, 3)` returns 'bcd' + +#### Numeric Functions + +- `ceiling(x)` + Rounds the numeric parameter up to the nearest integer. + +- `floor(x)` + Rounds the numeric parameter down to the nearest integer. + +- `round(x)` + Rounds the numeric parameter to the nearest integer. + The midpoint between two integers is rounded away from zero (e.g., `0.5` → `1` and `-0.5` → `-1`). + + ::: warning `round` function with more than one argument + Note that most databases support `round` functions with multiple arguments, + the second parameter being the precision. SAP HANA even has a third argument which is the rounding mode. + If you provide more than one argument, the `round` function may behave differently depending on the database. + ::: + +#### Date and Time Functions + +- `year(x)`, `month(x)`, `day(x)`, `hour(x)`, `minute(x)`, `second(x)` + Extracts and returns specific date / time parts as integer value from a given `cds.DateTime`, `cds.Date`, or `cds.Time`. + +- `time(x)`, `date(x)` + Extracts and returns a time or date from a given `cds.DateTime`, `cds.Date`, or `cds.Time`. + +- `fractionalseconds(x)` + Returns a `Decimal` representing the fractional seconds for a given `cds.Timestamp`. + +- `maxdatetime()` + Returns the latest possible point in time: `'9999-12-31T23:59:59.999Z'`. + +- `mindatetime()` + Returns the earliest possible point in time: `'0001-01-01T00:00:00.000Z'`. + +#### Aggregate Functions + +- `min(x)`, `max(x)`, `sum(x)`, `average(x)`, `count(x)`, `countdistinct(x)` + Standard aggregate functions used to calculate minimum, maximum, sum, average, count, and distinct count of values. + + +### SAP HANA Functions + +In addition to the OData standard functions, the `@sap/cds-compiler` and all CAP Node.js database services come with +out of the box support for some common SAP HANA functions, to further increase the scope for portable testing: + +::: warning Upper- and Lowercase are supported +For the SAP HANA functions, both usages are allowed: all-lowercase as given above, as well as all-uppercase. +::: + +- `years_between` + Computes the number of years between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/years-between-function-datetime?locale=en-US)) +- `months_between` + Computes the number of months between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/months-between-function-datetime?locale=en-US)) +- `days_between` + Computes the number of days between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/days-between-function-datetime?locale=en-US)) +- `seconds_between` + Computes the number of seconds between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/seconds-between-function-datetime?locale=en-US)) +- `nano100_between` + Computes the time difference between two dates to the precision of 0.1 microseconds. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/nano100-between-function-datetime?locale=en-US)) + +### Special Runtime Functions + +In addition to the OData and SAP HANA standard functions, the **CAP runtimes** provides special functions that are only available for runtime queries: + +- `search(x, y)` + Checks whether `y` is contained in any element of `x` (fuzzy matching may apply). + See [Searching Data](../guides/providing-services#searching-data) for more details. + +- `session_context()` + Utilizes standard variable names to maintain session context. + Refer to [Session Variables](#session-variables) for additional information. +- `now()` + Returns the current timestamp. ## Using Native Features { #native-db-functions} @@ -1023,7 +1161,7 @@ In case of conflicts, follow these steps to provide different models for differe ``` 4. For the Spring Boot side it's similar. If you have a local development database and a hybrid profile with a remote SAP HANA database, you only need to run in default (or any other) profile. For the SAP HANA part, the build and deploy part is done separately and the application just needs to be started using `cds bind`. -Once you have 2 non-HANA local databases you need to have 2 distinct database configurations in your Spring Boot configuration (in most cases application.yaml). +Once you have 2 non-HANA local databases, you need to have 2 distinct database configurations in your Spring Boot configuration (in most cases application.yaml). ```yaml spring: diff --git a/guides/deployment/assets/deploy-overview.drawio.svg b/guides/deployment/assets/deploy-overview.drawio.svg deleted file mode 100644 index a1c54de7b0..0000000000 --- a/guides/deployment/assets/deploy-overview.drawio.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - -
-
-
- - - CAP App - - -
-
-
-
- - CAP App - -
-
- - - - - - - - -
-
-
- - App Router - -
-
-
-
- - App Router - -
-
- - - - -
-
-
- - XSUAA - -
-
-
-
- - XSUAA - -
-
- - - - - -
-
-
- - HANA Cloud - -
-
-
-
- - HANA Cloud - -
-
-
- - - - - Text is not SVG - cannot display - - - -
\ No newline at end of file diff --git a/guides/deployment/assets/microservices/app-instances.excalidraw.svg b/guides/deployment/assets/microservices/app-instances.excalidraw.svg new file mode 100644 index 0000000000..fcbe630a4b --- /dev/null +++ b/guides/deployment/assets/microservices/app-instances.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1b2VLbWlx1MDAxNn3PV1Dc13By5iFVXV1cdTAwMDZcdTAwMDMxYfaA465bKcWSbTWyZCRcdTAwMTkwqfx7b1x1MDAxOWLZlkRscIDctlx1MDAxZlx1MDAxOI6GM62111x1MDAxZY6/v9vY2IxHXHUwMDAzZ/PjxqZz27Y811x1MDAwZa2bzfdJ+7VcdTAwMTNGbuDDJTr+P1xuhmF7fGcvjlx1MDAwN9HHXHUwMDBmXHUwMDFm+lZ46cRcdTAwMDPPajvo2o2Gllx1MDAxN8VD21xyUDvof3Bjp1x1MDAxZv07+Xls9Z1/XHKCvlx1MDAxZIco7WTLsd04XGLv+3I8p+/4cVx1MDAwNG//XHUwMDBm/L+x8X38XHUwMDEzrrh20mOFNcr+8NSrnfLK7m4zUlxcVc340fFNP6dcdTAwMTA67djyu56TXrqFdmlcdTAwMThSXGZzqZWYXFxcdTAwMTjBXHUwMDA1JikyhGNJ0/ZcdTAwMWLXjntwjWOCXGJcdTAwMTX454dM7ug5brdcdTAwMTcnj1x1MDAxM4GkXCLTb73v/eNcdTAwMDaetERxXHUwMDE4XFw6O4FcdTAwMDdzhSH+RYxcIm2aXHUwMDBl8JvVvuyGwdC3J/fEoeVHXHUwMDAzK4RcdTAwMTVJ7+u4nleNR+O3w17AXCJuzvVx8TByOtde9Fx1MDAxNHTa7flOlKx6OrtgYLXdOFlcdTAwMWSC01kkI1x1MDAxY1Ts8Vx1MDAwNv2djimEra0kO+RcdTAwMGY9b9Ls+raTrPumRWZ68+2H3mbujlx1MDAxYyd5XHUwMDAz0ZpLrIxkkyspXHUwMDAyXHIx863HgT9GI8FcdTAwMDJ2VvGpO9yoXGaAisev7Vx1MDAwMCiddLGTQexOgS2dy3BgW/ePXHUwMDEwxSkzhClNWLqWnutfzlx1MDAwZt1cdTAwMGLal2kv49ZcdTAwMWbv8/C7deZen2+rnaHu1COvdPpJxeW7LH5cdTAwMWTPc1x1MDAwN9FcdTAwMWN6teBcdTAwMDBEySjl2lx1MDAxMKzoXGaEhWKIa001ycGwVEhKwriQilx1MDAxMS5cdM1imHIklFx1MDAxNIpqTChcdTAwMTdU8/8rLD+0QSslnUrnbtSM/bDe/EqCRo1cdTAwMDKIXHUwMDFmri9cdTAwMDF5tZ+P+e9pV1x1MDAwZttNXHUwMDFmWn7MU4FiSmFDKMmjgtKqiFxu8IzWwij2hplcdTAwMTBxVVwi1bPj1sGVO1x1MDAxYX1cdTAwMWH9d//r8UWWXHTQR4ZcdTAwMDZcdTAwMTRJo7nCWFx1MDAxM4ONnKWBXHUwMDE2SGhlNGNcbnMqZYZcdTAwMGJcdTAwMTjBLKhSKiFcdTAwMTIm2pgsXHUwMDE5uEHYXGJtpMbAXHUwMDE3wcyaXHUwMDBiz+NC49lcXFx1MDAwMFlgXG7YIPKoIHWhKmiGYUe4+S2qwJbgwqR5XHUwMDEwuNNcdTAwMWVO8kn/2kiRNf5n8vff73Pv3iqGc/LJXHUwMDAwOX1fZlx1MDAxNz0rineCft+NYaKnySAzXG5cdTAwMWRbYbxcctvq+t35a45vXHUwMDE3XFxcdTAwMTk/VVxuw+Cm51hcdTAwMTmQwHPz11x1MDAxZbFcdTAwMWH7J7JR03gw/Fxc2jLfjkv4/HB4uYjVMJQgpTCgXHUwMDAxXHUwMDA0XHUwMDBlXHUwMDFimsLh3moopKhRklx1MDAxOFhMylK2TFlccthzXHJcdTAwMWVcdTAwMDZcdTAwMTFMMmNSeUythkSYXHUwMDAxKlx1MDAxNIdOqMR0LaHPM1x1MDAxYvr5Vlx1MDAwM1x1MDAxM0WxYDijlYnZ4GS+dWI2XHUwMDE4k7DhXCJ97Fx1MDAxZmc1XG7gnHyyQP7DzUa5XHUwMDFk0VFpv9Uo1z+fbZfN0DI+W9jththQKaO0oUIpMlx1MDAxYjlKylx1MDAxMNhcdTAwMTZcYj7AyEq4vPa934LheL7vTTTHKiFBjuHQTM63Tlxmh9JcdTAwMDJkxohcdTAwMTQmr2Q5XHUwMDFloVx1MDAwM+1URPjVWGdXR6rR/FLdOzxsXHJcdTAwMTbOolx1MDAxOHCxuaJSTGdLxllcdTAwMTRDXHUwMDExUULlhqBcdTAwMWEjhiEoYUpcdTAwMTSmUf6Q9IltRT3nZfMnJ4vimVx1MDAxNeKZXHUwMDBiQzjAM6N5if/DaSGgSVx1MDAxMmYp9qRYcjK8dKBT+Iqd23RjpuBZ4nut6/Pzmlx1MDAxZFx1MDAxZNTPavUr/eW0cbM5ue9Hxlx1MDAxZfxWsuSPJkOWmcnc88QwgLxcdTAwMDSvkkFcdTAwMTRcIqY8xnuyqGKySI5cdTAwMTJRXHUwMDA2aYZ4e+aOVDPWZClcIsvpXHUwMDEyyUZcbu4+XHUwMDE4JJ5n5oXItE5YIbFkgoBXsGozXHUwMDBmPtlUSmNcdFx1MDAwN3FcZkCYfrNaL5WmtjDw46p7N9Y6PNO6Z/Vdb1x1MDAxY/3MvKPkud1k9pttXHUwMDE4rlx1MDAxM25OL0Hsti1vckPfte1pcWjDSy2IvMLKXCIqXHUwMDEzhG7X9S2vlj9ua1x1MDAxOFx1MDAwN+dOdD/yOFx1MDAxYzrTa+J8+klcdTAwMDOCqHiEvEfb7bhTs/WJ5VW7d0136IbdxbJMWmcqXHUwMDAyyVpxw1x1MDAxMTjM+WlWXHUwMDAyjDYzxYVcdF1V4lJcdTAwMGIyo5lvmrcvXyM4e77PJrDmWlKZp3HcZFJHP9ksuGSgcPLVfbbfXHUwMDE07WVwmXymXHUwMDEwmb4gI61/QnR3Vm76/Yp7XHUwMDBivyjevcNcdTAwMTX/K6svUVx1MDAxNOSI5PiyXHUwMDFjXHUwMDE5XlRcdTAwMTJcdTAwMDTbgCnjLI/pRFx1MDAxYjRXYnzTRLeDZGtflOnnz/ZmtdRGQyyRR3Ra7MxcdTAwMDItMcWavnpa51x1MDAxMTRcdTAwMWabht2QJ4fl7tWpXHUwMDFiV+zBXHUwMDExv+ougWaKjM4pcXNmkMJcbrM84WJcdTAwMDRcdMxMXHUwMDAy9vtcIndOdpNRNOetvmlYv7x+VZdwO8GtN1x1MDAwMlwig7xqXHUwMDA24YXVXGaKIWJcdTAwMTDTe/D28HuoXHUwMDBlpTWSXHUwMDE35qZ0tNfbq3X3tY2Xwq8gbO40xVx1MDAxOMCaolx1MDAwMr9rXHLfXHUwMDE1wLe2TNQkXGYlnOnMYYxkSlx1MDAwNFx1MDAxN8GXaK4kx287OTa6XHUwMDFldqpX142jnj5cdTAwMTKXp1x1MDAwN6N+7dJf6ohcdTAwMTGhXHUwMDEyQncy51FcYqqyXsNcdTAwMWHBK0RwfVx0XHUwMDA0K4NcdTAwMDVWJLecXGY+Qlx1MDAxMYJcdTAwMTljRIDb8ZZcdTAwMWSIfnA3vIvtK9FcdTAwMTP7LXPDbkejRk6xI5uwksYgKXHWXHUwMDE55lgjWZSrYlx1MDAxMjFhjEqqqkoxnoPcNWSLIFtwLlwi/1xcnKKGYaXyjC4vLkgwLFx1MDAwNEkq369cctlcdTAwMTSBXHUwMDBmXHUwMDE5n3olWkWeynM6UzDIZKniYCrpNJuimlx1MDAxOex8PmpmdKvJRl3iT1x1MDAwN+6BU+/d+lYjvO3dqMuj84WlRVx0irTJZKRcdTAwMThEo9pwkevYg+KiRHTFT2FJcfLbyi5/dcafP5yZXHUwMDE3izOTayGZJrnOvHjklFx1MDAxZVZcXFx1MDAwMqVfsLJSLlmHV1HTJ63tnltcdTAwMWSWy5Vm9/TVKiv5o1lAqFx1MDAwMM/gSnGBqZZaXG7KZlx0IdgjhFx1MDAxMMBcdTAwMTZiXGbG4t5N+/1q9c/gQ3NxPlBcZlFcdTAwMWMn3ORcdTAwMTdVXG7DXHUwMDAzKTSVmvFXd64ySlV2XHUwMDA2XjBK+tqo+268XG7Vem515Vx1MDAxN1oyr2aFM1iNso3aW2eq9G2vXHUwMDE071xi4pxcdTAwMWbXyr3b0cLKplx1MDAxM1x1MDAxZpIyymk26oeoSVx1MDAxYsl1bthEOEFA5vFjY3nLoTOXiKrZqGtN61x0rdvPP1igXHUwMDE504JcdTAwMTf4pcVFXHUwMDE3ML+EXHUwMDFhpehcdTAwMGLqX9w6adOjlr+9U7luea3L3fZu53BcdP3TSjKhp46APUv/8kezgP5pbVx1MDAxMKcgflJTQrUhs5RhXHUwMDFhXHTGaEGqlyNcdTAwMDNcdTAwMWZpuJ7n2zpe+yVd7MVVUFxuISFayy1RMPlIjoxSolxiW3GO7Fx0yM2oYGkweFx1MDAwYsr3XHUwMDBirZlXvplRr0btuodmOGxslUNzXHUwMDE0nlebdVx1MDAxZrBcdTAwMTQurnZUIIqBgtiwTJFGIMbnXHUwMDBmXHUwMDExrNVuVfR1nq92XGbIpLTOPTDEXHUwMDFmSX2D0HHOXHJ/0vdQnqZ2Mtopu63Wxd2x43RcdTAwMGaZIC6NLpZTO57Iy2rULn80i6id0ogl32JcdTAwMTFC5ahcdTAwMWSHWFDpuaM1a7VbXHUwMDA1XTqLq1x1MDAxZGdJQj377dxkQqrYXHUwMDA3NJxcYjB2YrXfznpcdTAwMDJw36rY/UJqXkDsSn1cdTAwMDc7X1o3n3v+3Y4tfH1WOtxeXFzsiEBY5Fx1MDAxY0dcdTAwMDChMvOHyNdCtyrmdlcgdEBMTFx1MDAwNc+jNGOFWU1cdTAwMTh88u1j8pJCd3B61t7e2o9L/OJz42rn02nnrt5aUuhcdTAwMTjneJpcdTAwMWZPXHUwMDE3uvzRLFwidFIjXCI4hG1cdTAwMDRcdTAwMTY4I3RCoTmXca1yq+BKb3GVXHUwMDAzpFxiQzHNPXZWXFw1Jpopzlx1MDAxNZGrrcE9XHUwMDAxtm9V5n4hMquVuXdcdTAwMGamYNNcdTAwMWFcZqoxrOjEMm5eu87NdrEovHvgfFx1MDAwMnRnbFB/vPvxPz3DlkkifQ==XSUAAUIsDeployment UnitAppAppApp \ No newline at end of file diff --git a/guides/deployment/assets/microservices/bookstore.excalidraw.svg b/guides/deployment/assets/microservices/bookstore.excalidraw.svg new file mode 100644 index 0000000000..8f96b5610b --- /dev/null +++ b/guides/deployment/assets/microservices/bookstore.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1dbVfiSlx1MDAxMv5+f8VcdTAwMWP36zXb3dWv91x1MDAxYuqoOL4holxme/Z4XHUwMDE4iJBcdTAwMTFBIai45/73rY5KXHUwMDAy6SBgZtRdOHN0JCF0knrqeaq6uvKfP758WVx1MDAwYkc3/tpfX9b8h0a9XHUwMDEzNPv1+7U/7ft3fn9cdTAwMTD0uriJRX9cdTAwMGZ6w34j2rNcdTAwMWSGN4O//vnP+Fx1MDAxM16jd/30Kb/jX/vdcID7/Vx1MDAwYv/+8uU/0U/cXHUwMDEyNO1ni3C21Vx1MDAxZFx1MDAxZXdOj3nx69fqQHFVNtFHo51eXHUwMDA20/dcdTAwMWJhvdvq+PGmXHUwMDA3fF8p4nHNXHUwMDE5NZpI0EyPt45wK0jmXHUwMDE5yolkYvz+fdBcZtu4TVPhgVx1MDAwMaVcdTAwMTljXHUwMDFhOIx3aPtBq1x1MDAxZNpPXHUwMDFi44FSyjClpdBMjnd5XHUwMDFhyF9fyPidQdjvXfmbvU6vb0f7XHUwMDBmalx1MDAxNG2weKw/6o2rVr837DbH+4T9endwU+/jxYn3u1xmOp1yOIqOjlx1MDAxN1x1MDAxOC/m2tR3nD+Pn029n/Up/NJWu+tcdTAwMGbsXHKg43d7N/VGXHUwMDEw2mtESXxcdTAwMTZ2hDfFZnSv/lx1MDAxZI+pX7/2i/ZmdYedzvjtoNv07S1Yq9OJb+s2n79tYu+B79sjUK25JMrI+HrHZkVcdTAwMTnI6bdcdTAwMGZ73cjGKEiBn9RcIv5gMNhC41xuo+Ne1jtcdTAwMDM/vtp2XHUwMDE0X1x1MDAxM4ZcdTAwMTefzPCmWX/6XGJVnNmxXHUwMDAwVyQ+aCfoXk2PvdNrXFzF31x1MDAxMr37958uWy5cXPvE/167/9buPm42RVeXXG77XHUwMDFic9syJUJ6hEnBOIAkkLhcdTAwMTj2RnElPI7WiFx1MDAwNk2lXHUwMDEyQFTKpnGLR41hVCtB7Mth1Vxcelxmz1x1MDAxOZRYmbPbnJnbnF/udXwj4fmdv1NWXHUwMDBlwkjCXHUwMDA0N24r11lWzlxio5JwI8QyVj5cdTAwMWVgPNSE1YX+Q3xnXHUwMDEyRrt3XFxqbKzvhFx1MDAwNX7+7ex2c/f48rFSW1x1MDAxYu/395/uwz59uNbbJe1cdTAwMWH5fsw25eD7XHKUOmfnMPktL99f7/d79/NcdTAwMWW3VFx1MDAxOPVG51uNLr+s3lx1MDAxNGtcdTAwMWJB58ivzXfc5//NgDxcdTAwMTCEPMlcdTAwMDfy7quXgvzExX9Gu1JcdTAwMWU3klxuXHUwMDAzXFwgrNkk2rX0KOWKXHUwMDEyKjPQbrTHhDFcXCs8IalcdTAwMTOAXHUwMDFlg52tQJ5cdTAwMDFymJ+zXHUwMDAwL62xyHSBXHUwMDE5708mZaF2IIoqyJuyXHUwMDE4XHUwMDE1SqpcdTAwMDXsNzZHa4Z/2W/rXVxywl4/wUCXvW5YXHUwMDBlXHUwMDFln7zQxLvb9eugYy+7mDhOoVx1MDAxM7TsJVhr4JD9/lryMoRcdTAwMDFcbsHxXHUwMDBl10GzmSS7XHUwMDA2XHUwMDFltFx1MDAxZXT9fnFcdTAwMWXW7PWDVtCtd06zx15cdTAwMWaGvVx1MDAxM3/wNPqwP/ST18bffUFcdTAwMDNFvMyA8nopuDvZUJtDfVlcdTAwMTl0XG7HuyrcekxD2e90gptBirtcdTAwMTGNhKJQNJpyXHUwMDBl8aWyXHUwMDE3ToLyJDWcKmqI1Fx1MDAxMNPCXHUwMDBiliXuICk6XHUwMDAyqYBySWOkxFjmXHUwMDFl3nahmCaUccE0/7/Cdsy+jF5cdTAwMTYvXHUwMDFmR9Ww269UL2jv7JQhqFNu/1VcdTAwMTegduYlepZF9OhcdTAwMTVcdTAwMTjeXHUwMDEwRl1y1vDUu2PXoFx1MDAwNYviithcdTAwMTLeyTXMwMOAq1x1MDAwMi2XXHUwMDBla3u3wWi0O/q5c3F4nsaDhVhcblxm0lx1MDAxM4YgXHUwMDFlOFFcZtQktUluPFx1MDAwNFx1MDAwMqVcdTAwMDYovkg6NiOe0pSh+1x1MDAwNMY4odqYNFx1MDAxY/AgxFxibaQmiFx1MDAxOJHA1FxuXHJLoeHszWjA4Fx1MDAwZe+1SoiOXHUwMDA0XHUwMDE4XHUwMDE4z1x1MDAwNFx1MDAwM8dwRKLYoe9ccobx2ze9YFpTx//7XHUwMDEyW1b0x/j///7Tufd6tjnbV8qQ4+Ol7mKnPlxiN3vX10GIJ3psXHUwMDA3mdIsYb1cdTAwMWZu4G1ccrqt6W1+t5mxJfpUwVxu6bZfT1x1MDAxOVx0fm5621xmt7FzJM9ONblcdTAwMTl+K6ybXHUwMDFmh1x1MDAwNXKyP7yay21QKZBcdTAwMDXRXyhJXHUwMDA0Z2SKRFx1MDAwNfW0JNqg44xo1OU2XGZcdTAwMDWNYphcbpCA0thcdTAwMTn+XHUwMDEy0Fx1MDAxNNAzod9GXHUwMDFiWbHo2/yGfrvbQJ7Am51cZnFiv4G3O9NvoC1cdTAwMTjNzMeR17m7jVxmc7avtCF/cr+x1Vx1MDAxOLBRYad2tlX5VtrYMsO66cL88luiczCCSSbwj6lgWmpcdTAwMTTnXHUwMDAynS9FgkKqWcnvj+A43i6/qUZ9aUHgXHUwMDEyXHUwMDFjXHRcdTAwMWKYdlx1MDAxY3irKceI7d1cdTAwMDXHXGY4sMui6F+Yeun2QJ1Vv5e39/drN/PnklFqRNkhheEm4SbhRaOJXHUwMDExXHUwMDAznkC3wohSXFwl0+7j6Vx1MDAxMeJcdTAwMDHRwsBzJpmmXHUwMDExXHUwMDAxnyS51KxcdTAwMGba/u/NLlx1MDAxZL09hcyFTfQr7co66excZjJl6Fx1MDAwMunEXHL/5Vx1MDAxOeRcdTAwMDLfrt2dnJw2XHUwMDA3e5XSaeVWfz8+Wywjq/CHZPlcdTAwMDDHPZpcdTAwMTRwXHUwMDFjXHUwMDE5WVx1MDAwMIJSnDOU4kaj7U9BRr9cdTAwMDJcdTAwMTnJPcvSyNWIPlx1MDAxNPyfOFx1MDAxZvtcdTAwMGWQOV5gXHUwMDEykaGVo1tK51csNli2YJSIKlx1MDAwZWo5bORpvrE1Pic1q+VKofBcdTAwMTGSsa/QznQydmrc+SRiXHUwMDBmNlx1MDAxYeHlaVNcdTAwMWbVO+XWYzVcdTAwMThcdTAwMDb91lxciSdthMeRgkFqSiTVk1xiXHUwMDE21M7nXHUwMDEztFx1MDAwMiRGIWV6Rlx1MDAwNa3K46j/tMBcdTAwMWa2NMBcdTAwMDFhjFI9jSEop5IrTkF8XHUwMDEySP/+KZZSXHUwMDBlkSFcdTAwMTNcdTAwMTjjUVdCXHTFfibOqcR4klximjfOmXX+NGnT71x1MDAxMlx1MDAxOWabqX2lXHI0Pl6KiT9DYHhFdveCPb/SfujWz/pcdTAwMGbte3V1cDK3XHUwMDEy1pR7Rlx1MDAxYkLQJVx1MDAxMKpoXHUwMDFjtkWszjV6XGbAKFx1MDAxYWM7SSCdVMJcdTAwMTjD09FW9qSEY+L/ZUr4XHUwMDFml9Hrk+P/fH5G51pIjOOkXHUwMDEz6ImRTFx1MDAwMV1cdTAwMTjFpSG/UepuXHUwMDE16vu3g2qX1jbaQXm4tVWsto5cdTAwMTeTuqBAxDb0JqnrXHUwMDFlzVx1MDAxY1JXM+1cdTAwMTHCrMiVWidcdTAwMGJSXCJQXGJ4XHJcdTAwMTRCe5pcdTAwMWFElaBcZj2xo84ob6n7v4GJ6vyYYERaJ86NS+WKdFx1MDAwMV1cXHZAjeRcdTAwMTTJIW+Vu6jlplTuln/T6Y3sd32pdIPwI+jdV8hlWu9mnkE+yrdFrk/PXHUwMDA2XHUwMDFiW6CGXHUwMDE3JDw9lZxt8XlzoMCMp5TE+Fx1MDAxZlA1XHUwMDExPpVcdTAwMDM1xEOYXHTBXHUwMDE1QVmQ4MExqJUnuZ6oXGaM0YzbXHUwMDE01XlcdTAwMTdccv5mUMdjXSThXHTD+mBTso3N+2pF0tOgWPTPkrVoa6Ojy7vyI7k9blx1MDAxZFQ2N7t3P69cdTAwMGW+9pfIiH6f3zlcdTAwMThALUqTU2Sxb1x1MDAwMJpZRVx1MDAwYsiYtnx0qfLCWcJYW8mdXHUwMDBmqW2fnsKBXzhpP55cdTAwMWbRO+L7XHUwMDFig4KD1Fx1MDAxY/OHwIlHUK9xo51cdTAwMDCwxbFgTVxcuFx1MDAwMWBcdTAwMTiSXCKYiXLxMVx1MDAwMojHXGLQT85qXHUwMDFmXHUwMDFjXHUwMDAwtflcdTAwMDFANdVcdTAwMDTvomvOXHUwMDEwWGZ6VFxiZYDQ3DNAXHUwMDBiXHUwMDAz4NdEhlx1MDAwZVx1MDAwYrav9Vx1MDAxN+ONP5+6OZ8hXHUwMDEy3GidboZicHG+397f3bvQ15c96M5Lj5yBR2ylLdhcIlx1MDAwMpd30EzirTRcdTAwMTneYUWP7+xcdTAwMWTq83tcdTAwMDdBXHUwMDE1Mlx1MDAwMGeu+ntcdTAwMTCpd2PpLCk1gsjcJ1x1MDAwNnPkx9Luj/WjenW3tXu336zdfT05eFx1MDAxNPOV5Vx1MDAwMWiPK8E1pVx1MDAwZVx1MDAwNKD9eqC1yUZcdTAwMDBCXHUwMDA0UD+6S81RXTJKTO7rSlZcdTAwMThIYuDHXHUwMDAyXGZJXHUwMDA1cC2EdoGAzSirYVRcdTAwMDLRkuVem/pBODJtxfa1XHUwMDFlXHUwMDFi8CdnSYnkV6p1y3fbJ+d3P1x1MDAxZb9cdTAwMGZcdTAwMDfBsZxvXHUwMDAyVKBQMEpxqVx1MDAwMFx1MDAxY1x1MDAxNMk8Qe20NNHM6SCkirJCkmKQqXFXR6r0f2FcdTAwMDZ0OVx1MDAxZpGXXHUwMDBiaCyQQqJ2MYLWriiRmcyCXFyEgZKKJWfA35xY/YjLumJIPGd3vt5FJ1x1MDAxMd+LpdNSXHUwMDFk/zJhj6mkVNhLzKlOZqQmxjudfppcdTAwMWVgPlmn7eNvj1x1MDAxN6MhXHUwMDFm1b7dXHUwMDBmXHUwMDAzMvxqmFxm5p9gIaictS3oXHUwMDE3YFx1MDAxN0pPrWOT2jN24lx1MDAwNFBV4G/jXHUwMDEw1qtVqzlkli/dbmGRkiOlOWUkufg0sdCNk0zZXGa2/Fx1MDAxNOPL37hotVX+QVT/uqBq/nFrv392UFx0TXUhb2HNXHJyqtVzj2ZcdTAwMGXG1aBsylZcdTAwMDNcdTAwMDMujFwik1V6XFxcdTAwMTlPaPTEXGa3uLEjjcdcdTAwMTE7oFDcK9T0n5hxfz9kWlx1MDAwYohpQoldyESdi0BZukovXtyiXHUwMDE5UFREuadcXFx1MDAxN7XfXHUwMDE02/X9u8C/z4Xu3jpcdTAwMGLzXG5cdTAwMDNN02Bq5Pnw4GXws3vky3Cjsrm/d9LyK9s792KBklvJPFv/x1X04pOVXHUwMDA2XFxxj0jEsVx1MDAxMZJcIqRcdTAwMWRcdTAwMDWEK1wizFx1MDAwM9XtN1x1MDAxMyFDV1x1MDAwYlx1MDAwNNWMXHUwMDEz7Dy9TiWeXlx1MDAxMVxuWLJcdTAwMDLxl1x1MDAxM2FcdTAwMTWOTkj7olGT181OUKhUiyUm37t7w+3xRri+V1xytnZape3Bzk31W1ls5SXzmaFU8iTAlydu99Wbg7jRXG6oZ4ixXHUwMDBiSjDwkGpcbuyae9IojIKBZoBcdTAwMWSdXHUwMDA1VcagPsZcdTAwMWa2R9GKuefHeLBcdTAwMDBzS6ZcZvrdjI5DekZcdTAwMWSF4WbSXHUwMDBi5MTc0q78flx1MDAwYnP3+k1cdTAwMWPrRyDuVyhzmrinXHUwMDA3nlx1MDAwZm//PFRcdTAwMGal7vpeYb9cdTAwMGaDVoVcdTAwMWQ2rpqlNIpdK06Z8GyNjeTIuFxiwslcdTAwMTWnglx1MDAwMepvqimNpozAUTDsaYlcdTAwMDZcIlx0R4FcdTAwMDdaKMeKU0rRU1BkfYNBkkR1sOojloHqn28vXHUwMDE4NppjsOReXHUwMDE5QGfkvFx1MDAwNeoyzbRcXCbjNXE2XHUwMDFmdTVptqXaV9pG4+OlXHUwMDE4+TPkwOtFsnW+XmTfqv6oVdlU3++a/TmLSIzyQKJHXHUwMDAwIJrhpZhyXHTMTqGBoLaPi1x1MDAxNumInFx04zGC5Fx1MDAwMSjnhdSOqTL0J1x1MDAxZTCDL9RcdTAwMDaA6mDF81x1MDAxOVx1MDAxZeHqzVx1MDAxZYFRXHUwMDA0oWLGuY5cdTAwMGUvfaZHYIZHa69/gUdAXHUwMDE4mqXqKPNdX55pqfaVttFP7lx1MDAxMWbHOlPxyKRLYFx1MDAwNDxGiVBWunGYztJcdTAwMTnjXHQhOWpcdM2iNVx1MDAxNymfQFx1MDAwNVx1MDAxZVx1MDAwMP1cdTAwMDVcYkC5XHUwMDBmhjlcdTAwMDJ7ajtEYaBgxSpcdTAwMDZcclxcxFxuceVcdTAwMTQmnEInXHUwMDA3p4Amj7cho6VbdqdcdTAwMWGJN1BM9CPKzSksXHUwMDFkXHUwMDE05OpcdTAwMTSyLdW+1tNGmqdXSNzB567B8zSIi1wincYwmpj1XGJG41x1MDAxYThKelR0XHUwMDE0XHUwMDEyLm2tVb/BXaTHKWoyhfyOvzlP28iEO8pcdTAwMWHS7LhnckiEodyyU+bW19raXG5wjFxubFx1MDAxYlx1MDAwZnQtXHUwMDAyY06lddpyXHUwMDE3cYXTXszv/Ojdz5VcdTAwMTDpXHUwMDFmqetNWl2vXHUwMDFmXHUwMDA0hJPzn5WLdrud9pKOhFxiQ2VkXHKCXHUwMDEzPGE81ensJ1iuXHUwMDAxW1ul8dIn5orG1UXKI4B0I58zoGlcdTAwMTeZiG1XLnHCJV4vtqyEMkmcXHUwMDExksh2fUxcdTAwMTOtqFwiy2RDZrs+zrShi3Rgjs3xOa3Q6Pt4yC9RdsGZXHUwMDE1SVTEv+PsvXuY+eRAZqdcXGfKXHUwMDFizoVnojlcdCpR6E1cdTAwMDc8tj2ZXHUwMDE2RFxiLW3FdVxut4DxXHUwMDEyoFRmiH5hILFaLNY2yCraukEmOeGCrzIgXHUwMDE5OO7Oj2MqbHdcdTAwMTaulFx1MDAxM8jZaU3D0fmi9F9mjuJcdTAwMTVcdGOE0kvhOFdcdDOxt0LjZFx1MDAxMknVWONjSr326fVMc/5cdTAwMDWHS+Hid6ipuaWLrYjUUnIhiZRghG1zkVIu2rNSiuApXGKiKSqX5IGCh3ioXHUwMDEzt8hcdTAwMTOGalx1MDAwMqCkpVx1MDAxNTLZXHUwMDEwkeLXXHUwMDFhbpf+U4lfL1x1MDAwMVKXJUOtLVx1MDAxZjxmK6ZcdI9cdTAwMWOdUdlvOVx1MDAwMVx1MDAxMX1hcVC+8Vx1MDAxYlx1MDAwMVx1MDAwNinpL5zeNsOdzy6Um+nOQUYyWFx1MDAxOaWp1jKxfPSpXHUwMDE1mvBsNULUQ1EoV7BqqCds4otI/EeIclx1MDAwNas2TIi6llPUdPyzTEa/Q1ub3vxcdTAwMWVcdTAwMWQwYDGEXHUwMDFiV1BqMpf04X002lxikb8/XHUwMDA3+2yM91x1MDAwZkkn9l5X2uNcdTAwMWFjUFSx6DZcdTAwMDTlr318Pduef81cdTAwMDFT2PhcdTAwMWROfXbN92xfjFx1MDAwMlx1MDAxMENhbeerQaPPVVx1MDAxM6dDPKYkQfmvbHNEQFx1MDAwZXNdoDjeTTHEy1xyXjDCfj3ozz4jZaS2/lx1MDAwZkN8YXSc+rUv1NHMcFxmMpFbhCZUv/l0PjXR3MLljt9cdK7rQbBbOyS7VXLub85XXHUwMDAxwYQnXGLyNNeColx1MDAxMp2kXHUwMDE5ZllcdTAwMWOQ5DVcdTAwMDBPls2NaYbY9XRcbnVcdTAwMDDGlFRR7YhcdTAwMWI+S8D/XHUwMDBlvHIzP68oKpEg0u2S1mYuXHUwMDAzYlxu7y0oyD/VXHUwMDE5VUkt1UXpJd4/slx1MDAxMfRmXHUwMDFibVwiedU/XFy871x1MDAxZWY+8f51v6NO4Eepb453drbPN8qV/Y5jOsOVqKN4aI3wlUh6Qk22fpGMWXmI+lx1MDAxMW1UKFx1MDAxN26BeVx1MDAwNMNcdTAwMDGCQc1Tf9BcdTAwMTVuXHUwMDE3wO3t/LhcdTAwMTVcdTAwMTQvMFHc2Vx1MDAxNjtTXHUwMDBmXHUwMDFhVPxEc75MV+zXcMuSJejL1C11v3xcdTAwMTLoZo40XHUwMDFm9O6ellx1MDAwZWvhYbVdK7VJu7kjR61+PY3erEpjbtmV4N3A8JlD8mF6T51KXHUwMDE5alx1MDAxYy5cdTAwMDSyL0FAO1xujUHblXxcdTAwMTgjWlx1MDAxY6tEXHUwMDEzhURLM/RcdTAwMDO2U4LN+tomT6tcdTAwMTAvXHUwMDBi0n03pFx1MDAxNyg45nZcdTAwMDLeSdBcdTAwMThCZC4tsOtFNF9yne54cFx1MDAwYlVcdTAwMWKfbDSrP4OrXHUwMDEyuyhfYfzTLIR7d99cdTAwMTbs9MuEymnZjXs082lX7dme8dqAXHUwMDE2MFWpbyuAXHUwMDA02Fx1MDAwNqJ2XCLRaObIkTDu2SSgXHUwMDAx+0BL2yXNQYIrxGQhZjA/XHQylFwinClIV+o8oSMzz81cdTAwMTjYKVed97qbhVxyOMWCW/4gRK5cdHGwXHUwMDFmoob3XHUwMDE1Mkq3QHNccj9cdTAwMWZeLG1Vu9fF4Fx1MDAwMX8x8vWRXHUwMDE0u1x1MDAxN1CZm1x1MDAxN7Vt8MtcdEpXsF1cctnU8nVJPIFb0dVcIn+Co3TPdk5cdTAwMDUlteBUSSNFojwk5kWF4tlcdTAwMTDUV1IpZODYXHUwMDEyPjbIezbD9HaQx5xWfFx1MDAxY+2WNtTm9uWmf1x1MDAxNfo/arunncJcdTAwMTLL2cM3s6e27dpNxmpcdTAwMWQw2a3yjUQnL1x1MDAxNM+70YWyXHUwMDFk5HN6pPDobnhZvr07O2jrXHUwMDAzcXW8N7o+vXK0PMpChLJdjezjXHUwMDAwJFx1MDAxMZBseVx1MDAxZCHCbrX5MUq5XHUwMDA2lpCR8Yo0j9nJX6NA20fQMIdSxH2UxsNcdTAwMWI8POe4XHUwMDEz/Vx1MDAxY5DI/cEoeSFiuMAksDJcdTAwMDRlPHU+clxyI4TsTkeM20eE0KXKOX6X7V/3XHUwMDFlh49h81a0xU7N3MPDaHQ2V45cdTAwMDOJ3zNcdTAwMDa4pFx1MDAxNO07WbTwRFx1MDAwNNKjXHUwMDFjf1x1MDAxYZSATKl0KVx1MDAxMiBcdTAwMGbYZ6eQaHtSXHUwMDFmfjp192Gt/G5cdTAwMDEr12hVQJRydTJSXCIzQKKKUlxmkWCpubFcXI08pVx1MDAwMSvFj9xmZGJ0+Si7fWZ6jTP/sUX4fneTXHUwMDFldnbu+ntcdTAwMGIoO+EpKe1jVVx1MDAxNGBsNqXsbDTG0Zs9LdVytVx1MDAxOLHFiVxiZDRcdTAwMDdcIjFmNo7JXHUwMDA2ZZ9ZamHPpK3wSDy0dFx1MDAwNfBlXHUwMDAwfv9mYYdOXHUwMDFhVYtt2enAPcZ22Vx1MDAwZvhcIlx1MDAwNnU+W7LP7XiAXHUwMDBipUaGu+K+Ubx74Oe3Ye1Ql1x1MDAwNyVfNFx1MDAxNkyN5Eed7tHMRZ3GU1x1MDAwMIhcdTAwMTJj+1xmiGmkmVeQZlAxXHUwMDAyYsjQ7DLeXHUwMDE1db5ccllcdTAwMGZcdTAwMGJQJ0TTOFx1MDAxOVx1MDAxMGLpuYVcdTAwMTdcYmmlXHTGwmKZaYRfS531m1x1MDAxYjzrMKPSN+/cyVxmXG59jdOmWdUx8Hy49WqzS47C036hXzrUdfgpW1x1MDAwZmb+viVcYnQvmoW3vUuEoJNcdTAwMGZNsjFcInphRjFcYiTGVbe/ilx1MDAxMd/BXHUwMDA1jFx1MDAxNnBcdTAwMDHEcK6Vcj5cXFx1MDAwNVx1MDAwNVF2jIjUXHUwMDBiv6JcdTAwMWJunkT3ba92QDZcdTAwMGW3Tbt691gsr9dcdTAwMWFb5f25bd/YVY54ioaDUizZxunF9pWyne3Ats0lq/zIh7D9x0VcIkdbpFxyLvKjTGYvdjGCXHUwMDExbnjuj5Ze0vL/eL4+lkLKIVx1MDAxZXKso9dsI6yN7K7Jfzzjxlx1MDAxYYRcdTAwMWbJ77//+Pu/XHUwMDFmXjB9In0=bookstoreXSUAADeployment UnitEventsreviewsorderscreate orderOrderChangedon OrderChangedDestinationsUIsapprouter \ No newline at end of file diff --git a/guides/deployment/assets/microservices/complex.excalidraw.svg b/guides/deployment/assets/microservices/complex.excalidraw.svg new file mode 100644 index 0000000000..ef479520df --- /dev/null +++ b/guides/deployment/assets/microservices/complex.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d2VLjyLZ976+o4Lw22TlcdTAwMGZcdTAwMWRx44ZcdTAwMDEzXHUwMDFizGBcdTAwMWK4cYIwtsBcdTAwMDJPeFx1MDAwMnOi//3sNGDJUlxuLCOqoK5cdTAwMTXR1VWSh7S01t5rXHUwMDBmmfmfP378WFx1MDAxOYy73srfP1a8x1q16dd71YeVP+35kdfr+502XFyik3/3O8NebfLKxmDQ7f/911+tau/OXHUwMDFidJvVmodGfn9YbfZcdTAwMDfDut9BtU7rL3/gtfr/a/88qLa8/+l2WvVBXHUwMDBmXHUwMDA1X7Lq1f1Bp/f8XV7Ta3ntQVx1MDAxZj79/+DfP378Z/InXFzx6/ZcdTAwMWJ3WHmjPSw2T4t8J58/6yuuTszkrZNcdTAwMTe9/oSeV1x1MDAxYlTbN00vuPRcYuelYUgxzKVWYnphXGZcdTAwMTeYpMhcdTAwMTCOJVxyzj/49UFcdTAwMDOuaY2UXHUwMDE0cFx1MDAwNT9cdTAwMWZ8+oqG5980XHUwMDA29r5cYoJcdTAwMDRcdTAwMTHhdz9/+98/8PRMf9Dr3HnrnSb8Vlx1MDAxOOK/iFGkRoNcdTAwMDFeVWt3N73OsF2fvmbQq7b73WpcdTAwMGbuSPC6a7/ZPFx1MDAxOYwnn1x1MDAwZc9cdTAwMDJu4krkOyovI6eR80nvgi+9abS9vr3rZHq2063W/IG9O1x1MDAwNFx1MDAwN7/CjrC7U588oH9cdTAwMDdj6sGj3bFPqD1sNqen/Xbds/d9pUpmvq1df/m2mVf3Pc9+XHUwMDAy0ZpLrIxk0ytcdTAwMDFcdTAwMDJcclx1MDAxMdGzXHUwMDA3nfZcdTAwMDSNhFCDiTGhh+D3N1x1MDAwMFCDycdeXHUwMDAzKL3gZttB5ENgXHUwMDBifsuwW68+v4UoTpnWxlxiw4I70PTbd9GhNzu1u+BbJmf/+dOF31xcy8Pe+cXDXqP9tF5cdTAwMTdtfZTbX5tcdTAwMWK/mlx1MDAxMaSwwozQWfxyQlx1MDAxMMNcdTAwMWHGqeJcdTAwMDAmnCB7X1xioH6CYFx1MDAxNkcwl4gqxsNvX1wieFx1MDAwNsHUjeDX51x1MDAxYjw89nLmn1x1MDAxOLCZMFx1MDAxMlPBjVx1MDAwM9ha4SRgg1lS0lx1MDAxMMlcdTAwMTfB9XR4wUBDOFx1MDAxYniPwXNcdMF0t3hUW1vdXHUwMDFh5Hhlr3y/vl28fipdrExf98/L31x1MDAxMklcdTAwMDO2XHUwMDE0sEZ5NqRxjyZGmplcdTAwMWbzzFx1MDAxN82RxlJcbqE1U9qQWdJQglxmXHUwMDE3M2R65Vxm48jAIVxy15RPmOOy+kuuuLnC5rf2XGZuraGYXHUwMDEyXHUwMDA3J1x1MDAxNKVJnFCEMEqUxotwXCJL2Fx1MDAwNii06LNcdTAwMTa+21xyPb1Oe3DiP9lcdTAwMTFTPHN2s9rym/Z2i5lPyDX9XHUwMDFi+9NXajBYr7dcdTAwMTL+/Vx1MDAwM1x1MDAxZiTT9Fx1MDAwNS2/Xlx1MDAwZvuHXHUwMDFhfGjVb3u9nXlcdTAwMWNNp+ff+O1q89Q16upw0Dn2+s/jXHUwMDFl9IZe+H5426/4J4iKN1i7euSPjtfU+lBfl/rNXFxxW1xyNp7irPWaTb/bjzg6wKBGIFx1MDAwNDSlXHUwMDFjaItVXHUwMDAwhMk9U1x1MDAwNGnJqHZRVyokJWFcXEjFXGKXJHhrwFxcjlx1MDAwNGg6RTUmlFx1MDAwYqqDJ/7/gcmBy6Lkeuf6aXw2aPdKZ5ekUz6lQOGYdX+X8GprXu9Ik7wj2Fx1MDAwMFxuXHUwMDBmhFx1MDAxMpfsU1omWVx1MDAwMlx1MDAwM/aDXHUwMDBirH+1IXiDXHR9rnLk5OjgYvfeXHUwMDFmj7fHt1uXXHUwMDA3lThcdTAwMTMsuWI0kFx1MDAxMHxgo4BcdTAwMGbEYCNnaaBcdTAwMTkyklx1MDAxYc2YwpxKXHUwMDE541x1MDAwMkZcbjiilGJAJExAyjqkn0HYXGJtpFx1MDAwNl1cdO7QLLnwMS6UP8xcdTAwMDVi5VxusCFcdTAwMTbrWCpIlVx1MDAxOFx1MDAwMVx0MGVCSLyQUvxcdTAwMTSv2O34USFcdTAwMWH87UeArMk/pn//95/OV68mw9lcdTAwMWUxIFx1MDAwN59cdTAwMTd7is1qf7DeabX8XHUwMDAx/NCiXHUwMDFkZEyfXGaqvcFcdTAwMWE8Vr99XHUwMDEzvea161x0VybvyvV6nYeGV42BXHUwMDA03lx1MDAxN732htnYOpTlU427w73cqrk6yOHj/eHdfGZDXHRcdTAwMDRGgTFcZlx1MDAxZVx1MDAwZZvAoj5bXHKJXGLmSlx1MDAxMlx1MDAwMzeTsoAtIathXGLTXHUwMDE4nKdgklx1MDAxOeNIeUDAiJkmTHH4XHUwMDBlKsPwWJqNRcyG/rjVwERRLFx1MDAxOI5cdTAwMTlcYms2eGJ8SVx0ZZJgbYKb9tuZjVx1MDAwNDzbI47kb243Nmp9Os5tXZQ3SntHa1x1MDAxYmZYNW02v/BWXGJzMFx1MDAwZlx1MDAwMFx1MDAwN6FcdTAwMTSZzTNJSlx1MDAxMTVcdTAwMDBcdTAwMTZrZiVcXF6q769gOj6uvonmWFlcdTAwMTY4TIdmiTlXzSTQPPxLv576ptc7ondpqkf3XHUwMDA1VT47P9nc37/oxumQkHIlWFx1MDAxMlx1MDAxNE23SkMh/lQzaaFpuYCjl4zRc7nAobjZN0lcdTAwMWPVq/2G93MzR4dcdTAwMWbPsnJhIVx0QtFcdTAwMDFlSWJnp+JcdTAwMTlcdTAwMWJCwHAtXHUwMDE0SE6HlyrLmuObXHUwMDE3o+Pj03p/t3R0WrrX58XyQ7osq1x1MDAxNlx1MDAxMPtlw1x1MDAxM/doYjyJZ1lBbmJkKNxbaThjXCIkXHUwMDE3n8miXHUwMDEyyVwiObL+XHUwMDE4vDKJZHO+XZb1XHUwMDE3kKWYoqhcdTAwMDZcIs9WMLnLwEucrFxyMaNcdTAwMTBx0swjyrTAXHJw+JKxPDsp5XJfIdP6jn+JZloj484m11pYq1xyrk/r+rDaPLl5OvOHfu9mrlxmk1x1MDAwMblcdTAwMTct/E1qI0IjncRaUG1I4pla45SuXHUwMDEwR8BQ1Vxml780b99QdcF4M6Xt0cfVmmSEcEOwq5LIQ/Y3XHUwMDE26YHC4yZ7Nn+NQC+Oy8nZXHUwMDAwkcFcdTAwMDfEXFzrd1xi7I42ztqtXHUwMDFk/1x1MDAxMf5Hcf5cdO+0L1kpRfNcdTAwMGJHsz0qk85cdTAwMTc4a3hC61x1MDAwYtfIYDprXHUwMDFkXHUwMDAypmuDXCKtNF+a6PWOfbQ/1UFcdTAwMWZ/WM1qqY1cdTAwMDZEu8Qs5YliXHUwMDE2wndcdTAwMDFPTn7luOzAlOtlebi/cXNf9Fx1MDAwNzv1boHf36RAM0VGO1q5ODPxXHUwMDFlmWlZnyCBmcHTbi5HZpNR9Jv4r09C9UlcbtlcdLLeXGKjpKuSXHUwMDAxaEpcZsZcdTAwMDTXmukv4KneXHUwMDAw8L7al9WxrJiHXFxhs7F5erOl6zhcdTAwMTWAXHUwMDA1YVx1MDAxMYk1QbCmKKG2vcRvXHUwMDA2+D1NXHUwMDEzNlx0Q1x0XHUwMDA3JLrsb1xcflx1MDAwNfjFyigw3L88pf5cdTAwMDZ+x6Ph9cn9qFxcaOiCuCvujlund+1UvbSESiNizYiCqrhsWFwiOENcdTAwMDSXUiBYXHUwMDE5XGZgJM5aMjE6XHUwMDE5wVx1MDAwNHNBPqGbVnBKM0pZtTpPw6dB/V40xNaFeWCP43HZUemIp6ykMUhKXHUwMDFjl8NcdTAwMWOCXHUwMDA1qVwi6JtiV1wiJoxtxjBUQdDs7Fx1MDAwNF9i1o3ZhK5cYndcdTAwMDe4ooZhpZzhbWIxQnBcdTAwMDFvM4v1yX5qT2Bpp59FpqrpXYdQXHUwMDEwy1NccjqhtNNskmpmsNGM1MzosslH3eHtXX/XKzVcdTAwMWXb1XLvsfGg7lxux3O7XHUwMDE2hVx1MDAwNdJEzsakk2CVXHUwMDAzXHUwMDAznW5FYMRcdTAwMTidiP6JV1x0QPxpVZd/XU+Ob07Lyvy05FpIXHUwMDA2z8XlSYSOJZanNUJcdTAwMGVcIlxunlxc8Fx1MDAwMz69sLKRq+7f98/a5GKt4Z9cZjc2ds5uiilcbivaYExcdTAwMTWTYewv7qXco5nDSylcdTAwMDI80JQzgplcdTAwMWTPbF2FXHSSQFx1MDAwNobtfFx1MDAwZlx1MDAwM49KR/vbP81R/Vx1MDAxZWw4m59cclx1MDAxNENcdTAwMDTHXHQ3roqK4In9qpxAQGxzOtnqqvSIjXmpjVJcdTAwMTZO6qPllHdcXEfUeYVcdTAwMDedje9cdTAwMTLHebo78Fx1MDAwZve3xvleP39SW8V0/rxcdTAwMTSIQ2RDP1x1MDAxMdeWUlx1MDAxOcS5olK4cq1KSWRIRJJO6Specq3cPFx1MDAxZoGAXsrMXHUwMDE5XHUwMDA2n8/PYMUxU5RjZ2REZWJoJFx1MDAxOMSv4FxmXHUwMDAzdZpccoOJzVx1MDAwNZkwZlx1MDAxN/c5m73B6vnd6HCrtDXK3W9dNLz1tfP5QUwoep4xXHUwMDE1k2BSwiVcdTAwMTVccvuXMuyDsL2YXHUwMDFmtlx1MDAwNCsp7NRcdTAwMThXTUDTRFx1MDAxZEa44OB2XHUwMDE4/olCrLu3W3wqXHUwMDFmn++QvVa5XWrsXFxcdTAwMWVcZmU6IVx1MDAwNmhcbrWufohcdTAwMTTu0cwjxChFhoOgYiBmVVSISaneoMRSjC3KiWpcbk4wcKzYSjJcdTAwMDcnZHKWi1x1MDAxMlx1MDAwZVx1MDAwZlRIlXGWKz1sv6hcdTAwMWF7x498vlx1MDAxYTss7lbu97y7XHUwMDFibdY3VaW7ZiprXHUwMDBlNeZuZqbEIEZZdFx1MDAwNu9zV5pAnCgjnVxyLlxuSa7dZW9cbtdmazbfkbJpWlum5nqleHJ5ID1WPzmRzfJFcfPycJVcdTAwMWSH7P6K75f11v3hfelgW6rty+K+f9vuLtDRXFxLk+BcdTAwMTZKXGKKtUvG0eTONiCp4VpmXHUwMDFlh1x1MDAxMYVZaCbFh1x1MDAxY9bh6Vx1MDAwNW6tnXuXZ6TY2dzDVMq9q3naulxipVx1MDAwNlx0KTg1sVx1MDAwMrk0XHUwMDEyJTV8XHUwMDE4ivBLdSaOfIzobFV9XHT8zIFfn1x1MDAxZvhcdTAwMWFAr1x1MDAxOHW6PEpcdTAwMTJnznNcdTAwMTCBgHr2y3E/PZ1pXHUwMDBmmFx1MDAwM8H2WH1cdTAwMDVv8P7Yw/lcdTAwMGUtYI38QWfcJEfNrj66zl+O6MX6/u287lx1MDAxMH4/XHUwMDEyz6WraL+nbdKONNMsneFcdTAwMTexXHReXG5niJmQVEm3UYg3f09jQ1x0poRQxlx1MDAxN4pccn+SN9yq3JWv+lx1MDAxN0/Htf3e+rDQ1KWrh/qc3lAjTCmPNC2PJ6FcdTAwMWSL9jJPnSFBTIhZUoScIajL2fctoZ859K9TQJ/aTjOCqVx1MDAwYvokPlx1MDAxYmhcbn0lmDGU/nLkf5I/jIPYXHUwMDFlq1x1MDAwMX6/uUdcdTAwMWPk9nrkKVdcdTAwMTXbT/qAXHKOh49jtTpPUlx1MDAwN8ydQTqekH91h4q9scKMJsZIXCL4c6Lf4Vx1MDAxOL9JXHUwMDFmyFuzllx1MDAxNrNccllR/yZcdTAwMDX1XHUwMDE5XHUwMDE3WnDinPhOVGKXqWBaMUHF15HCr8mf/GjyNcHd+mo9I9FcdTAwMDFmk+zpXVb1KeejSlx0V82IXHUwMDFkXHUwMDFkPPFNXHUwMDA3l5OqXHUwMDE2XHUwMDA2Yl5FqKNqXHUwMDAxVEZ2fr+7Xk6JRlRcdTAwMTLlXuZccmRzXFxcdTAwMDd8aVL//FRtI42fJkIrsJ3Oslx1MDAxYiGJk5eIsClcdTAwMWKpZOaVc82IzqjXo01PXHUwMDFl6+eVY72+3ezf+uVcdTAwMDbd2Zq/J1xcXHUwMDEzguzCXGJcdTAwMGWPxLhCRL6s4lx1MDAxOUPwsu62IHD9XHUwMDE0wFx1MDAxNZhTzXR8/aWVSVk0XHUwMDExt0xyLmQ4QfnpdbfjfIXSfO3hZrNYz7dy5yVWZK10dTfJXHUwMDE5zqjR3D2aOSSaplx1MDAwNEGARlx1MDAwNXj4eN1cclxc91x1MDAxYpRY1t1cdTAwMTblxG1cbk5wwjDgO76sguVEvOc8mFZcdTAwMGVmXFxKXHUwMDFkmlx1MDAwMp6RLU9cdTAwMGLbmPT6XHUwMDFhdbd3/Mjn1930w9lo78S/Or3CXHUwMDE1dnnQuT7tqOrcjsxQjsCVRyeCT25cdTAwMTiILW2iXHUwMDE5xWmy0UiEyUSK8UijU0iQXHUwMDA101Ij61kvZdlcZpPv5meymtxR7mwqIeyNXG46tnV3qnjWTFx1MDAwNtktcUbdUFt+Ya+3092vXHUwMDE299dcdTAwMWZcbk1Z0Fx1MDAwNVGbXHUwMDFmzExcIlxumso11Vx026l60dBhqco+iNtmqvIvtcuIMfeid8m7XHUwMDA1SEpAQFx1MDAxOL1cdTAwMTBuXHUwMDE3U2XD0cg/q6z1ypdcdTAwMTdcdTAwMDdblby3e4vLJpUqy5JcdTAwMTTu0cyhyozdLFx1MDAwMFx1MDAwNC99nVx1MDAwN1x1MDAxNaGEeoNcdTAwMTJLVbYoJ1opOEGx5ppqZ2erwslz/ijYclx1MDAwNS47887WtLD9oqrsXHUwMDFkP/JcdTAwMTO6ofztw1xc9zH/yLuNPWnG40vccqzzk7yWXHUwMDFkRVx1MDAxY7uaQjjBXGKDXHUwMDFlX+4g8nlcZm67XHUwMDE5nGZtO4w15Vx0rb8meTlcdTAwMTDwj5iKcP33033d5XatJtjA3Jw0XHUwMDBiR7hSvL1ty910vlx1MDAwZWvNMypcdTAwMWS7RzOHryOEiMmEdUpcdTAwMDTWKtSe/7yDXGJGyk6vdk9cdTAwMTde7iHyY3G2dFL4Oy0gbtTOXHUwMDBlQFx1MDAxNZ+fNVxyXajEeGbB+4y8XVrgxrzdXHUwMDE32UPkXHUwMDFkb/NcdTAwMTP2XHUwMDEwXHUwMDE5XHUwMDE11mmnsNZvlvP1s8JBlzfGXHUwMDFimyn8XHUwMDFkyE0qdbR487rj27JcdTAwMTL0/Fx1MDAxY7OkbTdcdTAwMDVtXHUwMDE5YVJy6ZxBSYhO1KlKXHUwMDEyXHUwMDA2z4EslFD/SVx1MDAxZefg8dy/f3hcXD3uXHUwMDFmXZn+Re+hkdOHKZDLKUCNc1x1MDAxM+9cXGdcXCBD3Y3ry4zDgrC9T1x1MDAwMVvC4a5o9zZu4Sm48YyDrVx1MDAwMpmfOP1qVHo4XFzbrz2Wx/zy6HB8d8Z6ZfzLRJh7NPOIMCwgbqFYXHUwMDEwaaRy1IFwXCIhlvmGRVx1MDAxOdFLwVxiMDTUMKKci1x1MDAwYpvkVbKYvZvGZF7R/7hcdTAwMDL7XHUwMDFh+YZ3nMjn51x1MDAxYvauzjeuPSZcdTAwMWLkrHC/2oRcdTAwMDcw4IX5vVx1MDAxODVcdTAwMWHxmGJ6XmfJIFx1MDAwNrxOaDlf5lx1MDAxYjJgcN/N4DT5XHUwMDA2QZVRhlxiV3LdxJvMp411ymhFmVooslrM1Y3VcWVDXlRcdTAwMDXPnZbG6np4xZ5W07k6u+hkRjON3aOZx9XZVV1cdTAwMDT8oZz5XHUwMDA2YpBJXFxBYplvmJxdkC2D+f1cdTAwMWTHjGvjnnnlyM29skJiXHUwMDEwXCK2hShrb5dcdTAwMTa4MW/3RfJccu94m5+Qb9iSXHUwMDBm+db4eH1nKNd6q9U6Ld71XHUwMDFkK1x1MDAwNCT6O1x0Tsk4lyVmkiDF3a1KlIC8teI0vrpLXHUwMDEwtmFcdTAwMTh6dFnjJYVnKDxMXHUwMDEzxEkuKZXx5vBnXZpcXCOTtpYlVObTRShhoVx1MDAwNOaHvM/tZf6gfjzM6+L2zkH1avOqmJfjXHUwMDE0KFZcdTAwMWHBWKJofc09LJtcdTAwMWSyRe1oftSCXHUwMDAxoKDJaLytYcVmxZJbpzVEzYaEN/L6dEHWLlxmjlx1MDAwYni4dbOTOypsXHUwMDE3jnWf3zd/mSBzj2ZcdTAwMWVBRjXEMKDHZGLuYdnqkDEjXHUwMDFl5meEYkppSp0pZEWT15eXTDOQ2ItFKJ+qxb5G5uFcdTAwMWRcdTAwMTfy+ZlcdTAwMDe8lz+/XHUwMDE23Vq9aM4vb3M75cf8uFx1MDAxMyesY7IvhFCgtJhr/VguXHUwMDE0XHUwMDAyt1x1MDAxZpkgNN25T6DoktBB6YdgXHUwMDA0z5f+XHUwMDE2q5J/0r5Gj/PTXHUwMDE21LCGxyNdfVxmyVu9XHUwMDEzMKRcdTAwMTQkcNZrklx1MDAxYvhP6l8/V3c1jkB7hLFcdTAwMTd8RMx/fofJur5oNXP1wtnJsHreqzxcdTAwMWNcdTAwMWQ/dXLDOKtcdTAwMTOWr+BcZtnCXHUwMDBi1rZCXHUwMDFioGRiXHUwMDA2rTBcdTAwMDWBw7VduJ6FaofLjWmfz6aZw3tRL283N/imeNpvn/O1XG7hu7tPXHUwMDBizOFcdTAwMWS7LUKKnc5cZoTSyi7U6TJcdTAwMTQ02VJcdTAwMThhUzQy+zU6XHUwMDA1XHKvJfAhTVpbv3ystFx1MDAxYZWKf6T7R6PWfvNBzreeXHUwMDA143ZcdTAwMWJmbNOA2Fx1MDAxOEkjXHUwMDFkuODlYJRYYKpcdTAwMDDpXCLu6TCyXHUwMDFkS1bNQiyBwaw6NqnlXHUwMDA2YWO3edZcdTAwMTDTzXTJLLmwXGJcdTAwMTeePsxcdTAwMDVcdTAwMDKGidp1XHUwMDA1XTlcdTAwMGJcdTAwMWSf/Fx1MDAxZUzRsPu7K5G110zNhU/ymslotkdcZsff3IVu7/Pb+pE68mlxKEt3+3Jri/fnslx1MDAxYZxcdTAwMTDEJMNWXHUwMDE5a7hcdTAwMTWBj3y2XHUwMDFhxu5uXHUwMDAyMkNcdTAwMDPKJIl7ULuwNWE2uUlcdTAwMDSz2/Y69j/hXHUwMDEyYbuNuJ1sXHUwMDBm9jm8b8bSbCxgNq4w/rjdXHUwMDAwpk4o68xcdTAwMWGFotWoXHUwMDEzXHUwMDA1JUrgWZvf2HAkXHUwMDAw2lx1MDAxZXEof3PLMbpcdTAwMWGaQeVg42r3Sd7v17o3ud1TMb/4tptyge1cdTAwMTBYMFtfnDVcdTAwMWVGICFcYngoZZRcIiHZtZTfz2d/ie0gXHUwMDFmt1x1MDAxZCBcdTAwMWa5XHUwMDExRDpcdTAwMDW4ilmUV9thXHUwMDA0J1x1MDAwNpxI5rNCM9Tf90xwb/NyvXe92rxsjtj1Vpk6dlx1MDAwZnPpb1x1MDAwNdEmnexr6UoxSaGpe/9RjDSOzFx1MDAxNpjCX3PE1GcsKffbZJiuMHVcdTAwMDPaWeGTXFzY3lx1MDAxNSdy44vPhJeDI4RknmJcIkKF17b5hT4vikB7hLD3zZ1cXGljnbIz0s49+cNu5eL6orS/51hrxEFqLMHhq9hcdTAwMTYsXHUwMDEz96Ywojxp0WRQXHUwMDA3KDqnLpQ4XHUwMDA20Vx1MDAxZM1FL2k9S2uWgtaY2O1XmLMlLVnLKkylXHUwMDAxXHKTdT5cYlwia8VcdTAwMTaq92RKa1x1MDAwN1x1MDAwNO2xXHUwMDFhRt/3IfZcdTAwMWYvY1updrsnXHUwMDAzuOFTzbIy8r2HteS651x1MDAxZi9mwfLAm0idf/7457/Lj1x1MDAwMpAifQ==AppXSUAAUIsDUDUEventsDUDUAppDUAppDU \ No newline at end of file diff --git a/guides/deployment/assets/microservices/late-cut.excalidraw.svg b/guides/deployment/assets/microservices/late-cut.excalidraw.svg new file mode 100644 index 0000000000..f3d5fc9e83 --- /dev/null +++ b/guides/deployment/assets/microservices/late-cut.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d2XLqyLJ9769w+Ly21TVcdTAwMGZcdTAwMWRx41x1MDAwNp7xjPGAuXHCIYNsZDNcdTAwMTlcdNtwov/9ZmGbQSqBXHUwMDE42rBPw8OObUmIkipXrszKof7z28bGZthpept/bmx6XHUwMDFmJbfql1vu++bv5vib11xu/EZcdTAwMWROkd7fQaPdKvWurIRhM/jzjz9qbuvFXHUwMDBim1W35DlvftB2q0HYLvtccqfUqP3hh14t+F/z75lb8/6n2aiVw5Yz+JEtr+yHjdbnb3lVr+bVw1x1MDAwMO7+f/D3xsZ/ev/CXHUwMDE5v2x+MUtvduvti+rVXHUwMDA1y+7tXHUwMDE1XHUwMDAyyWRe977au+j7XHUwMDExWl4pdOtPVW9w6lx1MDAwM45zRFx1MDAxY8qk4ILw/olcdTAwMGWcoFQ4TCg5OPrul8OKOYO4o1x0l1xuXHUwMDEzjuCD+1dUPP+pXHUwMDEymkswc1x1MDAxMEf9S5DoX/M5hj83UP9IXHUwMDEwtlx1MDAxYS/eTqNcbk9cZlx1MDAwM/1cdTAwMTfWXHUwMDEyl8hgmFx1MDAwZm7p5anVaNfL/WvClltcdTAwMGaablx1MDAwYt7L4LpHv1rNh53e3WFG4FVuRn7j9utcdEjkeNK34EefKnUvMO9+8JSNplvyQ/OOMFx1MDAxYTyFXHUwMDE5YTNb7k3Tv1x1MDAwN2NqwVx1MDAwNGfNPNXb1Wr/sF8ve+btb7p45Nfq5a9fXHUwMDFiuTrwPHNcdTAwMDesXHUwMDE0XHUwMDEzSGpB+2dcdTAwMDZyiFx0ldHDZ416TygxxoowQcngXG4/2Fx1MDAwNblcbnv3fVx1MDAwNNn0XHUwMDA2b9uMYi8qc8NyNyR7L3fv9/l6plPGb7n8Y1x1MDAwZd3u+iei/0Qj8ue2Wo33zf6Zv77+N3hV7WbZ/Vx1MDAxY1x1MDAxMJaMMKlcdTAwMDQmQqr++apff4m+mWqj9DJ4ht7Rv363gSRT85B3V3w/rtS7O2VeV7nMyXZ6kDBcdTAwMDQgoTKCXHUwMDEwhWLYIFx1MDAwNDuMUq7h4ojg98GBMXVcYkZ6+H5rVIyggthRMZDA71x1MDAxOaNfR/6KgVx1MDAwNeZAIMKZtoFcdTAwMDXLXHUwMDE4hvpgXHUwMDAxvUU0x5ouXHUwMDBlLN/iXHUwMDE1elx1MDAxZuEoOj6l8+hcIlfa3jpcYjPs9vjmdefw4rF7XZxcdTAwMGUrXGKDsl5cZlbso4lhZeRhejBcdTAwMTGUOlxcIKBcdTAwMTJMqVRcdTAwMWFH4GKhXHUwMDEy5mj4XGLNQEFcdTAwMTE8zDZ9uJA1Slx1MDAxMlBC03NcdTAwMDeFV6tcdFwi2IZcdTAwMDaEY4dcdTAwMDfUwT/V2SxgWKS8XHUwMDBlxM+IndHozebQ9DXqYd7vmlx1MDAxMVx1MDAxMzRydN+t+VXzvvnIXHUwMDFkMlX/yTz7Zlx0XHUwMDA267U2h58/9MFcdTAwMGXrX1x1MDAxMDaGfqdcdTAwMDR3dP2618qmYZVGy3/y6271yjZkt1x1MDAxZDYuveBz0GGr7Vxyv1xm77BPXHUwMDE2XHUwMDBl4WOwupXz3y635U5bPV5cdTAwMDfVzMWhXGZ3u3GsetWq31xmXCKsJpBykMZYXGJCNUZyINm998XB/kNklPa+cSukI1x1MDAwNKaMXHUwMDBiXHSmXHUwMDFlXHUwMDEwtVx1MDAwNbbM4WBWSqJcdTAwMTAmjFx1MDAxM8X+UTBcdTAwMWUwXHUwMDE1wY/Zx26nXHUwMDEw1lvXhXvcuLlcIoDfmEqfiHZ5kJZcdTAwMTRJXHUwMDEyKYJcdTAwMDIgMCGgni1qQFx1MDAxM1x1MDAxMj3a11x1MDAwMowrsCGHLP0laYExSFximMzgfO6sePTqdzqHneeD+7PbOFx1MDAxMlxmuKIwXHUwMDEwIMyUYqoo1kiLUVx1MDAxOFxi5YBBgOBcckjEiFx1MDAxMDEsIMe4OVJKSlxiQ1hpXHUwMDFkXHUwMDA3XHUwMDAz04A0rrRQXGLwwqleY2E+LNzMjVx1MDAwNfCmqFx1MDAwNDRwXHUwMDFiXHUwMDE00Fx1MDAxOPOQaFB4XFwsXHUwMDFkXHUwMDBi/cPNhlx1MDAxZrU+XHUwMDA3/9tcdTAwMThIVu+P/v///bv16q1kcTafmCBcdTAwMGbuXHUwMDE3m8WqXHUwMDFihDuNWs1cdTAwMGbhQS/MIGPGSei2wm2YVr/+XHUwMDE0PefVy1x0Z3rfylx1MDAxOHey4rkxIYHvRc+N0Vx1MDAxYVx1MDAwN+fi5kqhZvs4s6VcdTAwMWbOMujypP2SSmtI7khcbvzHXHUwMDEwRUiTXHUwMDAxontaQ1wiRzBcbkYtg5dJ6EBYhrSGXHUwMDA2jYOMLFFBtVx1MDAxZdDjQGtcYlx1MDAwN1GFKfxcdTAwMDYobfBm1lx1MDAxNDqf2lDza1xyhCVBnKLYYlx1MDAwYpxUUkSPfqtcclx1MDAwMYaSoEjPtFx1MDAwMvNraI1cdTAwMDRxNp+4IP/iamO3XHUwMDE0kE7moHize32c297VbVfXaWqzWziawXsgoEOkxKOLSuasolx1MDAxY1x1MDAwYkU1XGKNjGuOte29XHUwMDA0xTG/7Y1cdTAwMTVD0oDAZm/IRFx1MDAwN1x1MDAxYyRcdTAwMDRRTvjSXHUwMDE1x1x1MDAxODScbpfCx6uyOner+aduwW/7rad0pjcjXHUwMDBlpVx1MDAwNEyJSPCBaeUojkeCXHUwMDEyXHUwMDAz5lx1MDAxYz7al3nOXHUwMDFjofnInf5cdLI+hVx1MDAxMOfml2GOXHUwMDE0U4JcYtsykpAoSYixomBcdGnC/nuN5iGZNJ+BNP7iTPeCXHUwMDBlj/wj77ryUXdvWlx1MDAxZpV3+XJ6mT5wQoyVXHUwMDFjj5xcdTAwMTDiXHUwMDE4ecC28FwiKEpHfUUkk0IodMEg/9dj7/OLXHUwMDAz/NZcdTAwMGVw23owU1xcXHUwMDAwXHUwMDE5XHSb96uSl4NcdJJSKFx1MDAwNjbeLEDuQ2Gq4Mhuxj15XHJcbnVcXNyu+Pn27m628HSxtOCIfTQxPMSDI5wqRzJFwFx1MDAxYVx1MDAxM0pwQiOIkGNcdTAwMTDBgVxysdZcYnFMjFqxWHxrQFhcdTAwMDBRSFx1MDAwZlxigoThJ6Zt9pmULFx0XHUwMDExYIVLTsnqXHUwMDEw23d8ZNdrVlx1MDAxYlx1MDAxZPNbXHUwMDFi13V/2Hb5+2IlNb9cXFx1MDAxZaaASLhkXHUwMDAyl0TDJYlPsJjQyXt36+21qGuFt1x1MDAxMrnddjuNd5l3UzNcdTAwMWKwmoO0XHUwMDEylKio8YrBhSNcdTAwMWNHjNE+mFx0+MqfnNhjN1v6XGaYxT3+Y/rrszZp7Vx1MDAwMC9OXHUwMDAxcMxcdTAwMTj4zTpObmYoXGbH8lx1MDAwNPpLN1xmXHUwMDEzzfRgJv92xnu6VPKi+1o7e1x1MDAxN8Xc0X5JPZy+7k3FeFxugXCRYVjMznj20aRgPMm0gylcdTAwMDdB15b8Mow5nI1mnn2jRINFXHJyzzGW66yAKTGRXHUwMDEwXHUwMDFisaaUXHTKXHUwMDEwcFx1MDAxZbLZgUBtiXYgaDbOKOZ6XHUwMDE2VCxScGO899BovFx1MDAwNJXGzyRcdTAwMDdMILxcdFx1MDAxNFx1MDAxMyW8+NBcdTAwMTfDdPe6nrm7On4uXHUwMDFmuPkn+Xza2VPuYWqmU5o7QFNWXGYj6YBVSjWyYXjNdFx1MDAwYkT1Q3pQS6FcdTAwMTXTjFrTRFx1MDAxOVx1MDAxYlx1MDAxM9pcdTAwMTRcdTAwMWNLivBcdTAwMGZS3fGNPENcdTAwMGbFs6NcdTAwMDOhi0G2fV0q3pwujerso0lBdVx1MDAxYURcdTAwMWRcdTAwMWNcdTAwMDFJhGQmXHUwMDAzLkp1wpEqus7ZX8/XXHUwMDBlWFx1MDAxZprKz6Q523LHmuySYFGaXHUwMDAyXHUwMDE2WmDB7bDgKjFyh5nUhFx1MDAxMK5cdTAwMTadXHUwMDAyNz/Vtbw333tcdTAwMGZWgekmUEyU6WIjX1xm0eGrq/vXs4+mPr8qXHTv7lBcdTAwMTReX5vpiVx1MDAwZWNcdTAwMDdcdTAwMGKlXHUwMDE5j8UjsFx1MDAwMp7iXFxxbcuGW1x1MDAxM91cdTAwMDJcdTAwMTH9mFx1MDAxZdFcdTAwMDSZPG0xvFx1MDAxYTni0qno4b5L14ucMv6DRFx1MDAxN6it0r5cbs6P7662m/vHuawsy8ulXHUwMDExnX00KYhOMeQwkGOgKUykXHUwMDFlXcRcdTAwMDTn2lx1MDAxMVRcdGldxVx1MDAxNMTBUmtcdTAwMDZcYtNcZrM1z02Diqf0qOCSUq6ItmWocJHIc1IyXHUwMDA18sVcdTAwMTZcdTAwMWRonp/mXHUwMDFhrTJcZnVcdTAwMTVYblx1MDAwMr9EWS468MWQXFyu3tx+bsnM1lNp59jNdlx1MDAwZvab+XJqklx1MDAxM5o6XHUwMDAwYmVcdLpz5FhcclTKhYN5L1x1MDAxM5yNUtdcdTAwMDC4yKhcdTAwMDWm2LqmKVx1MDAxMcKVKYhNI0ooUTZcYmOmXHUwMDEy87RcdTAwMTnMgcnOXFwgr81d5/f7uPue5endY93ttshNU7BcdTAwMWTvoJ6/vlnAfVx1MDAwZmq7tHGQPS69bYelXHUwMDFhP/tcYlx1MDAxZbok3X1/loh17eo6372/dVx1MDAwYlx1MDAwZtLtvt/u+8GhJf80KVxiwWnS0lxmXHUwMDE1XHUwMDBl0URbXHUwMDBieNdcdTAwMTH2WTHsT1x1MDAxM28gopdcdTAwMWRsK0DUXCIx3IAxXHUwMDE1XHUwMDA0XGYkscBi3UnGaVe/XHUwMDFkVlm3xK/Pr1Wre9qs0dP7pWHCPpo0ximSXHUwMDBlyLaxfTDYomy0/pDhZERI4TBjm35HXHUwMDFjVFx1MDAxY1x1MDAxMOtcYrtcclx1MDAxMM9TXHUwMDAxQlx1MDAxM8U5sbGajsPkXHUwMDFiXHUwMDEwijGJNKOLXHUwMDBlsc9vmJ42yu2qt1x1MDAxMpbpXHUwMDA0XHUwMDFliVqmsZEvxjQlzfz+tto7Q1x1MDAxN3c3Lzu551L+dDhcdTAwMWE1KVnMVFx1MDAxY2qJpCVcdTAwMWZcdTAwMTRrR2CJXHUwMDA0Z1x0XGJcdTAwMDZK+7I/XHUwMDEzl2DoYErWpulcYopfpkCxqYRjXHUwMDA0x7tNmKFwmrjmgomJLTA2W1x1MDAwZehsvHa4XHUwMDFmNrogjOwu0Jf15zpcdTAwMGI6hZOlpY7ZR5OC18BOczjDlCDw0E111ig2XGKbgFxyXHRf51pTwSPlvEP+21x1MDAxYVx1MDAxYXZoVNNDw4RwsFx1MDAxNvHWXHUwMDEym6b+J9FpXHUwMDAzO5xhMFaWnuFcdTAwMWbjt7GhdCxGjv7dXHUwMDA0N4FafiyUfnVEL27dm/A60zlcZnJ7zPfunlMznODcofbFXHUwMDE3hpUjKFx1MDAxMVx1MDAxYa1cdTAwMTnua0pcdTAwMTdcdONaelx1MDAxODNcbs6XVsqaJ8aTi1x1MDAxY4RCRLJcdTAwMWZcZimc8qxEtbvrI+FcdTAwMTfx1lx1MDAxYru/yzx6S2M3+2hSsJtcdTAwMTDaUWD2XHRcdTAwMDb8psHaj7BcdTAwMWKdgFx1MDAwYsBcdTAwMTSnXFxbLMc1vU3ERX1cbsuPKFx1MDAwNrOgbOtcdTAwMTlcdTAwMTIlllxmYKqxwqvYQWZcXPj8h9ltXHUwMDAyrfxQ+PxCXHUwMDE2ro9KO1x1MDAxN8eo8Vx1MDAxOPCt02rOJUF6ckPcXHUwMDAx84eo0WywXHUwMDFliLl0kJZcdTAwMWM0q1xck9vGokHcmFwi4VNxYurQtbUtmmDJPVx1MDAwNMH/oEqi2XrAzEZwVyfnt8/yXHUwMDA00YfaR+a2/kKvu9eVpVx1MDAxMZx9NGlcYo5cdTAwMTAj3JxoxImkUYJcdTAwMDPnbjw2mHYko5rLWE3BmuAmYqOZXHUwMDFlXHUwMDFi8IJNzby1tYNUiVx1MDAxNUCYwFc0XoFcdTAwMTLtXHUwMDE4w42JnP8wwU2glp+JnFx1MDAxN/Ot7Ys9yj52RLOyV5X189PhXHUwMDEysUnpYaZcdChcdTAwMTg6kYqEz1650iFMKymTXHUwMDAyXGbrnrlcdTAwMGLE9Gt6TCtqXHUwMDAy4sxcdTAwMWVJJzxcdTAwMTHUWsA8gEex8Ej6XHUwMDFjXHUwMDEx78l0Z1x1MDAxYTexXHUwMDA10d3+U5Pf7zTOqvulSoXjbPXoRl+lXHUwMDA3i1RcdTAwMGVcdTAwMTVcItrm9qttrmkxZdLHkuLTWDtoXGJcYsiScWLKh3pf/06o/Ge1OJlcdTAwMDItLTtapumly7AmoOPiNXKfKEpcZt3B9INcdTAwMTDIXHUwMDE521x1MDAxZc1mNL5cdTAwMWN3XHUwMDBl2dNdcHxcdTAwMWHo8+5+caf4snu2NKPRPppcdTAwMTRGo6koMGxjXHUwMDFhZ1x1MDAxOJ8q1kt3XHUwMDAyhtaddXtHZ8RMkJ5hhDQ0QeNcdTAwMWRcdTAwMDN7I9GJ+ZZcbiMlQN5cdTAwMTZdQfff0Vl3XHUwMDAy+/xAZ917cXKsn1x1MDAxZW5PXvTx1rM8YK+Z4mtcdTAwMWO51lx1MDAxNl9aUIeYNUtlOobaOutyJiRPTEdZt/hK3+KrsvfKaKtcdTAwMTCg0iE/ZydcdTAwMWa5ztVLZ4ZcdTAwMTZfYVqaTOyOXHUwMDA0k0F6rZQtmkCL5MA4XHUwMDA1U1x1MDAxZkylle6ue9hcdTAwMGXYm/SyxWKB3eNs4bixr9txNMRbfGmBXHUwMDFkqTXiMFx1MDAxYc1s3XVFr1x1MDAxMlx1MDAwMzxcdTAwMWY8nLG17q67PCi054aC1Fx1MDAxY2w/ZF1K0fEtXHUwMDE5XHUwMDA27YVAZFx1MDAxNejNmdyuX6JP2D+puW7l1PVPXHUwMDBlXHUwMDFlXHUwMDBlVT23k9mvXHUwMDE2rtj5Vi2N0lx1MDAwMLdcdTAwMDE5SoI+XHUwMDAwXHUwMDBmUvJImZFprlx1MDAwYr4ueCTArVx1MDAxYSNrS+51c92f1lx1MDAxYW9za1xyLIy/L+P+5ObY7vRacFO0TlbHkl731p1Da9yF1feT8utN4fH4fetcbqmz3FtgSVwiTTC8iYOk4oKZzkygQEZcdTAwMTRHr/MuUJOUYDpTsDvWhvdcbuiN9/n1XHUwMDA2wVx1MDAxNJvdbew+OFx1MDAxZVx1MDAxM9SUmFx1MDAxOFx1MDAxNlll03vn5SPXPHlhRzTr57c0z7BCeT+V6c24Q1x1MDAxNZeR5Z/v/rrgdIDwJlx1MDAwNlx1MDAxZLlDXHUwMDEysmm4WXtcdTAwMWFccvL/XHUwMDEzJH9cbpH+mFukXHUwMDE1SFx1MDAxMddsqFx1MDAxNctQLHLMRi2cUERHXG6z/6u4MCaV5jOQx1+c+VrtnaOjw1x1MDAxY7rfOXg72X3vZvfzhYM40pNCLoI6XHUwMDEyXHUwMDFjasrjIVx1MDAxN0JcdTAwMWQjXHUwMDE4TIhcdTAwMDS4r0tcdTAwMDJnxXpniuRcdTAwMWLw/Dhi2lx1MDAxYUdRJLlcdTAwMDJcbjPwXHUwMDE3h1x1MDAxN5b+9ijKs2ggWr9+9ltXt2SviFH+7c37gareXHUwMDE4bi0xTsaGqifnolb7U8ZcdTAwMDBnqTSUwpFcdTAwMTJJY5ZcdTAwMGJcdTAwMWNv5qsmwW3d0Xc2tHXTo00pRM3eXHTWfNX4XCJtv6Gvltxslrs6XGa6wlx1MDAxZH0nMNZcdTAwMGZ39M09VEkn89pccsq7p/7z5VX4svUyRfsnXHKeXTTD7jNcdTAwMDVcdTAwMWQ5VFx1MDAwM1xcKbX3f1onr27MjOhcdTAwMDeE0kNaYqmkXHUwMDFh3k5ruDSDx4zlgZ9HNJNkRlDPxqDVxuM1wWxHn2ReguquXHUwMDE2lUC+Li1cdTAwMGbBPppcdTAwMTRMXHUwMDA3XHUwMDE05XBBpcKSKlx1MDAxMq895OOhsS49nFx1MDAwM1x1MDAxOdPsXHIvKVJaYKtpXHRWSFwi21x1MDAxMbPR1PDWzKvCdqtUfDiBWH6q+FBcdTAwMTSa70F1q45ubytZjm+K++6jJZ6SwG9cdTAwMThcZtJ4XHUwMDA3jFx1MDAxZYopd2DCKI76jmuCW1x1MDAwNIxcdTAwMTM2s7dcdTAwMTJcdTAwMWNcdTAwMDFNqVx1MDAxM1x1MDAxYT9cdJZotFx1MDAxMkSRUEipmWIgs1x1MDAxMVxcu1ZcdTAwMGKPXHUwMDFm7mvH94g3Pm7un/JFUl1cdTAwMWHB2UeTguAwYuCLma1XiOi1MIuUZzA0XHUwMDFlXHUwMDFi6/LDjTmwMc1cdTAwMTb2YMNRoZV9g5bEJVFcdTAwMDWGXHUwMDFmY1x1MDAxNK+eP7dC9YdcdTAwMTOY5YfqXHUwMDBm7zvHXHUwMDE37a77Vnzb7eyTav36se5/pOY3479cdTAwMTFO7dWHYJ+Ktf/2PZ1cdTAwMGKFMJuG3lxiV1JSW1TDdDBJ9N+widZcIkRmMlNno7dcdTAwMDZR753b4+ZRoZRcclx1MDAwZmjpssFeb5ZGb/bRpPHfMHbgzcJIXGJDxFZ8OFx1MDAxNlx1MDAxYevaw405oMHTQ0NrRkwuk43d1Jg9XHUwMDFiuNnRQVwitfRcYnaM3lan+HBcdTAwMDKv/EzxYeWjW6rXsrvutTypvVx1MDAxNc9cdTAwMDTNUcuW0YnOXHUwMDFiNlx1MDAwNYQkuinuZzFcYlx1MDAwNlx1MDAxMGtJcGK4YV1QtUhYXHUwMDBiO6ynqahSXG5cdTAwMTmXwd65niZ301x1MDAxMNxcdTAwMTSbUPGDft51Vd5Vu5yetf2t/dPgrXp03j1fXHUwMDFhXHUwMDEx2keTys9cdTAwMDMzUFx1MDAxM1x1MDAwMeZcdTAwMDeSmFxmecqfXHUwMDE4XHUwMDEyXHUwMDEzMLQuqOpcdTAwMWSdXHUwMDE1MzI9XHUwMDE1clx1MDAwMd64lNY9ycBcdTAwMTJMLqRQRCtE9VxuJYKuUknVXHUwMDA0XHUwMDAy+oGSqo9sWGqeu5fd7v3l2fnHXHUwMDEzp0d5y06CtnxwRFx1MDAxY1wiWW/biGjXes5cdTAwMWOqxjWIMus70YS1QTJbL2F0vWt8MnLVNFx0LkRcdTAwMTFcdTAwMDTgtdKaTI7PUbA+XGLWK7RIs9i0tbhcdTAwMDSaz1Zf+Fx1MDAwNjeIsemvkLjmtlx1MDAwM4/RYuWps507f9JcdTAwMDVdbl8+pFwiZZN3hqjAlm10mVx1MDAxNI4y7k1cIrCJdrjGWnNCv3prxPHN1sBOXHUwMDAytk5cdTAwMGZsglxiklxicXuVczxcdTAwMWU/7J5igdlsXHJcdTAwMDD+Vkp2XHUwMDFjx0rJdDrftOo9hrNcdTAwMTHyyGCj7DsyusWw7/g9PFwiXHUwMDE5eSMoNVx1MDAxYvkyREyDS6RMlGNcdTAwMDSnRDCTlFx1MDAwYlx1MDAxND2mXHUwMDE3gdCCglx1MDAxNGBEwLxGXHUwMDE2XHUwMDFlZuDDRneOWlx1MDAwNcCyyPFlXHUwMDAyNjN/WjlcdTAwMDBHcVx1MDAxYd/Y18ySTLSsOVx1MDAwN7dcdTAwMTNcdLE6WyAutsIqWUDNZ0g0XHUwMDE3ydND0+Z9vq6vZIOx+zv11FKpbVx1MDAxZVx1MDAxZDlcdTAwMThcdTAwMDFcdTAwMDBMZzlcdTAwMDAnpnIwgUbI3GZPNzuMcFx1MDAwMTT56dbGXHUwMDA0Y8RASFx1MDAxYVKW3uzW21x1MDAxN9WrXHUwMDBilt3bK1x1MDAwNJLJvLZccmlcdTAwMGI51GyXXHUwMDBlbCzBslFak9iQeqWdXHUwMDFhke9cclHjsjqNZVx1MDAxMtVXXvWh8Z5qJWF8lvI4fVxilpxcdTAwMDNAMiXOWqvhXoqf+lx1MDAxMFx1MDAxNLGkXHUwMDE0J/Zjx9xcdTAwMDHjxjiqglxihLAl+1x1MDAxNyxGXHUwMDE2XZNfq8NRdbg9tzr8LFx1MDAxYVx1MDAwM6vGZtYwnVxcZqOBXHUwMDA1XHUwMDA1JbPFo8Ymt1x1MDAwYkWW768kXG6o+VxmieaKqUNkckM14zAzVFx1MDAxMoqHiuu/1SFymOaAXHUwMDEyMY86XHUwMDFjn/U8qlx1MDAwZVx1MDAxOZJUaVOLint9nFh8TNT5So5cXJYmXHUwMDFjv1vaOE2IXHUwMDEx5Y5Zyo4vin6qQlx1MDAwMsRKzbop4cBVQ27AYO9cXG3ya4RiPCl9xmw5ylx1MDAxNTGdXHUwMDAwTEVcdTAwMDWnq1x1MDAxNZlYJZW4M79KXHUwMDA08GDQRFaVSHViw0TwXHUwMDBmMOVktlx1MDAwNLSxJqLGQ6ukS9OISWJqPnFcdTAwMDFdMcVIe/1cdTAwMDPMztbgi4NZMjT6byWEXHUwMDFkjYWA87JcdTAwMDdUIcarxtmXklKpqd++3tim22zmQ5CI/ntcdTAwMDBo+OXI6vnnsdBrXHUwMDBl3PTeodNG2duru1x1MDAwZtWodGya9KXt5Fx1MDAxYanfvp7baFx1MDAwMa9cdTAwMDegv3776/9cdTAwMDF7mdJ0In0=AppDeployment UnitbookshopreviewsordersModulesbookshopreviewsordersAppDeployment UnitbookshopreviewsordersApp... \ No newline at end of file diff --git a/guides/deployment/assets/microservices/modules.excalidraw.svg b/guides/deployment/assets/microservices/modules.excalidraw.svg new file mode 100644 index 0000000000..651051525e --- /dev/null +++ b/guides/deployment/assets/microservices/modules.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO2aa1PiSFx1MDAxN8ffz6ew2LdLpu+XqXrqKVx1MDAxNFR0xlFxvMzW1lx1MDAxNIZcYpGQhCSgsOV331x1MDAxM1BcdTAwMTJCUGDxsrX4giq7O8lJzvmfX5/u/uvT1lYhXHUwMDFh+Fbhy1bBujfrjt1cYup3hd/j9r5cdTAwMTWEtudCXHUwMDE3XHUwMDE5/Vx1MDAxZnq9wFx1MDAxY41sRZFcdTAwMWZ++fy5U1x1MDAwZtpW5Dt10zL6dtirO2HUa9ieYXqdz3ZkdcL/x79H9Y71P9/rNKLASFx1MDAxZVK0XHUwMDFhduRcdTAwMDXjZ1mO1bHcKIS7/1x1MDAwMf9vbf01+oVcdTAwMWW7XHUwMDExP9H1W0htn3qtQzvofq2KXHUwMDFm5Ir0R5eOXHUwMDA2Pb1CYJlR3W06VtJ1XHUwMDBm7Vx1MDAxMmtDXG4hXHUwMDEw5oLwSddcdTAwMDC6XHUwMDE4XHUwMDExXHUwMDA2kZpooWTSc2c3olx1MDAxNvRiyVxmjqhGgjBcdTAwMWT/ycmQlmU3W1E8hnJcdTAwMDMjTeV4RHrM2JQvW2jSXHUwMDEyRoHXtnY8XHUwMDA3Xlx1MDAxY+z9XHJriU2SWHtdN9vNwOu5jcmYKKi7oV9cdTAwMGbg8yTjbmzHqUWD0d3BMfBFXHUwMDBimWdcXDy+XHUwMDAyybTPu1xuXHUwMDFl2my5Vlx1MDAxOLtcdTAwMDBPWj2/btpR/KEwSt5cIrbQrzZcdTAwMTJvjVqHvYswuHFcdTAwMWGHePvnz35HtixcdTAwMTSIwmP/n4npXHUwMDAxhEM19qrbc5xJs+02rNhXhfr13pRVbuPRqqnhoWXFtyBcdTAwMDTcykjyyVNBi2m29chzR1x1MDAwMYyx5Fx1MDAwMlx1MDAxMUJ08viwXGYxXHUwMDE4jW56XHUwMDAzcWwlLolNqGTjM1x1MDAxZKNTIVx1MDAxOFn3iZ9SXHUwMDExfHpU7FxmTu/K9+f9ajC8vdtcdLyrm8Jk3MPMd+r5jfrYXHUwMDFliELCIEZcdTAwMTGmNHGnY7vt7FdxPLOdvMKo9eH3PD3lWzOjp6mXXHUwMDE5S0lgQ1xixaXGeWqi+Fx1MDAxOTVpZFxi0Fx1MDAwN4fPj9P9XHUwMDEzKaXutpHOStI5X1xcOpgqhFx1MDAxMME6TzyYkXnioZRrypRYSTvrXGbvJFrjKP1cdTAwMTI/zWuHLc9P+dpzo5o9jM0maKp1t96xndg5fOo2Jcduxu9fMMFiKyikP0JkXHUwMDAzuiZcdTAwMDOi9HNMuGPddq2gulxirbzAbtpu3Tmba3e9XHUwMDE3eadWOLY8XG56VvqzWPtcdTAwMTP0XHUwMDE4hD8j8r66sE/CO3FcdTAwMWOYre6eSe4j17tYXHUwMDFjmlxmXHUwMDFiXGZjTInCXHUwMDE5mXNMXGYsqZrumUCTKENRgih+hCabVTplhuRy6vJcdTAwMGat+IZcdTAwMTfFwfpcdTAwMTEl7+RLPoHTk6PpY8vDTCYggkrFWColpzKBVnMxilx1MDAwNNJEXHUwMDExllxc9+pcdTAwMTit1fYrXHUwMDAz/2r3/uu3K+dg//74e+v89t0wmm/NXCJcdTAwMThl2kBcdTAwMDLmpVpcIlx1MDAxOSsloy9tcE4kZTRvUoqpXHUwMDAxI5SWTMTzzjyQoo2s/pmszMVBqlx1MDAxNVx1MDAwM09QkqtcdTAwMWU+05qoR0pBXHUwMDExonJcdTAwMTX1vCpId+pR3fGaNSvo26aVi1Ms1ovTjt1opFx0lCHqXHUwMDBiKMtcdTAwMTJ13lx1MDAwYqyHq5WjcrR7eX5f02cnpHJ4sV2UR2pcdK5cIkNjilx1MDAxNJ/S7mj6LLhcdTAwMDFTaMGQ2HD1vVx1MDAxM8CcqfRcdTAwMTJcXNVcZjGkXHUwMDE0wemwf6pOU7VnNjFcYlx1MDAwZW6mVCXXvTpWOStcdTAwMWSbQlx1MDAwZkuBssPLs77NdorNd8NqvjWLYJVcdTAwMGKDXHUwMDBiUFx1MDAxN4iAXHUwMDEyRqaxyiQxXHUwMDE0RpzlV6cypqqG8lUqXHUwMDFll7ZcdTAwMWKsrl9VjcWxXG4yYEpcdTAwMGKt8uSTU7VO5MOZhJhcdTAwMTTy43G11OjY7lx1MDAwN6LqXHUwMDBiIMtSNd/89TC15rLh8Vxy/3X8/Ud0cVx1MDAxNrg1NPSDhZmKXHUwMDExVKRCXGKQ79TK0ujbXHUwMDExXHRM5Fx1MDAxOdwmUGVcdTAwMDaKXHUwMDEzwuzqbVx1MDAwMlVpjJe0nsb8O7LAmlapllD37Vx1MDAxMuqGZEyJpihv9YmxXHUwMDE5zSdwRFJcYkV5UpW+Olx1MDAxY381qzvdIFxit89cdTAwMGb2/MtcdTAwMDbpfNOnzlJwVFx1MDAxMnOl0sJYXHUwMDFkjvnWLFx1MDAwMEeMqDZg4lx0hT5VUmhOp3VCyTM6kdjAQEfBOPxA1almZfJvmXO+vS7ai+uCSoVcdTAwMThGambvXCLuXHUwMDEz83c0wJ1ES72SKtZcdTAwMTm4M8wzvU5cdTAwMDesz6PdWy7JvsCXLOqyVq9cdTAwMDdyxbJ/PDRFqXZ6qVx1MDAwNzX2rXx7eDNYXHUwMDE4clx1MDAxYTOY3HKW2V1cdTAwMTnvYnLjadqawziuwa64KnxcdTAwMDRYTuGolIEhI0yG/LdcdTAwMTiXTHRPu06/duztN83isFR1usH2zc7lXG5cdTAwMTPd9lx1MDAxMpuYglx1MDAxMMKFVDNcdTAwMTVhnHfR3DqRI45cYuTq5JVenYT+z93qzbC74+uLM/fwuPurOqT95UgooFx1MDAwMmNp9axOwnxrXHUwMDE2IKFcdTAwMDZBMMWokFRcIoF4dlx1MDAxM1x1MDAxMz0jJlx1MDAwMZdcdTAwMDJcYqmEYlx1MDAwMFxchsSslv5jIHxcdTAwMDXxLLGNqYVcdTAwMDTwodnN/lghev48XHUwMDEy0iFXXCJV5a9cdJjLxvdcZjBcdTAwMDOrb1t34btcdTAwMTPzXHUwMDA1WGWJOWP2epCJz1x1MDAwZWmPnDl7e6VexTo5Oz/Z9q9cdTAwMTZHJsxpXHUwMDExXHUwMDA3raKctVao6rRC+YtBm7XWt9T7P9/CXHUwMDA0XHUwMDFmw1xmXHUwMDE4odwswObuYHJNXHUwMDA0zKfeXHUwMDEyoeLgumyj/avrk9vdMlx1MDAxYVx1MDAwZarEvGq/XHUwMDFiQvOtWVx1MDAwNKFUXHUwMDE5XHUwMDFhYVx09Vx1MDAwMeNQsGRXWrkx8lxizT0ggDnUoZCcwV9cZkudOkqyWWpdm6g6SyzGQNlPXHUwMDE0wiRXPnjuXGaUMVxu0yT2XHUwMDAxIXo6ptFcdTAwMDdaa31cdTAwMDFkWZzOe4H1ULXcZrhYXHUwMDFldItlUsH3/vWe/bW9u8Rqq9KGkmR8cmG2XHUwMDEyfeZo0KZcdTAwMTJdOFx1MDAwZpRcdTAwMGba/dPeQSDLqrq3f1x1MDAwYlx1MDAxOWFn/3aFPOAvnlx1MDAwN6CelHFizjvJINHcXHUwMDEzgZhcYlx1MDAxY1x1MDAxN6Mpb746RkvfaLXqVprdm6NfR4NcdTAwMWaHstJ0/CUxqoFd68FovjVcdTAwMGJgXHUwMDE0Y+AowzFcdDEmUk8vycaV6HwxifhcdTAwMTBcdTAwMWVISCj4wWxTib6CeLpLXHUwMDFjRUeUSFx1MDAxMVx1MDAxZqjNrUTRPPVcdTAwMTDwXHUwMDFkIZKIVcSzzviegahcdTAwMTc0wNR3L0RfgFWWnFmr10PM/aaq6MZR1cZ+pd5sXuHSmbn4mVx1MDAxZlx1MDAxMDhcdTAwMDNizpyMXHUwMDFmXHUwMDE3osRQ8+fKm0L0XHLlXHUwMDFl5Mt9mbO0VEolXHUwMDE1zUMoU3OTXHUwMDAwxnBcclx1MDAxNnq1XHKc1Vx1MDAxMHrQ/75XxKXi3W236bq4+pO41va7ITTfmoVcdTAwMTCKhYFobI3k8VpPplx1MDAxMkUg7Lm7/0hcdTAwMThcdTAwMWNjjSWGJFx1MDAxY2toU4muX1XhXHUwMDEyM1CBuJZ5XHUwMDA0ZXy+eCg4j4r0ca+PgtDvI1x1MDAxOH2gMvRcdTAwMDWOZWE6x/6lmfrpMWCgXHUwMDFj8WtcdTAwMTF85UlWLcR17vasTH67XHUwMDE5/cXhNkpcdTAwMTixRKxRMn749PA3XHUwMDBlLbLDIn0=bookshopCatalogServiceAdminServicecommonreviewsReviewsServiceordersOrdersService \ No newline at end of file diff --git a/guides/deployment/assets/microservices/modulith.excalidraw.svg b/guides/deployment/assets/microservices/modulith.excalidraw.svg new file mode 100644 index 0000000000..65671640b2 --- /dev/null +++ b/guides/deployment/assets/microservices/modulith.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXNtS20pcdTAwMTZ9z1dQnNfQ6fslVVNTJlx1MDAwNmJcdTAwMDJcdTAwMGXgXHUwMDBiXHUwMDBlU6dSwpJtxbJkSzJgUvn3aVx0sGRdwFx1MDAwZT5AZuxcdTAwMDdcdTAwMDItWdrqXmuvfWnl57utre1wNra2P25tWzddw7FN37jeflx1MDAxZo1fWX5ge64+hOO/XHUwMDAzb+p34zNcdTAwMDdhOFx1MDAwZT5++DAy/KFcdTAwMTWOXHUwMDFko2uBKzuYXHUwMDFhTlx1MDAxME5N21x1MDAwM11v9MFcdTAwMGWtUfDv6GfdXHUwMDE4Wf9cdTAwMWF7IzP0QXKTXHUwMDFky7RDz7+7l+VYI8tcclx1MDAwM331/+i/t7Z+xj/1XHUwMDEx24zuWCPtqjs9cZontLa311x0XHUwMDA0XHUwMDE1XHJcdTAwMTV/NT7p4Vx1MDAxMXyrXHUwMDFiXHUwMDFhbt+xkkM3epwrXHUwMDAyXHUwMDA0gZRLweZcdTAwMDdm+lx1MDAwMOFcdTAwMTgoRCHHyfi1bYZcdTAwMDN9jEJcdTAwMDRcdTAwMTBm8OGD5mdcZiy7P1xio68jXHUwMDA2uEDpq97d/eNcdTAwMTacj1x1MDAwNKHvXHKtT56jn1Wb+Fx1MDAxN1JcdTAwMDJ1cWLgpdFcdTAwMWT2fW/qmvNzQt9wg7Hh61x1MDAxOUnO69mO01xiZ/HV9VroSdzO3OP83nKcXHUwMDE5L/uWvml/4FpBNOvJ03ljo2uH0ewgmDxFZOG4ZsZcdTAwMGL0d2KTr5e2XHUwMDE2rZA7dZz5sO2aVjTv21x1MDAwNlq4m2ve323h7MCyoisgKSmHQnEyP5IgUCGeXHUwMDFkrXtujEbFlFwiXGby5Fx07KCq8Vx1MDAxNMZX7WlMWslcXEc27KWwljzKdGxcdTAwMWF3X0GCYqJcdTAwMTCiUsBkKlx1MDAxZNtcdTAwMWRmLXe87jC5Szz6631cdTAwMTF8d07tq7Nd8Wkqe63AqZx8XHUwMDE2YfU2XHUwMDBmX8tx7HGQXHUwMDAxr2RU45BcdTAwMTOMqVRcYlxuvIBgJlxioFJiiVxuIMxcdTAwMDXgXHUwMDFjXHUwMDExyrggiHKE81x1MDAxMMZcdTAwMTQwwZnAXHUwMDEyXCJMXHUwMDE5lvT/XG7K92N6XHUwMDE0o16tdzvrhK7f6nxHXruJNYbvj6+AeHFQXGb5n8mt7pdcdTAwMWLfj/zKMlx1MDAwMUOM9YJgVMRcdTAwMDSRWqFcZlx1MDAxM1x1MDAxMFx1MDAxM1Qyhlx1MDAxNHvDVFxiqKigxmn94nBiz2afZz9cdTAwMGW+18/zVND3yPFcdTAwMDBcdTAwMDOuJFx1MDAxNVx1MDAxMEqkoOKLPJBcZjAplCREQIo5z5FcdTAwMDFcdTAwMDKhSVwihIiYXHUwMDA0kVQqz1x1MDAwNqpcdTAwMDBUTCouoSZcZiNqQ4bnkaH9bDJoWSBC04FcdTAwMTVxgedG51xc0IJAOcaKvDZcdTAwMTfmw2PPTkc40Sf5bStBVvzH/Pe/31x1MDAxN569U1x1MDAwZefok1x1MDAwM3JyvdwqOkZcdTAwMTB+8kYjO9RcdTAwMGZ6XHUwMDEyXHUwMDE5mVPo0PDDXb2sttvPXHUwMDFls1xcs+RI/K2K73vXXHUwMDAzy8iBRH8ve+xcdTAwMTGvcfCVt5tcdTAwMTKOp18qO+qyXoFnR9PhMl5DYVx1MDAwNISAkkCtcFDhhNF3XkNcdTAwMDCBleBI6cnEJGFLymsoRKSOXHUwMDFkXHUwMDExI5wolXjfxGtwXHUwMDAwiUREUH1cdTAwMTPMId5o6PPchny+14BIYMhcYlx1MDAxNEVug8hyt0GIkppVr1x1MDAxZU3+c26jXHUwMDA0z9Enj+Q/3G9Uu1x1MDAwMZ5VXHUwMDBlLtrV1pfT3aqaXHUwMDFhyiVLXHUwMDA33jo5XHUwMDE0Qlx0qTDTiFhMXHUwMDFkOSZAO1x1MDAxN1x1MDAwNCMvy9OA2UTfd6Ov4jmeXHUwMDFmfSNJoYhIUOA5dHRZ6jlcdTAwMTTTkIBaj1/bczxCXHUwMDA33Ksx/7syTifHot351tg/OrpcdTAwMTgvXUZROsamXHUwMDAyc5Yul8RlXHUwMDE0hVx1MDAwMVx1MDAxMkxcdTAwMTQmoVx1MDAxMlx1MDAwMlx1MDAwMiVTRLDSOspcdTAwMWZSPzGNYGC9bFx1MDAwMeXrsngmpXimTCGK9OJcdTAwMTRcdTAwMDBar1kpoLVj0n5PXHUwMDBiwe9cdTAwMDB6bl9iaVxuYKF1k6xMXG6fXHUwMDE1un9xdXbWNIPD1mmzNZHfTtrX2/PzfuVcdTAwMWPCXCJbqFBMx3gpi5/FlmJrcmxZeJg7oiiiMc91XFxJkCQsXHUwMDE1M96xRZSzhVNcdTAwMTCpstZmRFx1MDAxNos6iWhs2FLGlpNcdTAwMTXKjVhcdTAwMDf82iPRXCI/ryW7jFx1MDAxNkKvKFYsVX5Yg5v/XHUwMDFk4CY4jPCnn77TaFUqqVx1MDAxNfTcsGHfxlpcdTAwMDdcdTAwMTdG942R7cTpz8I1Ko7dj1x1MDAxZX67q821/O30XGaEdtdw5ieMbNNMi0NXX9TQqZdfW0ZlPN/u267hNIvtNqahd2ZcdTAwMDV3lof+1ErPifX5gVx1MDAwNVxiYPZcYnePd7thr2nKr4bT6N927Knt95crM0lcdDBcdTAwMTQ6bc6IXHUwMDFjo1x1MDAwMlx1MDAxNJdZYV5cdTAwMTVcdTAwMTOyXHUwMDEygNCmRfBcYmdPn1x1MDAxZrExKKnkmFx1MDAxNyqcKu1cdTAwMWMgSlx1MDAxOdWht0pmY01cXEZKKZHG7Wsle1lgRp9cdTAwMDSSyVx1MDAwNXLC+ickd6fVjjuq2Tf6XHUwMDFmXGb3bmHN/U5aKzRcdTAwMDUpQFx1MDAwNaEsXHUwMDA1ipa1XHUwMDA0JVBcdTAwMTBcdTAwMTNKRFx1MDAwMdORVCDTYnzTTDe9aGlflOpnz1x1MDAwZWYll0rqVKKI6ThVrs8ynUOOdI5NkzPeXnJWV22zzb9cdTAwMWVV+5NcdTAwMTM7rJnjYzrpr1x1MDAwMGdcZpQs6HFTooDIXG7aXHUwMDAzolx0XHUwMDAyXGZcdTAwMTJcdTAwMTWh/a7LXVDeJFx1MDAxOGSC1TeN65dXsMZcblEnioPHgsbFXaZWXG5ggpTEkpO33No7XHUwMDEyR9yY8XN1XTneXHUwMDFm7Df7XHUwMDA30oQrXHUwMDAxmCGS2U9cdTAwMTEjWGJAikOvXHJ+14Df5ipZXHUwMDEzU1x1MDAxOFFcInOuNk4zSrMmXHUwMDA2OdMh2lt2v7Oraa8xuWpcdTAwMWZcdTAwMGbkMVx1MDAxYp5cdTAwMWPORs2hu9JcdTAwMTYjhLlO3HN5XHUwMDAzXHUwMDE2+ahhg9814re1XHUwMDAyfoWCXGZcblTYTkaqPIAgUVxcXHUwMDBmoXjL/nfk3U5vQ3PCXHUwMDA27OBCXZOb2axd0OzI16u4UoBzmI+GKZSAl5WqXGJcdTAwMDeE6TwnaqtcbkFoXHUwMDAxdDeYLcNsycaI4o1xXHUwMDAyq6izUORz8+WrOWSRIDzaXHUwMDE58HZamVx1MDAwZpWqVi1YR53KsXopXHUwMDE45KpUoZcqOi2WqFx1MDAxNozN1qNcdTAwMTasW081alxiP1x1MDAxZtqHVmtw41x1MDAxYW3/ZnAthsdnS2uLYFx1MDAxOEhcdTAwMDVzqapOR6WirDCwR5JcdTAwMDJJRZz8x8KSVEH+sbbLX73484cz83x5ZlLJOJGoMJhnj+zT01x1MDAxYaRcdTAwMTD/rUbh/YFcdTAwMTX7KtWKcTRcdDouuthcdTAwMWTYjWm1Wuv0T5bvq6xZp4qtWUKnNJx1KEVcdTAwMTnUmZDkXGaTRT4w8lxiXHUwMDFmmCaLXHUwMDE2eFxi2V2Y9s+L1f9cdTAwMDZcdTAwMWQ6y9NcdTAwMDFDncNRRFVhS4WWJlx1MDAwN1JcIsmYxK+eXHUwMDFk5ISqao1cdTAwMWRvXHUwMDE23Wur5drhOkTruc2VJ6QkK2alT7BcdTAwMWVhq6vG98tjISa9sHm9ezBcIrVdNVpe2CRcdTAwMDRISIGgXCK5qlWkeZzKwrRcdGNcdTAwMDQoIcmmglx1MDAwMnVDiFx1MDAwMJy58iZcYl3g9rfnby7AUjLI0ptcdTAwMDTS9SxRroFEO3DysrtcdTAwMGJcdTAwMGWnx3uN2+OhNenUZ1NcZlx1MDAwN+7p8e5qu1x1MDAwYrTWizXtLii2Zlx0XHUwMDE1lIJcdTAwMDCiXHUwMDE4i3ZcdTAwMWLp+VcoQ1x1MDAxY/FcYnFcYlx1MDAwNUp/uKJcdTAwMTJTvFBp2yRtT/LlYoWkjSDtmjgvJlx1MDAwNsnvLktVXHUwMDFhJCRcdTAwMWFl8HeIsU7s5tSwMlx1MDAxZb+IXHUwMDAyPpK4PSU4WflbMHk9kjdCk65z1Nk/wyd00MKHNedq0l5e8lx1MDAxNFx1MDAwN4Tg/PZcdTAwMDIq4vI3o7kmznwjXHUwMDFkXHUwMDA197QufyExWYlccnVcdTAwMTeoa6xAXcpcdTAwMTDjWp1wXHUwMDFholx1MDAwZtSVONd7nFx1MDAxN7k1z1x1MDAxMFx1MDAxN/BcdTAwMDUljX2untXq51x1MDAxZF7f+dTrSbPuXdSOX03Siq1ZRtIgXHUwMDA2RGhniVx1MDAwNInfZcxQQzxBjYg70WugUU5Y3J3HXHUwMDFiZpQw43KV7lx1MDAwZlGaXHUwMDE4Slx1MDAxNomaouVcdTAwMWJtJKNcdTAwMTRhmiqJvFx1MDAxNU279LxhMPCKhS310vFLpHZPKEtW2/Kmr0fgfjRcdTAwMGaHk/blXHUwMDBmXHUwMDE14KOrTlx1MDAxZO9cdTAwMTlcdTAwMTdcdTAwMDfLt3GlYvndrXcsRoBhpVduI3D3XHUwMDBiuk5cdTAwMWF3l6exXHUwMDEyOjiVkFx1MDAxNetcdTAwMWIrXHJNOdVJW7SP5uX0rd04VztHs35zXHUwMDE49urd1uS216Rnr6ZvxdYsoW9cbiogdNSHXHUwMDE5olx1MDAxMcRphlx1MDAxOfxcdGYwXHUwMDA2WLSPXFzlNvJsXHUwMDA07klmmCtcYlx1MDAxY9Nw0WFcdTAwMDaHXHUwMDA11FCPVPRl9FJcdTAwMWRcdTAwMTb47Vx0nG9d2dZ1cb/thfXtXHRhyepbzvL1yNuUV29ufkxcdTAwMGbb44PPX8JmZ3B1Y7Dl5Y0yULA3POI3Q5IgyDbatrVuXHUwMDA2W8szOHpxRnCYY2pkSPrt79xbXHUwMDFkXFwwSehcdTAwMGJK21x1MDAxNa1cdTAwMWPd9HfxOVbWl9HJp8bIIbVXk7Zia5ZJ3ThcdTAwMDZUz5xUKt7ltChtWu+eIFx1MDAwNtXMoUQxXHUwMDEx/b8lXHUwMDFiaVuNXHUwMDE4veWJXHUwMDExvdeug1x1MDAwN1rIXGbIy3M3JjBCjLP1vyTxXFxp83xT2/pcdTAwMTaU7Vx0TckqW9bwlYXt3b1f2DbG40ao53X7oWuzXHUwMDFkaeZueTP63b1cdTAwMDOI8G7FzZ5f7379XHUwMDE3wupSYyJ9XSUAAUIsDeployment UnitAppbookshopreviewsorders \ No newline at end of file diff --git a/guides/deployment/assets/microservices/monolith.excalidraw.svg b/guides/deployment/assets/microservices/monolith.excalidraw.svg new file mode 100644 index 0000000000..415c2c6b2e --- /dev/null +++ b/guides/deployment/assets/microservices/monolith.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1aWVPbSlx1MDAxNn7Pr6B8X4PS+5KqqSmDXHUwMDAzMVx1MDAwMWIwNoSpWyldS7Y1yJKRZMDc4r/fI1x1MDAxOSxZXHUwMDBisYGwzKBcdTAwMDcv3ZL6dPf3nbX//rC2VoumY7v2ea1mX/VM17FcdTAwMDLzsvYxbr+wg9DxPehcIsn/0J9cdTAwMDS95M5hXHUwMDE0jcPPnz6NzODMjsau2bONXHUwMDBiJ5yYblx1MDAxOE0sxzd6/uiTXHUwMDEz2aPw3/Hnvjmy/zX2R1ZcdTAwMTRcdTAwMTjpIOu25UR+MFx1MDAxYst27ZHtRSG8/T/wf23t7+RcdTAwMTN6XHUwMDFjK1x1MDAxZbFJu1xyb9Jyj1qs+eXLSSiZbOvk0eSmuylcdTAwMDR2LzK9gWunXVfQLjQ1JEVMKMnnXHUwMDFkU+igglx1MDAxOFx1MDAxYTMkSNp+6VjRXHUwMDEw+lx1MDAxOMJcdTAwMDYmXHUwMDFj3V14fsfQdlx1MDAwNsMoflx1MDAxY3NDSJx962z0z2to3lx1MDAxMkaBf2Zv+i7MXHUwMDE1RPxcdTAwMDNriXskXHUwMDE18C+zdzZcYvyJZ83viVx1MDAwMtNcdTAwMGLHZlx1MDAwMCuS3td3XFy3XHUwMDFkTZO3w17AXCLWcmNcdTAwMWPfSk5y7VVPwaCDoWeH8aqns/PHZs+J4tXBKJ1FLOG4aSVcdTAwMWL0ZypTXHUwMDAwW9uMd8ibuO682fEsO173molcdTAwMTdG86zb0Vx1MDAxNu5cdTAwMGVtO35cdTAwMDNWilx0JLWg855cdTAwMTSBXHUwMDFhi3zrvu8laNRcXGvKkUhn4IRccsBTlLy1XHUwMDBmmLTTtY5l+JLBWjqVydgyZ49gyVxi1Vx1MDAxODMlUbqUruOd5SV3/d5ZOkrSevOxXGa+9ZGN7Fx1MDAxZqeX34be9abFPXVQ391YXHUwMDFhvlxuYIZ4XHUwMDExvYxcdENLXCJ4XHUwMDE5fDFcdTAwMDP4ak1idCb4pUX8wvNEUkbf8VuFX1KO37vtTfeO3rbcXHUwMDE0YE25XHUwMDE2iHCmS2CthKqCtZRcYjZWUP5cdTAwMTBYz8VLXHUwMDA1zcAssq/SfcmgdKd10NtY347q7Phb93zza6t/3Tmtze+7uf31TJwpl6bAmYXJzOhcIpSBOaOYYEql0niRNFxcXHUwMDFhXHUwMDAy6Szm7yhDmaHhXHUwMDEyminCyIJan1OGvFOlgip0eVVPYWk1QVx1MDAwNJdQQlx1MDAxMlRFXHQsQFx0SkCaflxiJ55cdTAwMTK2KVxuY/TFXG5+PM5sn+9Fbec6XHUwMDE2OTOZuHXLXHUwMDFjOW683nzhXHJ111x1MDAxOcRzr/VAWDuoZVx1MDAxNyBywGGa3zByLCtrXHUwMDFlevBS0/HsoLmMnfFcdTAwMDNn4Hime1QmtTmJ/EM7nMlcdTAwMWRcdTAwMDVcdTAwMTM7u1x1MDAxZfbXO1x1MDAwMmCD8HtYu37gXFxcdTAwMWNuyM2J6ndCt976KqPGdZG1tus64zBv5zhcdTAwMDOPS1BCXHUwMDE4sFx1MDAxNsl0R5Ilk9RgSlx1MDAxMYVLrJ1cdTAwMDBSXHUwMDBiTFx1MDAxOVx1MDAxN5JiJnD6aMpcXGZwKbgkXG5hwjhR7P+KyanFXCK43+xfT09cIi/onPzEfveIXHUwMDAwhVx1MDAwYsr9l4SX28tcdTAwMWFHUmVcdTAwMWNBXHUwMDA3XHUwMDEw2Fx1MDAxMFDUZZogs0N5TcAlU5xj/SDr+ExcdTAwMDYsZLKO21x1MDAwN/unO+fOdPp1+t/tn/vHRSrE7MrzgFx1MDAxOEIrJlx1MDAxMVJYIy1cdTAwMTZ5oLjBldRcboxcdTAwMWJiRIhcdTAwMDJcdTAwMTmQIYEkUsqYSVxiK62LbGDaQJorLVx1MDAxNFx1MDAwMsJwqt/J8DgydFx1MDAxZk1cdTAwMDZcYoCoXHUwMDA0OvAyLohC65xcdTAwMGJcdTAwMTD6MEGIpi/NhXnz2Hfyjmj6ay1FVvJn/vvPj6V3r1fDOb5cbkBO31fYRddcZqNNfzRyXCKYaCtcdTAwMTay4KBEZlx1MDAxMG3AtjreIN9ne1ZFT/JUPVxi/MuhbVx1MDAxNkBcdTAwMDLP5fvu0Vx1MDAxYdvfRfdIofHkW31d/7VfR4e7k7NltFx1MDAwNoR6XHUwMDA2XHUwMDA0XHKKXCKwcEhnnKSZ1pCGJFpcbqxhMVx0TdmS0Vx1MDAxYVx1MDAxYVOFwHpyKqjWqfbNXHUwMDA2jIgqTCWDQVxihDXvNvRxakM9XmsgXGYuM6dIlqlcclpcdTAwMTlfYkSpVsCqXHUwMDE3z5v8PrVRgef4Klwi+Y3rjUYvJNP69mm30fl2sNHQXHUwMDEzU3t0acebXHUwMDE4WkpccsEy4YCIxTSTINRcdTAwMDDlglGsZUVcdTAwMTYw7973rPVFNMfjvW+sXHUwMDE4kjFcdEo0XHUwMDA3eJeVmkNzgFx1MDAwNFx1MDAwMnv80prjXHUwMDFlOpB+k1x1MDAwNz+1eXC+J7snP9pbu7un41wiXHUwMDFkKjKuXHUwMDFhfGyWz6wmXHUwMDA1XHUwMDAzTVxmLLksXHJCXHUwMDE1MihSPM4tVVZcZt5I+sgyw6H9vPmj749PtTKuMcOwOSWAzoY4OUBcdTAwMGJcdTAwMDZ6S1x1MDAxMPlcdTAwMTA4z6VbKdNaZ1unXHUwMDE3h4dHVrjTOTjqnKtcdTAwMWat7uWLZVrLpSlwpZhp1ZpcdTAwMDLiXHUwMDA1eJVcdTAwMTQryjNcdTAwMWXjjCuymiuCXHUwMDE5sU1cdTAwMDbLjOliSufNpVpfgCutXHUwMDE1ympcdTAwMDTcfdBHrEzLg1x1MDAxZK7U8sBcbk6Vwk/uXHUwMDFmSlxi1PhcbshNgXibtjxpd+r115Bu/YWRyadbc3I/TcJ1b6NcdTAwMTf1jyz13XTbg+tcdTAwMTNn4lx1MDAwNIPlskxKXHUwMDE1at9JgUQzXHUwMDAz/OXyPCtcdTAwMDZGa1FaXHUwMDE5kbFHzfGCyXzVvH3+XHUwMDEyycHjXTaOXHUwMDE0U4KIMlx1MDAxM8c0ybfO2VxmbonWiJNcdTAwMTf32X5TtFdcdTAwMDBmfGUgmb6gYFvfQnR30DjxRk3nXG6+XGL6co2a3k/aWeH8XHUwMDBiM3CJL8tcZs2qTr+AckBk8WzAnOpYaSN3muZVM93y4619VqpcdTAwMWY+2ptVQmlcdTAwMDWxRFx1MDAxOdNJtTOLXHUwMDA1XHUwMDEyXHUwMDE4gmz24kXSe+C8r7tWV3zfbVxmzltO1LTGe+x8sFx1MDAwMpyJoVXJcS5GtSFR7F+UIJpigyOqY7TPXHUwMDBldJXkNykxcv7qq8b181uw9lxujic49prrksrFLFSrXHUwMDA0MMVaXHUwMDEx9cCTL89cdTAwMDTgXbkrzKk41pf1va3h1tFgW1loJVx1MDAwMHNMc0dcdTAwMDdcdTAwMTNcdTAwMDQrYlS4Xu/4fVx1MDAwMvxcdTAwMWWtXHUwMDEyOHFNMKOq7OBcdTAwMTYpptvv8MuR4OCivWb1O72Y9NvnXHUwMDE33b2h2uNnrZ3p6OjMW+k0LSZcdTAwMDJid5zzKDiRRa/hXHUwMDFkv0+I385cbviV4O8jiUvryVhXO1x1MDAxMJQweFx1MDAxNMnXrH9H/vXkOrLO+ZBvn+pLejWddkuqXHUwMDFkxZSV0NpcdTAwMTBcdTAwMDJcdTAwMTW9YYaUIaqyVVRcdTAwMThcdTAwMTRcdTAwMDIoXHUwMDE511WlpKxcdTAwMDS675itwmzFyYjyM+CS6Li0UKZzi1x1MDAxOaw5ZLGkXCI+XHUwMDFh8HpqmXe5qk4zfIpMlWv3MzAo5KlcIj+TdlpMUi1cYpvPSC1I9zT5qDP0dcfZsTvDK8/sXHUwMDA2V8NLebZ3uLRtkZxcdTAwMThKXHUwMDE3clJcdTAwMTTCUaVcdTAwMTkvdeyxYoZiMinWJIYlPVx1MDAxMfXb6i5/9JPrjTPzeHlmMsVcdTAwMDVVuNSZ5/dcdTAwMWPUXHUwMDAzXHUwMDFipLF4UNbptmPF0kqjbu6ehydcdTAwMWU+3Vx1MDAxODrtSaPRPFx1MDAxObRerLRSLs1cdTAwMTJ2XG7gXGauXHUwMDE041xiXCIhJbJ5u4RcdTAwMGac3sNcdTAwMDdcdTAwMGVkXHUwMDAxXHUwMDAzj1x1MDAxMJ+5ab/fWP1v0OFkeTpcdTAwMTBcdTAwMDQxXHUwMDFjw0yXVlVYZXCgXHUwMDE0VpyrV3iCvWGPXX9cdTAwMWGPtdbxnOgpjNZjyyu/MCV5Y1Y5g5VcctuHW/1QM8fjdlx1MDAwNCtdu8vc1S5cdTAwMWP7cqOaXHUwMDAwXHUwMDFmblx1MDAxNUFMXHUwMDAwO0n43Xy4+Vx1MDAwN3qFK98ifQ==AppXSUAAUIsDeployment Unit \ No newline at end of file diff --git a/guides/deployment/assets/microservices/multiple-apps.excalidraw.svg b/guides/deployment/assets/microservices/multiple-apps.excalidraw.svg new file mode 100644 index 0000000000..dcf4de2f12 --- /dev/null +++ b/guides/deployment/assets/microservices/multiple-apps.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGlT4spcdTAwMWH+Pr/C8nxcdTAwMWRzel9O1a1bMuA2oqDCqLdOWVx1MDAxOYiQYUmEIOKp+e+3O1xuXHRJh1x0ittcdTAwMWNS5Yx2tk7yPO/zLt39z6eNjc1g4jubf21sOndccrvrNlx1MDAwN/Z487Nuv3VcdTAwMDZD1+urXSj8e+iNXHUwMDA2jfDIdlx1MDAxMPjDv/78s2dcdTAwMGY6TuB37YZj3brDkd1cdTAwMWRcdTAwMDajputZXHKv96dcdTAwMWI4veF/9b9Hds/5j+/1msHAim6y5TTdwFx1MDAxYjzcy+k6PadcdTAwMWZcZtXV/6f+3tj4J/xX7XGb+o77uF7sjyrds1xu2S+Vzoec8FNcdTAwMTmeXHUwMDFhXHUwMDFlNH2EgdNcYux+q+tEu+5UO5PY4lx1MDAxOFx1MDAxMCY4ne2YqFx1MDAxZJghS0JcdTAwMDJcdTAwMTiK2sduM2irfVx1MDAwNEBcdTAwMGJcIlxuplx1MDAxYpxcdTAwMWTRdtxWO9CnQ2oxXHUwMDBl41d9uPtfXHUwMDFiYNYyXGZcdTAwMDZex/niddWzqi7+XHUwMDAxJYdcclx1MDAxNHXwu93otFx1MDAwNt6o35xcdTAwMWRcdTAwMTNcZuz+0LdcdTAwMDfqjUTHXbvd7mkwXHSvrr6FeombiXt8e+w5SrRnnaVu2mr3naF+69HTeb7dcFx1MDAwM/12IIieQvfQ32+GXHUwMDFm6O+oT1x1MDAwM/Vp9/VcdTAwMTfqj7rdWbPbbzr6vW/acO5u/ebj3eaOXHUwMDFlOo6+XHUwMDAyXHUwMDE0gjDAJcOzPVx1MDAxMVx1MDAwMiVcdTAwMTTJ1iOvXHUwMDFmopGo9ylcdTAwMDCPneZcdTAwMGWLXG5PQXjVa4VJJ3rXulx1MDAwZqVcdTAwMTjWokdcdTAwMTn5TfvhXHUwMDE0yFx0wlx1MDAxMkJcdCWL7tp1+51kz7teo1x1MDAxM90lbP352Vx1MDAwNN+tqnt7UuBfRuK6NuxuV/Z4ULxPw9fpdl1/mFx1MDAwMK/gxEJcbu9USiEh4GhcdTAwMGXBlDNcdTAwMGIwZoYw41x1MDAxNmNcdTAwMTBcdTAwMTPKOIaEQZSGMFwilrpcdTAwMDTlSFx1MDAwMIhcYkWC/Kug/NimWlx1MDAxMbzev76fnFx1MDAwN/1B7fxcbnr1M6Qw/Lh/XHTE810z5P+JbvX4udFjy88kXHUwMDEzXHUwMDEwQEh9XHUwMDEwXHUwMDA0TUzggmUxXHUwMDAxqnNcdTAwMDQhmL9nKlxmXHTfhqfVo8uDXHUwMDFidzLZm/zYvTr6lqaCukeKXHUwMDA3yFJcdTAwMTZCXHUwMDAyTlx1MDAwNZRAsnlcdTAwMWVcYmFBXHUwMDA1diAwXHUwMDA3XHUwMDA0MZZcIlx1MDAwM7C4gIhzjlx1MDAxMVK2XUiZZlx1MDAwM5FcdTAwMTaQVKiHXHUwMDA1ijBcdTAwMTTLNVx1MDAxOZ5HhvqzyaBkXHUwMDAxc0VcdTAwMDdq4lx1MDAwMuOZXFwgQnIsXHUwMDAxeHMuzJp9z417OHqLftuIkFx1MDAxNf4x+/3vz8ajt7LhrLdcdTAwMTSQo+ulvmLXXHUwMDFlXHUwMDA2X7xez1xy1INWdCdTXG5cdTAwMWTYg6CgPqvbbyX3Of1mxp7wrO3BwFx1MDAxYrdcdTAwMWQ7XHUwMDA1XHUwMDEydV5y31x1MDAwMquxe8zqZ1x1MDAwMvijr9tb8vvRNjg5XHUwMDFjdfJYXHJJoPLwJGKcXHUwMDBiXHUwMDAwJIpcdTAwMThcdTAwMWRaXHJcdCxcZrhcdTAwMTBMqpeJcMSWmNWQXHUwMDEwXHUwMDBi5TtCilx1MDAxOZYy0sfIaihcdMZcdTAwMDJiToCywFxmoLWGPs9siOdbXHJcdTAwMDA5XHUwMDAyVH1bk9kgINk6NVx1MDAxYlRRhTMhXCJr89tZjVxmOOstXHLkXHUwMDBmbjaKjSGabO9e1ou1r9VCUY5s2ce5/W6s4j9GICOIclx1MDAwZecjR4aYRaREXHUwMDE4KCOrjEvacqyd7zcwXHUwMDFjz3e+oSCAa1x1MDAxMlx1MDAxOFxmh8ApLyQyXHUwMDFjUGAgY1x1MDAxZeP7c73R9T5cdTAwMWRcXEm7elPm9fOL053Dw0s/d1x1MDAxMkVcbmpcdTAwMTGOXHUwMDE4jUeaYVx1MDAxMkVcIlx1MDAwYnKq3ZF0XGIqtMBcbioxp5lZlFx1MDAwZpI9adrDtvO66ZPjvHDGmXAmVEJcdTAwMDLVxzHgmZJU6yyrQoDQdu0pcJ71LupnXGZegXNcdTAwMTd9l1x1MDAxODq3yc7l7cnJWXN4UKue1W7ERaU+3pxcdTAwMWT3M2Vccl6UK+bepLgy9zBcdTAwMGY0kVghnikvXHUwMDAyK4tAY/7iXHUwMDAzV3g2V1x1MDAxOLG0JCthhlx1MDAxOM1cdTAwMWRcdTAwMTEpxporWVxcqSyRakTK2Vf2iJiMPCWZQaXONHKl5KuOKbGKVGPav4R3XHUwMDE44k89/flpbXs79lx1MDAwNb1+cOreh0JcdTAwMDfmWnfsnttcckOfuWtsd92WfvjNhuquM9iMv4HAbdjd2Vx1MDAwMT232YxLQ0Nd1FZh12A/j8Z4XHUwMDAzt+X27e6Zud/2KPBOnOFDz4PByIm/XHUwMDEzZ2/KXHUwMDAyaCG6gLvlQiO4PmuKY7t72ro/d0fuoJUrxSSBXG6bk/JGJLNUXHUwMDE0ac6wQsVmOVdWmFFVJ2YxhXNy+a45u8Cri/q7UspWn+2tXHRcdTAwMDKBIMp3Nlx1MDAxMJnEbG+CyFxiKKedKDv81t7aXHUwMDBihXkpWOotXHUwMDA2yOhcdTAwMDIpVf1cYmFdtXje7+27d+o/XHUwMDA0Svdgv3+Fa0tUXHUwMDAziVx1MDAwNVxyXizJpjlcdTAwMTGWXHUwMDA0XGJcdTAwMTNsXCI6XHUwMDE0ymrM11x1MDAxNt81z5ue/rSvqs0nz/ZjXHUwMDA1XHUwMDEzUqgowuTGomw3Vjm+hGJcdTAwMWGPz99fXFx2JOvNOjs+LLZuKm6w3/TL5Ka1XHUwMDA0nJElhaG4TbC0OOBcdTAwMDCbfE1cZi1cbrDUaH8ob1x1MDAxYvKaXHUwMDE4WVx0T/Vd4/r1q9unS7icyqWXVEVcdTAwMDUmpYLZLickXGapczF784zkXHUwMDAyXHUwMDAwXHUwMDFm8kNmT9g3Od4u77R3zlq7olx0llx1MDAwMjCFODGQXCJEsEBWXCJcdTAwMTJa43eF+D1bJmSiXHUwMDEyQYKFNOBXuVNZ+GXKOOtcdTAwMGb1nuE7uVx1MDAxZF2f3tzWy21Rpp3KwaR31ukvNbhcYlwipsJ2mHApKOJpt2FccuBcdTAwMTVcdTAwMDK4tlx1MDAwNIC5XHUwMDA0XHUwMDE0cGgsJCOQ7UFg5StCieibXHUwMDE3klx1MDAxNyC4592P7oPmXHJt091LOcZ3k0ndUOdIZ6uYlFx1MDAxNmMg7Vx1MDAwZVx1MDAxMyAslpWowszCVEquXHUwMDBiqpxjYoDuXHUwMDFhs1mYzVx1MDAxOFx1MDAxMmFcdTAwMWVcdTAwMTLHkcSAc5PRTce8U8hcblx1MDAwZShCXHUwMDEyvJ/odpqmqu1cdTAwMGZXkaTqOtcxXHUwMDE0pFJUgVx1MDAxN8s4zeen5jqbTEbN9W41qahcdTAwMGXYO3BcdTAwMGacWvuub9dcdTAwMDd37THvlE9yS1x1MDAwYqfIXHUwMDEyMpWRwipcdTAwMWNcdTAwMTWSUKNjXHUwMDBmXHUwMDA1sVx1MDAwNOFhmSbUlWgk1ItVXFz+uFx1MDAwZbdcdTAwMGZOzG/5iUlcdTAwMDRlWECjM09cdTAwMTdcZtCDUnFcdTAwMTNj8aRo9HHHkmWV4rZ9eDM878PLQts9XHUwMDFkXHUwMDE1i/vnrcqblVXMvckhVFxu0MqXXCJcdTAwMTQgwVx1MDAwNKOxXHUwMDE0fMhcYopcdTAwMTcwgiq6QClcdTAwMDGgXHUwMDBmftrLq9XvQYjz/IRAQEVxXHUwMDA0XHUwMDEyaa6oZIZcdTAwMDdcdTAwMDKqc1x1MDAxMH37snlKqoqO3/Um+l5cdTAwMWK1vlx1MDAxYqxCtp5bW/mFmCTlLPNcdFYjbaOaLyG/vXNY7bp5s1fnO559l1vaXHUwMDA0l3o0L0tUT0LXk0qLXGKWXHUwMDEx+FNuYVx1MDAxMe56XGKbXGbDaj7KgILXZ/TF88dcdTAwMTNwqSdcXDBukj7GM2dpIFxiMJOMyldcdTAwMWNRsFcola79XHUwMDFh+XJeqpcnXHUwMDE409JdtVx1MDAxZJO+z+bLPpx81vHHo3tU3Vx1MDAxYnW2Llx1MDAwZjvH3vejnfL8Xab3t3XlXCLvdS+527kjhVx1MDAxZNQsXHUwMDA0peu94y9b/nec77q/lmqktC42rv1ZUm1+ezmkWkhcdTAwMThcdTAwMGUjXHUwMDA0ylxuM6T+mmc3I1x1MDAwYtiNiSXVxiRcdTAwMTGIIGgsrK5Dyyx6Xy5cdTAwMTFaMuUzXHUwMDExbFx1MDAxOOygbSzOzEdcdTAwMTNcdTAwMDEkxJKsNrZcdTAwMTScXHUwMDEznUt4jmBv+/57XHUwMDEw6V/IYlKk53q9XHUwMDFhYf6+V1xuXHUwMDBl+EmrUebHI390elmp9nj+mFNcdLNOk1x1MDAxOeJOwqCeVZBRUVpcdTAwMGLzxtOZ23h+gVx1MDAxNHFcdTAwMTWUXG6TLPPY2ONUfVSxTp31tPLS02TZxzUk6zetRqmxVzjpl8vH58fyzWTO3Js8XHUwMDExqVx1MDAwNFx1MDAxNmKIQ0BDuiRlji3gylrmwtYnkqW5hMxcdTAwMDFcdDBcdTAwMDfIXHUwMDFjl/JUa5T1R1x1MDAwMjO06vHcSueA+qFLQPe96twvVOZcdTAwMTV0ruFcdTAwMGb83bNRwS+yfvlmdMxqndZubp2TXHUwMDFjW4xNS3CpXHUwMDAwXHUwMDE0cZrYs9a5XHUwMDE1UNd5ts4xhiBcdTAwMTPcuEpcdTAwMDDPrkNcdTAwMGKmXHUwMDAyXHUwMDBmddYrhp/HlLQrt6fnVVjdLdk7x+JqWP7yXHUwMDFih5/asOH4+lx1MDAwNM/SZfPby6HLUlx1MDAxN0Co+txcdTAwMTm6TFx1MDAxNnB7rcth61x1MDAxM8l9nV+XOeBCXCJqXHUwMDFjzkd5trtcblx1MDAxNPU5XHUwMDA2dLXD+X6j8PNcdTAwMTei+FxusuxtuZNvvNxcdTAwMWLSinu+9214UWzVXHJcdTAwMGW1YYFcdTAwMDekwktoXHUwMDFljSCFxVByXHUwMDA1nylrubBwJl05S1xy0V+XeGaUbeWnLNTlM1xuhNGTznSk1Vx0ypFe9ZIkirCQXHUwMDEwXHUwMDAx4lx1MDAwMH2TkfZcdTAwMDbohc0z0EVcdTAwMTdIyedHXHUwMDE4aT+xQfHusLGnfr7Sxs7lmI5cXC9cdTAwMGaZpeRcdTAwMTahXHUwMDAygPlcdTAwMTFE01x0NVx1MDAxOCf3zMiM07NMIzJcdTAwMGJrXao18bidn8dcdTAwMTJcdTAwMTLO9dooXHUwMDA2XHUwMDFli0zl5VBKTFRktHJcdTAwMWVcdTAwMGJcdTAwMGWfJLyrnTFjgJ3eQsB9cFx1MDAwZS9cdTAwMGUlXHUwMDEy7v48ifXiKTi1vFhIYlx1MDAwZS2CYcbCY0haJHNiXHUwMDFjUCpcdTAwMGZfYNW8V2byXHUwMDBizYnrmJm8zPJhXHUwMDAySYFcdDRcdTAwMTGcZlx1MDAxMlxcXHUwMDE5XHUwMDA1LqlET0pcdTAwMDQv9KyVXG48aW7rSlx0bkCk3rZcIjCukuWxr/W4smWe4lBcdTAwMThcIjRG+pFVvzS5XGKWiGGd1qeRi6WxZfv6k1k80flcYlx1MDAwYnOmJas7i4OFWHf0XCJcdTAwMGWMak9cdTAwMDdwXHUwMDAwlEtcdTAwMTeNLZ31XHUwMDA2WVNzkFx1MDAwNuYy1ixpiJzud2+cK2WwOLex0NAxaTEokubsYVx1MDAwZYp6zVx1MDAxNErz2DLELIp4xsRAYKGPYeVcdTAwMTbN1n8hM9d9tpmDQFx1MDAwZkSRzDiJXHUwMDFmZ6dcdTAwMTCYQIhcdTAwMTNcYlaeQlg2+fVCnoxcdTAwMDGS4YlcdTAwMGZgfFxyK5fbrCgrR7BcdTAwMWXTSbVIXGKMabzPMyv3XHUwMDEw9j/NxOW2uKorjCEkMGeCXHUwMDEwXHUwMDE1o1x1MDAxOFxmLrXEdGmOt7JxtEGHk1x1MDAxZuCub1x1MDAxN7rVQuuywi6bXHUwMDA2Z864opXiioKGnlx1MDAwNZOKyVx1MDAxONJzRZN7ZiNoucWUXHUwMDFkNFx1MDAxYTnEX2RcdOR37MxFqKhcdTAwMDTfncZhlVx1MDAwN9Vt5yvw6Wj0o1aMg+tIXvlbXzv3O+iU7ZRcbn048JrHT1jXapg/vEOCQiziK/fEpjnhzFx1MDAxNVx1MDAxMSAkXHUwMDAyMVx1MDAxMU+Xr8Ysqq5cYrGiWv3pVdu7aI47R5XihXf4XHJcdTAwMWN1h8P9XFzZXGKuRJxcdTAwMTkn6TFcZlwiNyqFfIkskCxcdTAwMTHG9X2+vL9G/sqRXHUwMDFm5Ec+XHUwMDE0XHUwMDE0KHc5vSasfjMke1KGim8lYfHFgN5cYvkv41x1MDAxMFx1MDAxOCCst60pelfpXHUwMDExvEF2o9e8KJzYO0RWXG4lXHUwMDE0nNdbXplcdTAwMTnqhEZBhIBcblx1MDAwYiCSXFz2Y2pcdTAwMTegXvlxrYjv0S6Mllx1MDAxOerKhHbNpXHtkFhcdTAwMTEhtZy6XHUwMDA0Up3JVj3WdZWS+CVoleDOwc6gMlx1MDAxY5VG1eL+3uS4lE9cdTAwMTKxilxipGlBR0a5JWRcdTAwMTLfM0mEXHUwMDE2ppRk5fYggkDiNfhfXHUwMDEy/Lf5wU9cdTAwMTnTYVx1MDAxNTZcdTAwMDXJKLambTLdj4gyfEL8rpqYxrDetlwi+H5wVVxmxtvS/1Hi1yfji+JV7awy6J5cdTAwMWPkXHUwMDFiPUOzUv5cZsNcdTAwMDWeMuPhJEtcdTAwMDapQo5eZcwgjP9Go7Aqzo/zc55QoFx1MDAxNI+m2a07TDNDQP3RlM3nq65cdTAwMDA8nfPTsTWl2/A20ct6bytcdTAwMDckO7j0SJpPj1jYtH3/NFDvb3Oa0tq8dZ1xIVx1MDAxYvyfXHUwMDFlTYBcdTAwMDa/XHUwMDEzplB/fvr5f3R2blx1MDAxYSJ9XSUAAUIsDeployment UnitAppAppAppEvents \ No newline at end of file diff --git a/guides/deployment/assets/microservices/multiple-dbs.excalidraw.svg b/guides/deployment/assets/microservices/multiple-dbs.excalidraw.svg new file mode 100644 index 0000000000..bb45fc5baa --- /dev/null +++ b/guides/deployment/assets/microservices/multiple-dbs.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGlT4spcdTAwMWH+Pr/C8nxcdTAwMWQzvS9TdetcdTAwMTZcdTAwMGUuuOKGjrdOWVx1MDAxMVx1MDAwMuRcdTAwMTBcdTAwMTJMgoqn5r/ft1FJyOKAMqOeY6bKge4k3el+nndcdTAwMGZ/f1paWo5HXHUwMDAzZ/nr0rJz27Q9t1x1MDAxNdo3y59N+7VcdTAwMTNGbuBDXHUwMDE3XHUwMDE5f4+CYdhcdTAwMWOf2Y3jQfT1y5e+XHUwMDFk9px44NlNx7p2o6HtRfGw5Vx1MDAwNlYz6H9xY6dcdTAwMWb91/zds/vOf1x1MDAwNkG/XHUwMDE1h1YyyIrTcuMgvFx1MDAxZsvxnL7jx1x1MDAxMdz9f/B9aenv8V/ocVtmxFx1MDAxYW1U/WHdO66z2traWSSZPNLjS8cnPT5C6DRj2+94TtJ1XHUwMDBi7UJTS1LEhJJ80jGCXHUwMDBlKoilMUOCJO03bivuQlx1MDAxZkPYwoSjx1x1MDAwM0/O6Dpup1x1MDAxYpvLMbeExOm73o/+dVx0TVqiOFxmes63wINnhSn+gbXETZJM8NJu9jphMPRbk3Pi0PajgVx1MDAxZMKKJOe1Xc87ikfju8NewFwiLmfGOH2YOcm0l11cdTAwMDWDdrq+XHUwMDEzmVVPni5cdTAwMTjYTTc2q4NR8lx1MDAxNGaGg1prvEF/JnNcbmFra2aH/KHnTZpdv+WYdV+28dRofuthtKmzI8cxd8BKMYGkXHUwMDE2dNKTIFBcdTAwMTOUbd1cdTAwMGL8MVx1MDAxYTXlXGZcdTAwMGJGZTJ8VFx1MDAwNTzF47u2XHUwMDAxk06y1mZcdTAwMGVrKawljzJcdTAwMWO07PtLsGSEakxcdTAwMTXVKlx1MDAxOdVz/V525l7Q7CWjjFt/fC6Cb6XvIOf7+c1217/71uK+OqjsrM5cZl9cdTAwMDUwQzyPXsaEpSVcdTAwMTG8XGK+mFx1MDAwMXy1Jlx1MDAwNp1j/NI8fuF6XCIpLN1cdTAwMDd+S/BLivH7uL3J3tGHllx1MDAxZjlYU65cdTAwMDVcIpzpXHUwMDAyWPPUxmVgjWH2XFxcdTAwMTIq6XNwPZlfMtNcdTAwMTTOYuc22ZhcdTAwMTRMt+pcdTAwMDfN1ZWNuMJOt1x1MDAxYlffNuvtu5Pz5cl5P1x1MDAxZT6VkkZJXHUwMDA1bFRyMaQpnk2ONFNcdTAwMGZzz1x1MDAxN6EszFx1MDAxOcVcdTAwMDRTXG5TwtOs4dJcdTAwMTJIp0H/yFx1MDAxOcosXHKH0ExcdTAwMTFGpuT6hDPkgyslXFyhs8t6QFxuXHUwMDA3kU5wXHUwMDAxJ6guXHUwMDE19ZhIxSlRXFw/h1x1MDAxM4uEbYJCgz4j4Vx1MDAwN4PU9lx1MDAwNX585N6ZKaf0lmldt/uuZ9abT92h4rlcdTAwMWTz7MtNmKxcdTAwMTMup1x1MDAxNyB2wWKanNB3W620fmjCTW3Xd8LaLIomXGLdjuvb3nHRrO1hXHUwMDFjXHUwMDFjOtH9vONw6KTXw9l8JFx1MDAwMLZcYn+CtStcdTAwMDfu9eGq/DZU7ZPIq9Q3ZVxcvcuz1vE8d1x1MDAxMGVcdTAwMTSdxtpigFxmJaRcdTAwMTRcdTAwMTKssiniclx01lx1MDAxNifT+uqRulx1MDAxY1mESM1cdTAwMThVgmmENMtTXHUwMDE3K4swRjilXGZcdTAwMTOBXHUwMDE4Te7/b6ByorNcYm7X2nejs9hcdTAwMGZPzi5w0DgmwOGcdP8p4+XGrOqRlKlHXHUwMDEwXHUwMDAyRFx1MDAxMlx1MDAwNZK6QFx1MDAxNEilykRcdTAwMDHTXFxcdTAwMDBU5GIlwWKtvojJXG4+Otg737pyR6PN0V9cdTAwMWJcdTAwMTd7p3kqXHUwMDE4dmV5oCywaVx1MDAxMUWYXHUwMDBiLOQ0XHJcdTAwMTSzXHUwMDA0ZUxcbsxAWVx1MDAxMZ3jXHUwMDAysmBdtFx1MDAxNESAZ6O40KLAdVx1MDAxMVx1MDAxNpiPXGIp0HNSY4k/qPAyKjReTFx1MDAwNXCAqFx1MDAwNDLkTELDXHUwMDA0kTNcdTAwMWZcdTAwMWaZQJRcdTAwMDKfXHR8oNdmwqR5XHUwMDEwuFkzNPm0lFx1MDAwMGv8ZfL5z8+FZ6+Uo9lcdTAwMWM5XHUwMDFjJ/fLbaJnR/G3oN93Y3jQuplkzjyJ7TBehV11/U62z/FbJT3jqyphXHUwMDE43HRcdTAwMWQ7h1x1MDAxMbgu2/eEzNjYXHUwMDE3jWOFXHUwMDA2w+3Kir7cq6DDnWFvJpkhtCU0XHUwMDA2q1dIJpmYdlx1MDAxNblcdTAwMDJXXHUwMDBmlFx1MDAxZnhcdTAwMTVcbv7hRFwipISGkkb5clx0W2+s31x1MDAwMqHBLFxmYlx1MDAwN2GKNYzDWKJkP6TGc6SGernQQFhcdTAwMTLEKUqUREpqMJJtfZRcdTAwMWFgSElNXHUwMDE1/1x1MDAwN0uNXHUwMDEyOJsjXHUwMDBm5HcuNqrNiIwqXHUwMDFi543qyfbBalVcdTAwMGZt7dOZrW5lXHRCOcFcdTAwMDJWgiaGmOGCwNRCXHUwMDFhKYw5ZzLtsX1Y3lx1MDAwZq2vXCI4Xm55Y8WQpKrQ8FY0J04m4VZcdTAwMDVahHH8lsOtpF3j4YW2XHUwMDBmrnZl4+z70frOzvkgz4aScKtW3JJIXCKKSSZboFx0iFxyLlx1MDAxNS6ItypkUaS4iSuVplx1MDAwYt5J6KhlR13n98aO9l9cdTAwMWVnZVx1MDAxY6xcdTAwMWZcZptTgGeeslSyilAzqbR8llx1MDAxZZzMbq4oa4Wtn19cdTAwMWZcdTAwMWVcdTAwMWW3oq2Tg+OTK/W93riZI8pquMLBU1hcZleKZ5PjSj7KqjW1wOugXHUwMDFjlKiiPCX277lcIsu5XCKMXHUwMDA3qzWnoF3I1Fx1MDAxOe8uzPpcblxcqc+RUyOYmFx1MDAxNCYrXHUwMDEy8lxcPFx1MDAxMWglXHUwMDFhnEqGn5V8eFx1MDAxMrlcdTAwMTJjPFx1MDAwN3JcdTAwMTMgPoQsz45OKpW3XHUwMDEwav2JksmGWjPzXkywdXe1XHUwMDE5t49bat/2jjp3Z+7QXHI7M0WYlFK5xPc4OaKZRUWGlFx1MDAxM0uPWIjqqWsmfJXK4lNcbvNNs/ZcdLsume9CSXvwYntcctgoXHRY2EX6jaVCfFx1MDAxOSpLoiihVL26vfZrXHUwMDFjvTwmzfGIxuTqnFJ9XHUwMDBmXt1B9czv19xb+I+gtTtU8y/oyVx1MDAxY1Uv4OXyLMMptGpWVvNcdTAwMDJSXHUwMDAxZTIsKSdOW5lcdTAwMWGaN03yVmC29req5sNcdTAwMTebsUoorcCJKGI5yavxSTqEXHUwMDE5z5qJt5xcdTAwMGXZ041WQ+zvVDtXdTeutVx1MDAwNrvsqjNcdTAwMDeaiVx1MDAwNVMpqIKhOu+uTTL62OJcdTAwMGby4b6KqyA4QYmVsVPfNKx/f2L/aFx1MDAwZYNcdTAwMTNcZnrNtVx1MDAxNEVJXGacr4GZJDHAdSPcuFx1MDAxMm9cdTAwMTjAO3JH2CNxqm8qu+vd9ePOhmqhuVx1MDAwMFxmXHUwMDBmmKlcdTAwMTdcdTAwMWMjWFx1MDAxMavE5PrA71x1MDAwMvB7PI/DxDVgkaqiai2Cc1I5wS9VjCNcIt4yfkfXw/bR1XVjt6t2ea++NepcdTAwMWb3/LmKaDFcdTAwMTGa5+JinMi82fCB4Fx1MDAwNVwi+GRcdTAwMGVcdTAwMDRLjTiSuDCNTFCpXHUwMDA1XHUwMDAxXVx1MDAwMjHQovxcciO4XHUwMDFm3Fxy7+LWXHUwMDE17/KNc31Db0ejRkGaI1x1MDAxZqxcdTAwMTJaW0KgvDnMkLJEWZyKXG6Lcq0lQppISVlcdTAwMDF0PzBbhtmSgoji0m9JNEVSXHUwMDE2SV1WmopcdTAwMDB5TJRmgr06ZFx1MDAxM1x1MDAwND5Ee05q0VwiYlSe007BIFx1MDAxN6GKg1TAaTo8NTXZbCxqanaLiUT10OaWu+WcdG99u1x1MDAxMd52b2Rv93Bm3VwiwY1XOlx1MDAxN42i4I/CXHUwMDA280LTXHUwMDFlK2YpJsfV72PFUlTltGB2/tFcdTAwMWVcdTAwMWbvnJmnszOTKS6owoXmPC+vzsNIXHUwMDBiXHUwMDBlZtTzzPmHjjnTKtWKvXNcdTAwMTWd+fh8tetcdTAwMWVccqvV2lmnPmdaZXGaqng2M2gqXHUwMDAwNFx1MDAxOFPGllRCmXLXaUZw+lx1MDAwNCM40Fx1MDAwNWuNXHUwMDEwvzfUfr26+mdcdTAwMTDibHZCXHUwMDEwXHUwMDA0flx1MDAxY1x1MDAwM0+2MKPCS1x1MDAxZFx1MDAwNIWp1Jq9QVVVdVx1MDAwNl4wMmMtnfhuvFxitfXS1MpPlElWnZU+wWJU22j7dGuv7nXRTsRcdTAwMGVsvblyV6tszVhbo8bmJZg3Ulx1MDAxMLAxU0V191x1MDAxNe3CXHUwMDAyzGiGNcKIpuRpqrhcdTAwMDZzLVxm5Vx1MDAxOcJYXHUwMDE0XHUwMDE05ZnaXHUwMDFhuIWAXsQoSt3k32CQTuT78mVv6yyoxsejK7pWb5Jzue7YtWeU1nx/caaGISaEZrrwjS9e6oFJwsHbkHTRL7doLPCi1Np+7bT3/Vx1MDAwMlXaw9vTm631xvFd35st4YikJcxcdTAwMWI/ILFcdTAwMDTSKlueyi1OMGg8XHUwMDA2kpXokpp2rECMXHUwMDEywlx1MDAxNWYpVyFd0061YFxuXHUwMDAz0bBMJ70/mPBcdTAwMWMmnL+YXHSKcsk0UoXKkpbW5Fx1MDAwMGBcdFx1MDAwMXnHXHUwMDE3qy2fQYVJ8y+oaS9EszlyOE7ul9vE95DGvNyXXHUwMDE3V3+FXHUwMDA3/d3Nw7X9XWzvXq7uzSQ0jEFLuKaMm1x1MDAxYa3Uqy73QkNaxtFgxChWrYtr2oV5l4zCeiqqSFx1MDAxMkqYqmkn4K1oosG41v/WivZFyVxme1x1MDAwMW+EaYooV8XqM1/n/ig0qERcdTAwMWNcdPy8Sr73ITNKwGyOLIzfucioRLp+vdvQgb9xdmR3wrhcdTAwMTZcdTAwMWNcdTAwMTakKkpsblx0Ulx1MDAwMzZcdTAwMGaARFx1MDAxOZ62NEw9u6bgXHUwMDFim3p1TlPZiFx1MDAwZpN73PpcdTAwMWFC4/LlxeyEMSU5KayOXHUwMDEyqPQ1XHUwMDE4gjih5ldHXt3SeIJcZrLeOo1q24NecF3nbad+XHUwMDFhrK3vzFblhy2mNMmkN1x1MDAxZVx1MDAwYv1Ac5YkPpiwxMNvh+TRr5BcdTAwMDX6XHUwMDE4Lb7a7zdcdTAwMDeTflGlX3P2YFx1MDAxMldcYlx1MDAxM8RkUXQ1bzNPKtZcdTAwMTlcdTAwMTjUNP3TIVx1MDAwYoIsMXr39Vx1MDAxNV1cdTAwMDH4zJGC3ftRbp9cdTAwMWXmtmxcdTAwMGZcdTAwMDZHMaz3RKwtX7vOzWo5XHQ+PUhcdTAwMDNDXHUwMDAyZyxccn98+vF/J3uTviJ9AppXSUAAUIsDeployment Unit \ No newline at end of file diff --git a/guides/deployment/assets/microservices/multiple-deployment-units.excalidraw.svg b/guides/deployment/assets/microservices/multiple-deployment-units.excalidraw.svg new file mode 100644 index 0000000000..523405d521 --- /dev/null +++ b/guides/deployment/assets/microservices/multiple-deployment-units.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGtT28hcdTAwMTL9nl9BsV+XybxcdTAwMWZbdeuWwYFAgPCycbi1ldJawlawZSPLgL2V/357ZLBkPVx1MDAxY1x1MDAxYlx1MDAxYyC7qGqzoGdr5pzu0z0t/n63trZcdTAwMWWN+t76XHUwMDFma+veXdPp+G7o3K7/bvffeOHA71x1MDAwNXCIxr9cdTAwMGZ6w7BcdTAwMTmf2Y6i/uCP9++7TnjlRf2O0/TQjT9cdTAwMTg6nUE0dP1cdTAwMWVq9rrv/cjrXHUwMDBl/mv/PXS63n/6va5cdTAwMWKFKHnIhuf6US+cPMvreF0viFx1MDAwNnD3/8Hva2t/x//CXHUwMDEx37VPrHQ97H25uP3UXHUwMDBlxluuXGL0cWV/M740PunhXHUwMDE1Qq9cdTAwMTk5QavjJYfuYL8mXHUwMDAyYcGlVmK6f1x1MDAwNPs5l8goKoWkyYFb343acJBwgogxlMBV2G5sekrb81vtyN5AXCKqXHUwMDE4Z6n7Tlx1MDAxZf/HXHUwMDFhnu5cdTAwMTlEYe/K2+p14GXBxt+IUaRJXHUwMDEzXHUwMDBi/3KaV62wN1xm3Ok5UehcdTAwMDSDvlx1MDAxM8KQJOdd+p3OaTSK71x1MDAwZZNcdTAwMDGjuJ55xvm96TSzv+wqeGirXHUwMDFkeFx1MDAwMzvsZLq313eafmTHh+DkLayF/V03nqE/XHUwMDEzm0KY2107RcGw05nu9lx1MDAwM9ezXHUwMDAzv+7QmadcdTAwMDXu/dNcdTAwMWWmN5k7dr/ne2K759lcdTAwMWJcdTAwMTMmjMRUcDM9kiBTy9zew15cdTAwMTCjlHAhhNJUJKPhXHUwMDBmqlx1MDAwMLQovu0lgNVL5sDa9iFcdTAwMGLCNFx1MDAxMGdwXHUwMDE2eXfJxKRgund03Nzc2Ikq/PxT/Xrr49HluHaxPj3v+/1PyfhccvuuM7GHKE6ZIVx1MDAwMFKiklHv+MFVdnA7veZV8lxu71KDliFNsTU50sy8zIQvUiNcIjgjlDCmtCGzrFx1MDAxMVxuSWzSoH/gXGbjyMAmXHLXlMfMyXOGvnGlhCusmCszZ9+TgsHQXHUwMDFhiikp4ITK733ghOZKY4WpflxmJVaJ2lx1MDAwNIRcdTAwMTZ81sH3+6nZ61x1MDAwNdGpP7ZcdTAwMTZTPLN32+n6XHUwMDFkO9xi5lx1MDAwZZWO37Kvvt5cdTAwMDRjvXA9/f6RXHUwMDBmXHUwMDExZ3pC13fddHhowk1cdTAwMWQ/8MLdReJML/RbfuB0zoqsdoZR78RcdTAwMWJM7I7CoZdcdTAwMWVcdTAwMGbv41x1MDAwM/5cdKJiXHUwMDBlaTeO/ZuTTbU11Je1Qady9FFF1XGetF6n4/dcdTAwMDfZOCc4XCJUMko5kFx1MDAxNqtcdTAwMDTb8ZAphrjWVJOCaCeB05IwLqRiMJ0kuTQhLkdCSaGoxoRyQTX/V1x1MDAxMTmJWJRcXO5ejkeNKFxia42vpFc/o8DgnG//Id/VzqLBkZZcdTAwMDVHcFx1MDAwMVx1MDAxNCZcdTAwMDT8dJEj0Fwiu/fBXHUwMDExUFxm2kdQlnrXXHUwMDE38lx1MDAwNHOoMOCqQk6PXHUwMDBmL/au/dHo4+jbztfD8zxcdTAwMTUsu7I8oEhcdTAwMWFwdlx1MDAxOGtisJGzPNBcdTAwMDJcdK2MhtiGOZUyR1x1MDAwNoxcdTAwMTSQRCllmYSJNibPXHUwMDA2blx1MDAxMDZCXHUwMDFiqTFcdTAwMTBGMPNGhqeRof5kMlx1MDAxMK2ZXHUwMDAyOuRQb7mQmuasUKRcdTAwMDRcdTAwMTNQLPL1RMV+z8/q0OSntVx1MDAwNFnxL9Of//y98OyNcjjbLVx1MDAwN+TkfrlZ7DiDaKvX7fpcdTAwMTG86JE1MqdPXCInjDZhWv2glT3mXHUwMDA1bsmR+KpKXHUwMDE49m7bnpNcdTAwMDNcdFxclz02x2vsfJb1M437w0+VXHLz12FcdTAwMDWf7Fx1MDAwZq9cdTAwMTbxXHUwMDFhkOohpbBmoI8wNjRh9MRrKKSoUZJcdTAwMThcdTAwMThMylx1MDAxMrakvIYhTGOInoJJZkxcdTAwMTJcdTAwMWbTXHQjZpowxeEhXHUwMDE00pq3XHUwMDE4+jS3oZ/uNTBRXHUwMDE0XHUwMDBihlWR22Bz8kspXHUwMDAxKIw9Kr/8NdxGXHSe7ZZH8i/uN6rNXHUwMDAxXHUwMDFkVXYu6tXap+PNqlx1MDAxOTomYFx1MDAwYlx1MDAwYm+KjFJcdTAwMDZyZSqUXCKzZSZJXHUwMDE5XHUwMDAy50Kw9bJcdTAwMTJcdTAwMGW/qe/X4Dmerr6J5lhZXHUwMDEyXHUwMDE0eFx1MDAwZc14dm9cIr5cdTAwMDXRXGb+e3HBMYdcdTAwMGX0cleEX41zfH2g6o0vp9v7+1x1MDAxN/08XHUwMDFkSiquXHUwMDA2NDbPVlbt/DJDXHUwMDExUUJcdTAwMTUmoVx1MDAxYSOGtbClpbjiSvI0YL9I9ch1XHUwMDA2be95y0efn15q5Vx1MDAwMkBEYHJcblx1MDAwMM1EeSg0QkpcdTAwMGVcdTAwMDGCPFx1MDAwNtBT+5YqtVb49sXNycmZO9irXHUwMDFkn9Wu9Zej+u1cdTAwMTKlVq2YhPhGVsOWYmtybMmXWo1hgHlcdLqSWZ+Q0oxcdTAwMTO2qHK2SI5sVIbYTNhsUeeXq7W+XHUwMDAwW45cdTAwMTYvtkKCSCl4JF7k51x1MDAxOdeltFCCgVdmauVcblx1MDAxMVx1MDAwNGuqhvuIcmvjtFapvIaC61x1MDAwZsJMtuCasXs1JdeDzWZ0eebqz07ntDVu+EM/bC1WZ9JcdTAwMWHBbOBskOOGI1DMxZVWXHUwMDAyjDaycGlEWU0tyEzQfNW8ff41kuOnizaBNdeSyqIgx02O41x1MDAwZmzmXGYzLPiLXHUwMDE3TH9StpeDpd1SgExukIusv0J2d1xcbVx1MDAwNN1d/1x1MDAwZf5H8Ycx3lxyvrLawnJWXHUwMDAyoUmBluVcYjTPLP9cdTAwMWZozsE1YDrbXHUwMDFiMCU60Vx1MDAwNuHZfoRXzXO3Z6f2WYl+8mQ1q6U2XHUwMDFhcolcIp7TXHUwMDE0jzM8XHUwMDE3kKdjrVKVuSdr2Vx0XHUwMDA0r7fH1ebn820v2jCNr9XjRnj+7WBW2j7A0LHATonZ3+fdd+MgXHUwMDFj11x1MDAxZH13c+I5nZZsVHbbtc3F7vsjkbzalPLQ1N26/LxfbV1cdTAwMWb50a7bP+DXrSU4SJHRuKCLh1x1MDAxOaSwwqwo2jKCXHUwMDA0ZsZSNE4pcUFRllGUkdivmozPXHUwMDFmdE+X0MqQi1x1MDAxOFx1MDAwMelM0VwiXGagqYx1XHUwMDAwNSUguj6Kdc+E3321L52RPDe3lYPt9vZZa0e7eCn8XG5cdTAwMDJJJ8lcdTAwMDFYU1RcIlx1MDAxNt/gu1x1MDAwMvieLZPqXHRDXHRnuqjZjOLyVE8zXCK00Pw143d0M7w8vb6pXHUwMDFmtPWBuDraXHUwMDFidc+ugiXwy2xziVx1MDAxMTN+Nk5ccqnKa503XHUwMDA0r1x1MDAxMMG1JVx1MDAxMKxcZuhcdTAwMTdFXG5XwYkpbVxyo1hat02JWSmCQUhBtqXUalx1MDAxMNztjYfjyL1cdTAwMTZtsXNhbtndaFQvWKPJl9mkMUhKnNfwXHUwMDFjayTLKmxMXCImjFF2NVgpxlx1MDAwYqD7htkyzJb0c1x1MDAxNGJcdTAwMTZQYlx1MDAxOFaqyOvy0nVcdTAwMTSYNYCWkC/uc1x1MDAxM1x1MDAwMN6XqWq7g1VcdTAwMTTXOt5lXG5cdTAwMDW50lrUS1XKZutqM8Zmi2gz1q2mhOY0++PaMNpQ7INu1lx1MDAxYbuN7vW4oGtxXnpcco5CXHUwMDE05NiSXCLNRYm6V1xmXHUwMDAyR9xozE28XHUwMDE1xFx1MDAxNlwi0CT8vFx1MDAxMbWYqO7iRFXxYJticZ82JctUXHUwMDAz4lxi9H1cdTAwMTJcdTAwMDZWXHUwMDEyW2wjXHUwMDBlxjKN2MfHlk+bwfbBx1x1MDAxM9H8sDVyXUpcdTAwMGb3trecxT8xwVx1MDAwNOJILpJMIMxcdTAwMTBmilx1MDAxNLfMU7tcdTAwMDAkJlx1MDAxZqeUrXlcdTAwMTKJ0iA3/66l/yWg7C1cdTAwMGVlQ6RRuuD7kdhcdTAwMTJWWlx1MDAwNiaQIGiY5Fx1MDAxNFx1MDAwN/699SGtOTE6tS72JFx1MDAwNm57X+jW9vC80b5cdTAwMTnpq1pTVIJv11x1MDAwYjNQYYF4/ruTmIFcXCFJREmGwiFAMNt0cE8vLVx1MDAwYii44ujx22W8/eJ0u1xcnG5aWuKIfDudXHUwMDFk3LzuS/Jq224piGSrY9uPOlx1MDAwYj6cXHUwMDFjbu35w72N6MtOVX/4Vj8j1cOlSEHgZYVYXHIpiq1ZIOVRXHUwMDA0wlx1MDAwZZE2Qk6yl1lK2Fx1MDAxNvlSSjBsP32EkZfa8NmE/6dlPv9cZkq0XHUwMDE2p4TUkjFJaGFXXHUwMDAxK62Ualx1MDAwMlx1MDAxN2mhV66lllx1MDAwNW0u66mmylx1MDAxNC/XUfCDKJJNhtJGryZcdTAwMTe6wlx1MDAxZvf8Pa/Wvlx1MDAwYpx6eNe+VVdcdTAwMDcni1x1MDAwYkngnmJcdTAwMDXLXHUwMDFjzDbqq0x0m8YwOCYln1bZXG5cdTAwMWFI30JYXHUwMDExX9uL85VrIdmM8Es3x5WWhinmithv8p6xOa5acfavXHUwMDA3jYBcXGy2/dNhtbrbaFx1MDAxZC1cdTAwMTfCLFx1MDAwNldUtSu2ZoFcdTAwMTCmOcQhyaRcdTAwMDZBjmk+hPFyQrxFsMcywl+cXHUwMDExXHUwMDE0S0M5KWiaWY9cdTAwMGIvZZQwglx1MDAxYVxifPhRKdQqQftKQ9hcdTAwMGZcIsjPXHUwMDBmYfNT0Uy6OKs6uURCXHRSsEzEJZ4jOaVGpX8yXHUwMDAwrvtH9MQl9q6UsWExY5f6kFx1MDAwMUuhiC7siSO8dNFIcJhPmJpHhbZ5VVx1MDAwNUz147ToSpvi8pi028Y9XHUwMDFjk6tz4XTpjrjUXFzd/ymc3Vx1MDAwNdrWYnfUXHUwMDFj2tfdsJ9OxFx1MDAxMp5pkI7ELnWlTms5feuSXHUwMDExZNL3q2A5KMz04ZXZM79ImrJcdTAwMDfMMVx1MDAxNFx1MDAwM7il5lx1MDAwMqQpw1wiZ1x1MDAwZSSok+a5PCyX6fzLuiGv81fvdiFhMr8yNs/NacJcdTAwMTEobdvnkltRVFx1MDAxMoHaK/mrQlx1MDAwMCmebWJK+bms33zVnm5e1/5PcnWDp7s6SoRhhFx1MDAxNX7joml257Q/iXEqgFmPqkPN83SUXHUwMDFilejaXHUwMDE3a/8twGV84Vx1MDAwM1win8PZLeFcXCA8SGKwYJIzI5JcdTAwMGbwXHUwMDEy30LRQyn4cZ5uXHUwMDE5z0uYhKGzn+FwqjRTeVdHXHUwMDExXGJe82ze7t39/Kw7/f5pXHUwMDA0iJu+6PqN791ulucu7+5ccrP+wYup9f3d9/9cdTAwMDOX8yV+In0=AppXSUAAUIsDUDU \ No newline at end of file diff --git a/guides/deployment/assets/microservices/true-microservices-full.excalidraw.svg b/guides/deployment/assets/microservices/true-microservices-full.excalidraw.svg new file mode 100644 index 0000000000..964fb39391 --- /dev/null +++ b/guides/deployment/assets/microservices/true-microservices-full.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1dW1PqytZ9379iled1m933y6766lx1MDAxNHhXRMC7p05ZXGJcdTAwMTGi3FxmQYVT679/s1FcdCRcdTAwMWRWwKylrk1cdTAwMWW8JFx1MDAwMZr0XHUwMDE4PeacPWf3//749m0tXHUwMDE49ty1v7+tuc+1asur+9WntT/N+UfX73vdXHUwMDBlXFxcIuP/+92BX1x1MDAxYt/ZXGaCXv/vv/5qV/17N+i1qjXXefT6g2qrXHUwMDFmXGbqXtepddt/eYHb7v/b/CxW2+7/9brteuA74Yesu3Uv6Povn+W23LbbXHT68O7/gf+/ffvf+CdcXPHq5lx1MDAxM3O1rTu+1yxcdTAwMTXcXHUwMDAzsvl82eqPRm55/NLxTW9fwXdrQbXTaLnhpWc4LzRxXHUwMDE0wpJLhVx0n1xcXHUwMDFhmkuEOopcdTAwMTBcIriYuvLk1YMmXFxlXGI7cFx1MDAxZb1cdTAwMWR4ckfT9Vx1MDAxYc1cdTAwMDBu4cxcdTAwMTGYXHUwMDEy88aRW15cdTAwMWHy9zc0OdNcdTAwMGb87r270W3B14bW/lx1MDAwYmuJayRs6021dt/wu4NOfXJP4Fc7/V7Vh4dcdTAwMTPed+u1WsfBcPzu0C3wPNdcIp9x/vpcdTAwMDVI5HzSq+BDXHUwMDFizY7bN1x1MDAxZFx1MDAxMH6Dbq9a81x1MDAwMvOYMFxuv4VpYW+vPu6r/4Zt8qGX90xndVx1MDAwNq3W5LTXqbumXHUwMDBi1qqXM5/Wqb9+2szdfdc171x1MDAwMJ0hKaJTXHUwMDFkXHUwMDEyYlx1MDAxMVMko6eL3c5cdTAwMTiYVHFGKZY0/Pj+JkArXHUwMDE4v+stwNNccp+1acPWXHUwMDE07MKvMujVqy8vwZJcdTAwMTGqlFBIovBRtrzOfbTlrW7tPvyU8dnvf9qQzFWtxi99dNpcdTAwMTNcdTAwMDPGXFxcdTAwMDPpwjA1kiVijuZaXHUwMDBipKmMIFx1MDAxOS4pXHUwMDE2vfKGZIFcdTAwMWNFXHSimDBtXHUwMDBlXHUwMDFlRzLl2UL3X7fj44vD9jY9bJlcIlgqTqhcdTAwMDW2XFwlolx1MDAxNlx1MDAxYsxijrReXHUwMDA2tq9cdTAwMTdCnM3AKHCfw3FjXG6Fp12/dOBcIrzLj9zKQf/+/LjQKq5N7vv++tdcdTAwMWNOSKqoXHUwMDEw2XDC3ppcdTAwMTgnZr7MXHUwMDBiXHUwMDFkXGJyiORacYmllJRFXHUwMDE4oecwglwiXHUwMDA3XHUwMDAzXHUwMDExXHUwMDE0XHUwMDE2SjNOp4VhQlx0sqKEhVx1MDAxMo30lMCIc6pcdJXaxlx0plx1MDAxMjmBqMbQp0tRXCJL1IYgNOCDb795OtV73U5w7I1Mg1x0mjm7XW17LfO4+cxcdTAwMWLkWl7DfPO1XHUwMDFhtNX116a/fuCBZTS5oe3V69ODf1xy3rTqdVxcfy+NinR9r+F1qq1cdTAwMTNLo6uDoFtx+y/NXHUwMDBl/IE7/TTc3TfoY4fwOZTN6UvBdp9cdTAwMGb3K4Wm389vXHUwMDFjXHL3rrfjlHVbLa/Xj4iYlsRcdTAwMTFcdTAwMTQryuNcIkalwzDCVmtcZktHMMWmX1x1MDAxMzJcdTAwMTWuSazk1yZs2NZcdTAwMWZcdTAwMTN2MlivbTc22M1F5UF29pr7srBHZeXo6W0wX4DXzfS81lx1MDAxYWMsbEKHKY6efSM1QVxmRlkmWSiFXHUwMDE5sZpxovQ0jpfXoqebXHUwMDFjfvLr5VP34vFcdTAwMTI3/OuWpypxYFx1MDAxYq5EUK3AyZBcZlx1MDAxMcriqFZcdTAwMGVDNkiDY4JgqJu5NsE0aFx1MDAxYprRpVx1MDAxNaRcdTAwMTeFtLdcdTAwMDCkXHUwMDE1x4xcdTAwMTBtUyow0ZJArYmkXHUwMDE0keWMtywxPTnd63pR2zD861vYIeN/Jn//90/r3Vx1MDAxNoCaY/1ccpvh62Od06r2g41uu+1cdTAwMDXwxUqmUbEnXHUwMDFmVP0gXHUwMDBmveV1XHUwMDFh0Wtup55wZfyqnO93n5puNdb38LrotTls7+h1/65QkqeqfJlcdTAwMGaCvKye9mopZVxmI8FcdTAwMWSNMI1cYs9cdTAwMGLjtVx1MDAxM+X1SsZ+XHLn79JzniCCwDrlVvNcdTAwMTSImER66DxNwLD9aM7PQba3/bjnXHUwMDFm73fy68fd/PV2zmvQ6lk6XHUwMDFkw1x1MDAwZTikiDNcdTAwMTFDNVx1MDAwMF4kXHUwMDE4Z1x1MDAxYV7FI6+ZUjJM8KxcdTAwMWK2wvWiuL5fQMsoQkpbXCJlpsGYJMFcdTAwMWFLXHUwMDAxuJZLRlwivoCYxTFqjvVcdTAwMTCeX1xczva8i9ZdfXhw2DxqrHvbpZ3z26OLNIFcdTAwMTStmcM1k5E4+JuUXHRNI87aJK4oXHUwMDFkhbVcdTAwMTZcdTAwMTjsejCgeOhb/7Qgys9cbonXq/2mm1x1MDAwMfNcdTAwMTegdGuB4FwiMlx1MDAxY1Ik5l3BNSqiJyeMVmZcdTAwMWNcdTAwMTdcYoV9kFx1MDAxMaM5XCLyXYGUrcfxx4RcdTAwMGZr6WBKy72dQkIslFx1MDAxMnR7SXGUmfZGgybRXHUwMDA2Zlx1MDAxNDhpu8i9vHo6aHZGXHUwMDFidd5R5VxcIZ8+/s/BrlRcdTAwMTG30DxcdTAwMWFGmMOSSMqlM77wNo1cdTAwMTU6Mz8t+P/7zFu1mJ2kYbz9rdPo65nvUe5i6Fx1MDAxN4FcYmc2M1OJ5Cgo2Jlcblx1MDAxMfxcdTAwMGInXHUwMDA29kvlWn59J8ix84Ozh43d0u3o9GqBiVx1MDAwMY1cdTAwMTnWTKBpbixvxNpbk0LPpERcdTAwMGU8O00wR1xufuNcYll0Mlkoc8xcdTAwMDSZ0ExcdTAwMTFGsNWe/SqS9lx1MDAwMWxR6SWNwrPV4ILZJE1OaUBM0zQoIahcdTAwMWFehlx1MDAxNllcIjemableL1x1MDAwYkF77+zAXHUwMDBmNCYqdDOtzkbl1sveYyUvN1x1MDAwNur2tN/KlXZlsDlKXHUwMDE5V5FCOzDqSVx1MDAwZfzDispcdTAwMTDd40dGsIOlnvEzJ+lcdTAwMWHY0ZJRLVx1MDAxMWNcdTAwMTJkLnR/JszF3CFMUMKo0tDfKpTCf1x1MDAwMpFDyVwi+HbvdjS8XGI6/unFNe6enVx1MDAxMGBwbHT/Md930qojSVJHXHUwMDE4XHUwMDAxiCRg2dqmXHUwMDEzpEpcZsLA0My0RjRb0zZbXHUwMDAx6zOZw8fl4tX+gzdcdTAwMWPuXHUwMDBl73aui+dpojBSKEdjoSX4Y2D46yhcdJQjXHUwMDE0V1xcc1xuUjdlQbxRXHUwMDAxOVxmhkhJOVx1MDAwMlx1MDAxZVHw4VicXHUwMDBiXHUwMDE0buJcdTAwMTJTqlx1MDAxMdBcdTAwMDb0Tq7I8D4yXHUwMDFjvZtcZliZXHUwMDExT1pTn6SInZ1wgVxirVxiVzjb1Kf3aGKmkZv1ZDybI47k8FxyY934XHUwMDE1XHUwMDAyOTtH4uxEod7gILeub4o5VClcZu7TjFx1MDAxYlxuY0dcbiHhOVBcdTAwMDHOf2TcgFx1MDAwN4Uw45SCXHUwMDA2XG5cdTAwMTH6XHUwMDE1U8NcdTAwMDY8QoZcdTAwMDCE0P3wXHUwMDBiW8Zcclx1MDAxOHw4uDOEIKkoY9NpXHUwMDFjq3FjqXHj7P3jXHUwMDA2wmAyc3vIV9JkXHUwMDE3k1xuxKFcdTAwMGazjfh+qnEjXHTQ5ohD+YtcdTAwMGZcdTAwMWObtT5cdTAwMTnmdq7ONk9cdTAwMGbK+U09qOpcdTAwMGVdwPBGYFBJbEJFSs46zZxcdTAwMTPHOF1cdTAwMWFGXHUwMDE1xZEtW3plfv/ykaOegcXBXHUwMDEwXGZcdTAwMTBW61vRxLxcdTAwMDcmtVx1MDAwNKHNOLCcrfV9mK9cdTAwMDW3J3V1VG1cdTAwMWQ3Rlx1MDAxN97A81x1MDAxYumsb1x1MDAwNeNcdTAwMDKOTYVAw5zYxOiUdCZkkXJcdTAwMWTPafsnwH1cdTAwMTFcdTAwMWMnzIQsgmOOXHUwMDE0U4JcYls4iek5k55cdTAwMTiDyY3Jb2o6R3FpjilEfnG9K29edNp73jP8XCJoa4T2Otf0NE7xxMIg5sRpXHUwMDBlzoVcdTAwMDPqZM/YY8JcdTAwMDFcdTAwMDVcdTAwMWNcdTAwMGZcdTAwMDEv9T5cdTAwMTZcdTAwMDNcdTAwMTlT7rze8zVcYl/vmi7+tYxcdTAwMGbSMj5xWkVcdKWVptJGeEJjcy2TNFRGpFRIfLjJO1x1MDAwN9ZFfVY/XHUwMDEzR4XNxkPJXHUwMDBi9uq9Q/bQWFx1MDAwMNZkXHUwMDAy0llcdTAwMDGj2pFIzk5cdTAwMWZOJj6ww19z1l6mXHRtISPiRIroPjWuf72QtVx1MDAxN6iNwJRori1RXHUwMDFkc5ElxnoowdqM3Vx1MDAxZq5Xc/BbkFx1MDAwNVFcdTAwMWSKc/2UO9xubp80dlRcdTAwMWQthF9uzatkijgvxZYr+P5cdTAwMTT4LjB9XHUwMDA3j1FcdTAwMTNsPDnb+JtcXKWJjVx1MDAxN8lcdTAwMTnRn1x1MDAxOcDt7mgwXG7qXHUwMDBmvMl3rvRcdTAwMTN9XHUwMDFlXHUwMDBlzyx+dHzmWWjtiGiS5Fx1MDAxOLpIOVwiWoI8XHUwMDAxr3Ao1+A5I01MXHUwMDFkmy3qtlx1MDAwMm1cdTAwMTJoXHUwMDEzwmdW0CpJTGKkNeM3OcufXGLTLVx1MDAxYX+4t1x1MDAxYlwi8HXy9nTvM+dQzbQum6nlrYte7lx1MDAwMfVb+efTRn5YzW/Vt3K5XHUwMDA1pIU6TGBcdTAwMWSx7sdmv1x1MDAwMG15q+WPMVx1MDAxNEvlUCZn8yNDclLkhGtcdTAwMDDAXHUwMDExpsatyDpL1lx1MDAwNXJcdTAwMWXBTCVMaLuBRHiiwiiJNVfZ15kpXHUwMDA19oWaRu07ap6veVx1MDAxNbXwvXt9eLSpXHUwMDBi7c3H8/3d9HmASDv8RSqixWaUgfOJWSRcdTAwMTi1Wlx1MDAwN+CduO2kh61CklwipuNcdTAwMTX/ZvjlyXO4UilcdTAwMDGdqpZcdTAwMTKZ11x1MDAwYlx1MDAwYqb7XHUwMDFkXHKIf76xfjYqilxyViA7lcrDnrvQOlx1MDAwMFlywt6aXHUwMDE0RpckzIFWSG1WYIitXHUwMDAzQDmaw4jVOlx1MDAwMEtTXCIh98dudyGNpbZXWnGcmMAuNSFcdTAwMTIglnX++sKwjVx1MDAxOV6fYyGAXHUwMDFmyMjPX1xioNjbPUOjytFGXHUwMDE3PdSeusWRkMNBelx1MDAxZFPCQYwrreI6JrnDlEgwx6R8y4N/0bFcdTAwMTBWXy6lfV7dyc9ibuqEg8TgqySGQUJZk/Z44lx1MDAxYVx1MDAwMFx1MDAxOFx1MDAwNI6Dd0x+oci1925cdTAwMDflI3d0SLdcdTAwMGWkfr4qeHfCWyyn3Vj+XHUwMDE5LVx1MDAwMGVvTVx1MDAxYZHTxCFcdTAwMDJcdTAwMWNcdTAwMThJuEn9i4icQnPoXCKYIyhYe3H/5svFXHUwMDE3PoIuXHUwMDBi+CySglx1MDAxZEdp3MxbXHUwMDFixzWTeFx1MDAwMZ2CqDky1bklkFx1MDAxYtO5i+PTaf/646TuXHUwMDA3Slx1MDAxM5W6SLuzUTs+YFi266KXK21uu1x1MDAxNzfPV/fNu9RqNy6tNmlcdTAwMTOaxCPbYMLCiDqrg6tcdTAwMDKu97P3XHUwMDA20XeLnVx0WYN3xq3ZdeCxzXPpJOVyOVovp3a507JXUo+9i1Llcr3UXG5cdTAwMDTGuc5iLp02ky/ZqJ29NSnUToPamVx1MDAxYSyqmZScWiq4XHUwMDEy2bKq4Fx1MDAxYZ9dli5cdPWO1khcdTAwMDfhWjLL2oZj035OXHUwMDA1XHUwMDE39CiZZlNGTt2iwI2J3Scp4PqBzPyCXHUwMDAyLvLYedT3XHUwMDA3RXp/UrzZLcpcdTAwMWKC6mnzSFxydTFWXHUwMDAyXGZcdTAwMGaMXHUwMDAwXHUwMDFj8Vx1MDAwMi5jvtqYu8ogTZ1B2rrS7KnlukVORs3RTWXb3aDdxTNIb1x1MDAxMH9/5lx1MDAxZFx1MDAwNb+DzmTQTaWQzontUJOFg+SHXHUwMDBmXHUwMDAzc3hwfcipauVH+mjnuVR62q/06/VUhVx1MDAxOFpcdTAwMDPDwNfSpsyYyFhcdTAwMDGXSbU2i46sXG64Plx1MDAxM1x1MDAxOcS7yaC5IDMl+1OKqFx1MDAxM2esuFRCc579jNXSirgq33pHVupJ/77SO3xcdTAwMWV5lZ27jV5ul9Xopk4zamBcdTAwMDTGK1LjdFx1MDAwM8ThXGKH1Lf6LfZSq7Kq3/pEw4Z897BcdTAwMDE+jllJkFvHXHI5by1cdTAwMDSijb2U7VpcYp9q4PhH1W9cdTAwMWRfb+eOXHUwMDFm/FxyioeDwWX58qp/4PdT2914vMysXHUwMDAyipoy4nj9XHUwMDE22OKMreq3PtHIkZBvucDIXHUwMDAxNiah2Fx1MDAxZXBWyVx1MDAxNeNcblxcM6GWXFxb6Fx1MDAxN1x1MDAxOd/BPj3G17RJePf87uTxcFRr7Vx1MDAxZKaTUWzyXFzGWabxJHiBXHUwMDFjjvFscvGqiiuL0JF+vy8pNSNm9X9cdTAwMWKcmU6eV9RgPWLB6G9qQf/eVVxcpe2tq839m2p3o1x1MDAxOFxcPe/qkt6+8uNET5pWwcJhkmAh42RcdTAwMDcgxdM2J4LHXHUwMDFk8ZJcdJqYRKDgXHUwMDE5jzdcdTAwMTB5veOLeNi/vqDrXHUwMDA25dKSP3GahUMvgVx1MDAxNVx1MDAxN2e5edGczUIwXHUwMDAzn1x1MDAxYumsbWBcckZSVlJWKtefrmXx4qTcXHUwMDFj3G+2j3aGXHUwMDA3d2RcdTAwMDGEg92lXHUwMDA1VbaSXHUwMDE4hlx1MDAxY5yUtbwqislcdTAwMDLZeTuyrXluXHUwMDAyXHUwMDA0w8xu2GKhJHlcdTAwMTOocSFccrgwXHUwMDFm7sfNwfBuYbdUXHUwMDE5XFxcdTAwMWPUXHUwMDBmytJcdTAwMWY8ja52b7dacVxmW+byiPEsXHUwMDA0kswy740sK5quymKygO1WetiC/yxcdTAwMTmCcdS6YrhK9FwiwO9g4FxmXCLxecyuf2xhjDwqn97u31xcXHUwMDA3p+u5W3yznu9cdTAwMDU7XHUwMDE2es6RXHUwMDE4KeVrcUC8MkYm1q6tKmMy4uv2XHUwMDAyMlx1MDAwM8YuR2bFdlx1MDAxYl9cdTAwMTlK9pOI2XuEoI/3k+Yg+aAgds+2T1xuzYN6oXRb3SyLvcun9EhcdTAwMDajRyMlrDVeTDgmWGiXm1VxzLLQXaRcdTAwMTJcdTAwMDA8Vm3WZLaFukX87CRgRczCVMvtuTJxQVx1MDAxNsqkXHUwMDFhVOsj2jm5vmmWPOKL69Fccr7UXHUwMDFmlkllb01cdTAwMWHri0nAPJNYYCU4iVx1MDAxNcfgOYxYXHUwMDE1xyxNid1cdTAwMDUooZBZwFx1MDAxM1mT6YVInv0xfFBoelOIz2J9fY7qmFx1MDAxZlxiyc+vjuGNUu7kuX99VvBaZ4/1u1qF0ss4aS3reFJcdTAwMGXMM3M5MVxyY5g48d0zw1x1MDAwMHYsLzKcvdHG1+LZ78P0+2zJcoNcdTAwMGVcdTAwMTaRMrCa0czuKlNpwYmLXHRwiaGhbKpcdTAwMTMyYq3AdMo++bhQdVx1MDAxNIHmmMJe+Fx1MDAwNjFcdTAwMDX9XG7Bar9YYqzzeJX3RkpcdTAwMTG0X1x1MDAxOGzuWHZwt81KmYg9R1jz+P4tQOpcdTAwMTd1tpM6VlwiXHUwMDE3yi9Czpdaa+xcdTAwMDM4XViA05KCoSOEPVx1MDAxMlx1MDAxMlx1MDAwZutNSM1cdFNUkcznU03Y6+NJvVx1MDAxZYegOULwfXFSS/EoSzLoqjI7wEf0LsfWn1k6oZZcdTAwMGWihNh2Tlx1MDAwM07H91R74zQh1KGJ9Vx0YHdHQvorUn+bJfVhelJLyVx00TS+mq1cdTAwMTl5k4ObaJxCoDPf51NJpKbWXHUwMDFh+ihO21x1MDAxMGiO9Vx0+L44qW9yO9uqfHPO/IO7bm1Yeng4KeNUpNbU0Vx1MDAxNFx0XHUwMDExjyBcdMxMylnyXCLAYnaf09D4Vs5KopPZXFxMz2YwXCK1oshcdTAwMWFAkolcdTAwMDJNwcTCUrLMl/001kLogH9ggsgs8swxxtxcdTAwMTcncfG+dNtez1X0Zat/py6va/6ok34pSYwwdoDEKJrw9bLEhHCYvYZwtb7E+OyybE69XHUwMDExTnLJLVxirzRJ8LZkXHUwMDEwhZJ3hVwiWJgyXHUwMDAyvNRcdTAwMDTHpH1cdTAwMGJcdTAwMDWKvVx1MDAwYvWQ59dXw2Gv2d1s9/J+viNcdTAwMTZcXGBCT+909b6dv62tiVx1MDAxMSZcdTAwMWUoNnuIOFx1MDAwNEZWjjiVXHUwMDE4xVaYwElsWS0vMT67LFtKi4ScODxcdTAwMWONrO6pnGPLSuhcdTAwMWPJs531W1x1MDAwMrchXGY/3fJcdTAwMTLzdeaXLC9RUqq/fc5cdTAwMGKjre7tRaF4duU3bft3J2id4sJRisScypddvKkjo7bpSuwyoW/53WLHpFx1MDAwMrFL2IOFJU7mU1x1MDAwNFx1MDAxY2RiuVx1MDAwMtrlpK6+Wa34qFEu9N3GcLuog9Y+vVxcTOqYwGRcdTAwMTF7eVx1MDAwZV/srUkhdUpQxyy4IyiyKJ2gKpksK61cdTAwMWKfXZYslfRaJ5A0XHS49v2BVfKGLmBcdTAwMWRqjEjGodglkPt5xe5cdTAwMDdCk7XY/fE6XCKsVXu941x1MDAwMJ7qZHhcXHv03Kd8clx1MDAxMsBcdTAwMWav1Ddwd8ej6vc/vv8/XHUwMDA0J/qyIn0=DUEventsAppUIsDUXSUAAAppUIsDUXSUAAXSUAA \ No newline at end of file diff --git a/guides/deployment/assets/microservices/true-microservices.excalidraw.svg b/guides/deployment/assets/microservices/true-microservices.excalidraw.svg new file mode 100644 index 0000000000..eddf3cfa40 --- /dev/null +++ b/guides/deployment/assets/microservices/true-microservices.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1da1Pi2lx1MDAxMv0+v2LK8/WY2e/Hqbp1S8C3IIpcInDrlIVcdTAwMTAgXHUwMDEwQiZcdTAwMDRcdTAwMDRPzX+/XHUwMDFkdFxikkRcdTAwMDFh1DNQNZbuXHUwMDFkwk52r+7V3YvMP1++ft3xx66589fXXHUwMDFkc1Sr2lbdq97v/Fx1MDAxOYxcdTAwMGZNr2/1XHUwMDFjmFwik7/7vYFXm1x1MDAxY9nyfbf/17dv3arXMX3XrtZMY2j1XHUwMDA3VbvvXHUwMDBm6lbPqPW63yzf7Pb/XHUwMDFi/MxVu+Z/3F637ntG+CG7Zt3ye97jZ5m22TVcdTAwMWS/XHUwMDBmZ/9cdTAwMWb8/fXrP5OfMGPVg0/MXHUwMDBiebZr7/ZcdTAwMGYqZsZuVvdV/Spdnrx1ctDPS/DMml91mrZcdTAwMTlOjWBcXGhiSEJcdTAwMDUlXG5cdTAwMTM+nVx1MDAxYcNcdTAwMTRcZlx1MDAxYVJiplx1MDAxOJXhzL1V91swi1x1MDAxMbxRSlx1MDAxYUyi4MWmh7RMq9nygzNgZkiqglMzXHUwMDFkvMJjXHUwMDFll/LXVzRcdTAwMWTp+16vY6Z7NlxcOKz3XHUwMDBmrCWukXC1d9Vap+n1XHUwMDA2Tn16jO9Vnb5b9eD2hMc1LNsu+OPJ2WFj4I7uzH3GzdMlkLnxpHfBhzZbjtlcdTAwMGa2XHUwMDAwT0d7brVm+ePJrVxir1wiWKF7XFxcdTAwMGZ3azJqn9xcdTAwMWXeXHUwMDFlO7V2o9+vU25dntvjk52n+b/DpXtgXHUwMDBlx8GuOlx1MDAwM9ueXHUwMDBlW07dXGb2aqdaVM9W5dSfVvXs8L5pXHUwMDA2p9Cwq4JyXHUwMDFjbl1otZgxNT+c6zlcdTAwMTNcdTAwMTPGXFwxxqTSNFxcQD9cdTAwMDNW6E9O21x1MDAwMEs2w01cdFx1MDAxNrE/Y6HhxVxm3Hr18S1YMkKV4lLD507nbcvpzC/d7tU64adMRn/8XHUwMDE5Z/Sa76J84VKOz6+Imde58jhzOFjY6CVWXHUwMDA2x1xiloWVfG70TFx1MDAxMIMjpSXFcFx1MDAwNKyaRSyfK0MquK+CPFx1MDAxYT6NMXy+NfS3XHUwMDE5+mG8of9cdTAwMTN+1NNcdTAwMWXTp5FcdTAwMWbz9s+ZXHUwMDA2r4Z5nP2rmU2bM3/CXHUwMDE5xpqplax/urpwnTPW6JujcPtcdTAwMWVPPTHmlLPfyd0hp1hptLPlzm4zfaBcbjvT435Ebt9GoVx1MDAxNb+aXGK0nl3MI6ooMlx1MDAxOKKUci4xUVx1MDAxYc9cdTAwMDFLvlx1MDAwNizKjCBIXGK4+2RcdTAwMGWaU2SRLbLeiKzi4iGEXHUwMDAwJdBgYlrHYEiI5Fx1MDAxMFx1MDAwMm9cdTAwMTGCY/neXHUwMDExJLTawFrh8vdcXHdmm3uOX7BcdTAwMWUmqEfPRlx1MDAwZqpdy1x1MDAwZfaFPzvDnm01g0vfqcFiTW9n9vp9XHUwMDBi2Nv0gK5Vr89GnVx1MDAxYZy0ajmmd7xI+Op5VtNyqvZV3KqrXHUwMDAzv3dp9lx1MDAxZtfte1x1MDAwM3P2fphHP4GCXHLCX0A5vVx1MDAxOarT8sEh19X2ffGo0rhcdTAwMTat0yjKTdu23P58+CTMoFx1MDAxMP5cdTAwMTBcdTAwMTdcdTAwMTBEpVxu7WByy1x1MDAxNDaAXHUwMDFjXHUwMDAyqXxcdTAwMDJ6XHUwMDA050xcdTAwMThcdTAwMTjgrVx07KmWjMfAXHUwMDFjXHUwMDBiQ3LgjZxcdTAwMTNcdTAwMDG7zpn4TWFcdTAwMGZ3uL37oG4uXHUwMDFit6mTYu7h+Pb0tDhcdTAwMTNE1udcdTAwMTfqi0ZcXJJcdTAwMTRxMYa9XCJcdTAwMWMzOVx1MDAwYowndyExnlx1MDAxZp26XHUwMDBijShcdTAwMThcbpbhXHUwMDExXHUwMDFmj3Gq87GVrjgpVMrnjtK316OrTtqLXHUwMDAyJsDgPFqoXHUwMDEx5EBaSSSR0GJcdTAwMGUtXHUwMDFhsIS4QpqBr2U4glx1MDAxNmRwSrhgmDPFXHUwMDAxLlpF4UKpwYRcdTAwMTBwds5cdTAwMDVikmzRsmG02G9HXHUwMDBi5FjBfsWiXHUwMDA10vIktIA3REBS3z89m1x1MDAwZbs9a57/hr99XHJNb/LH9Pe//4w9ejfZ3oNXxNLD80V20a72/XSv27V8uNB8sMhcYs/xq56fgm21nOb8nOnUXHUwMDEzZibv2vO83n3LrEaMXHUwMDA03jc/94JbuWvU273v+LtLXHUwMDFiXHUwMDE3uGRbOHt4UF/IrUg0uVx1MDAxM1x1MDAwNMH+IUSes22ukaEwglxchqLA0GK9XG54XHUwMDFjJFx1MDAwNaJwXHUwMDFjIaFbminfXHUwMDA0jotcdTAwMDCoKLBApERoqVu3slx1MDAxMbdy82avQmG7MFx1MDAwMfpcdTAwMTXL2CO58DRcdTAwMDRjRiRcdTAwMDQp9m/2KvH2XHUwMDFlvKKW/sndymXnvN3zLos33rjh7D20Sns0U1mY3lODS8omZVvOUJj8XHUwMDA1aFx1MDAxMZhcdTAwMWJMIWB6wP6J5uHslt4/jn5Mz7JwQS3ZtWitKISTuGJcdTAwMDDAKsm1KEhcYplcIvLdi1x1MDAwMS/AZVg6PttcdTAwMWRcdTAwMTL3uplj3pmHlPSvrYWiMFOGhOA71ySZhGBEXGZcdTAwMDKuWKvEglx1MDAxNyTKUovYOpdcdTAwMTRcdTAwMDaij0Xm31x1MDAxNFx1MDAxYWuz/ISC11JUXHUwMDFkIfB1XCI2qlx1MDAxMiFcdTAwMTJNX1CqXHT4t/c2/VxyXHUwMDA11Yj9XHUwMDA2r1x1MDAxOcv95EG0g45OrFx1MDAxM/O6NXKqRW/Uuped7OVcdTAwMTJNJmIohGS0r1xuPkNpNIvsaWtcdHjItKFcbq9cdTAwMTBtXHUwMDFiay390Zi8flx1MDAwZl9QWrz2XHLJp6BcbkcpczDHXHUwMDEya1lcdTAwMDKrgGXOkKaNt48ye9Wz7/2SgyupllVcdTAwMThkMselZv7d2kfxq4mAJqZ9XHUwMDE01MkkeFOgnJxcdTAwMDD/fFx1MDAwZVx1MDAxYU6TQEORgTVwXHUwMDEzLFx1MDAxNDB8Olx1MDAwYreNtY1+K9SUXHUwMDE3R1xyQUJcdTAwMTOGZ6tTM7BBiSRcdTAwMTEjQYFdXHUwMDEy/e4l4NBSn5ovmeuP0DF6JVx1MDAxNs13jGZcdTAwMTe9noZR2z7Z61+dmbqpi93Ty1x1MDAwM+rf+nzhYKg1RDZC59u6P2VGXFwxLZDe6ozeiPiD/bPiQao5KJdaebfV8HRcdTAwMWZcdTAwMWbxXHUwMDE1XHUwMDEw319cdTAwMWPxWDCBXGKabffP6IyETKxjY8zhbYTxlULlS5BXSs+UM99cdTAwMTTNXG6FY8ZcdTAwMWGlym1hvzFIfXfLKKu/L2z1XHUwMDE4YWlgXHUwMDFjSeR+XG6NKCGMgflBcNtcbo3ex9L9eEtfQmdcdTAwMDRcdTAwMGVccoN3QjRWaFx1MDAxN1OJXHJcdTAwMDFcdTAwMTDYP9GrtT1X44rjUaNYObx2XHUwMDBi9nlaZXKX45ubZnc5rrhGdMWvZlx1MDAwMa5cYrdVXHUwMDFikFx1MDAxZWmAXHUwMDBmXHUwMDAwSek5aMnXoLWVXHUwMDFhTUY3i63BXHUwMDEyUVx1MDAwNGlMqI5Xq6rkZijmXHUwMDE0dlx1MDAxOOj/2kssy5p5hDd+XHUwMDEwqdErXHUwMDEx7Fx1MDAxN0iNhmM3Wzh2K6wsL4vFdi83rND2gr1cYlx1MDAwMDoxNFx1MDAwZkrIXHUwMDFhuLycQzpXyNBYwP4/IT1cdTAwMDL0bTNicdxcdTAwMGZPbc/N1irswL5cdTAwMWJcdTAwMWRma5dcdTAwMDdd252JXCJrc1xmwzdXZINcdTAwMWNTSVx1MDAxYcc5dTRcdTAwMTBPvVx1MDAwNVEqcFx1MDAxNu/vLV6AS4vyvGxcdTAwMWTsy8rhQY+kUPfwwdGL9FwiXHUwMDAwK9jQXG7Iolx1MDAwMl5cdTAwMDGJuJzDijaArCNcdTAwMGV5lGJbpdEnwcr927tcdTAwMTewpVx1MDAxNDGi4moy4D1cdTAwMTPRXCJJ4Fx1MDAxN9H6azIrx9at0mj1blx1MDAwNuqmMpVsRp96jYtDWVx1MDAxY1x1MDAxNsxcdTAwMGI7poBcdTAwMTPnV1x1MDAwNJBtISBV4UzPNiV+Ko1cdTAwMDRhkOkqQfVWafRJ3Mro7W6FXHUwMDAwkCmS8ZRdRVRcdTAwMDJTt8KokFx1MDAxYVxi/7vXfTboVn4jqVFnf3DVPKyj/ez3h7a1m1x1MDAxZY28fIwwOpneY0lcdTAwMTFccr5kXHUwMDEyqzWCXHUwMDFjXHUwMDFmU1x1MDAwNsmh3GqNPolvXHUwMDE5v9m3MNgmLLmIZSwz5Zx5aTSSUlxurdYuNVojvWd7Od483H246/OBTpWH+dyRPlgsXGYzaVCIszwqKVxitEaIcC5ZYsVrqzWajG624vXw9qhcbjFcdTAwMDPiXHUwMDAz1bG6XHUwMDAzndhAXHUwMDE1lFx1MDAxMCDs+N/K1f/lUiNeuFx1MDAxN81r/y6XXHUwMDE5V29u+ubDwV3lMOpcdTAwMTWS+0zYYFxiXHUwMDE4XHUwMDA1mm8z0YlAkcFcdTAwMTF8Kzj65Vx1MDAwZeFcdTAwMGWhJWrgmFxuXHUwMDA2QU/GQf9F7T5AX1x1MDAwM89cXCnsTa95qT5SrnRauDOz5+lBgVx1MDAxY5X7ab/suOfv1keKX01cdTAwMDQ9sX0kbGhcdLeOSYViRUcvgGcrPNo0evDi6NFcdTAwMWHySoJYXHUwMDFjZVx1MDAxNCSRMlx1MDAwMs1cdTAwMTRqNeRstHv0MVRHr4SlzauOblx1MDAwNu2TTjt7PbpcdTAwMTiOs+lGeaw9cbNwXFxUjFx1MDAxYlx1MDAxMlx1MDAxMkhcdTAwMWSvOlwi0amt6ujn1i5cdTAwMDH3lLpPj91WpZFy2/S2fVa8z6Zrq8CdLFx1MDAwZXdIgiDbXHUwMDExsaIjmazIXHUwMDA3dDKyojr3JbzroESxnmA2pqVxpXVz5o3OT3Xq5KDafriI+epWkslLbHAsXHUwMDA0ilNcdTAwMWNBrFx1MDAxM5BcdTAwMWUjLbaKo/ezcvp2yVx1MDAxMWFBzYyxWMlcdTAwMTEmiep0SCAxZ4Kt1P9cXI0p7mdoqVwiMntyUGh6dqE7XHUwMDE4XHUwMDFmda6WY4prXHUwMDA0V/xqXHUwMDE2YIpKUciwNNZPgqP5h1x1MDAxYonXoLVVXHUwMDFjTUY3jC22hFRdMFx1MDAxMTi7uO90aZXcXHUwMDE1ZVx1MDAxY2NcdTAwMDHUYO3flF7WzCOc8YMojl5cdGC/QHHUuS1ao4umy4fS9kx+Rlx1MDAwNu6Zs2BLQklpXHUwMDA0iYRIXHUwMDE0XHUwMDFjcYHA+z5cdTAwMDE9gvNtR2Jx2O/dP9By5ip/I3t+umSbNyVW3JvtSKzNL/BFY25iYVZcdEZcdTAwMTgnKEHlm9zu5ECl8KrfXGL7RWHx4aGePzVcdTAwMGJnWpZcbo5dlmORyS2kOVJSXHUwMDE4sFx1MDAxNslcdTAwMTMkR8qAXHQ+OWArOfo0aFx1MDAxMWtoY0gqIHmOXHUwMDA1XHUwMDBiTuxiQLYtwVxi3lx1MDAxYinT4a3eaPWWhuvmMdN5du2YzfuU+1DWjrfQM1x1MDAxNTSBOyEhxUmSXHUwMDFiXHUwMDExoWBcdTAwMDbjrdzo87hcdTAwMTT5dpeCg0fLXG6sYkW/UXHjjOiXa7CI9T+N9Fx1MDAwM7mV30hvRKs1+6wm7Vx1MDAxY3+gZrbczfauLmKeypBA7oVcdTAwMDGMm6hcdTAwMDS5XHUwMDEx5OhUXHTBqdzKjT6Pb0l4KP5cdTAwMTK+hUPGR1x1MDAxOUTlWL5cIl94Vj6VRNLZrODjkXvnkl/U8qxTuT3Pe6P0XHUwMDEwp+l4b6FAjLBcdTAwMDFcdTAwMWUj7lx1MDAxOSZcdTAwMWOmhFx1MDAwNCbHXHUwMDEzXHUwMDBiXlvB0WR0w1x1MDAwNS/9ZttcdTAwMTecXHUwMDA0j3qLrYPxZK1cdTAwMWTE4YCCYflx6mBbxdFcdTAwMTJhNN++XHUwMDE5mde7Pv0+bqbvZH2v3ZnN+15rM1x0XHUwMDBlmb1cdTAwMTAsVnBcdTAwMDT/5v9Dma3g6Fx1MDAxNzmEvXiHXHUwMDEwV1x1MDAwMVx1MDAwN1x1MDAxMkRcdTAwMTlcdTAwMDEvXHUwMDFmg3zJXHUwMDEzK1pBo1x1MDAwM7LU1Vxu4NMrXqqJlC+eXHUwMDE0L8+PjqoqdVnc37uosvGg9G5NpPjVRKBcdTAwMTPTRFxuqmVSgcNNUlx1MDAxYr2AnK3aaNPQSS2h1ePBc/20jH08mMSJhJFAqqZcdTAwMDTWXHUwMDFmJ1x1MDAxN/1YgqNXotJaXHUwMDA1R1+erGOn6rpcdTAwMDVcdTAwMWbu55Q37VxmLfM+lVxmhy9PniGAgzmhWz++/Pg/Of7y+yJ9AppDUAppDUAppDU \ No newline at end of file diff --git a/guides/deployment/cicd.md b/guides/deployment/cicd.md index e51fc5d6f8..58a79842af 100644 --- a/guides/deployment/cicd.md +++ b/guides/deployment/cicd.md @@ -20,28 +20,23 @@ status: released -## SAP CI/CD Service +## SAP Continuous Integration and Delivery -SAP Continuous Integration and Delivery is a service on SAP BTP, which lets you configure and run predefined continuous integration and delivery pipelines. It connects with your Git SCM repository and in its user interface, you can easily monitor the status of your builds and detect errors as soon as possible, which helps you prevent integration problems before completing your development. +[SAP Continuous Integration and Delivery](https://help.sap.com/viewer/SAP-Cloud-Platform-Continuous-Integration-and-Delivery) lets you configure and run predefined continuous integration and delivery pipelines. It connects with your Git SCM repository and in its user interface, you can easily monitor the status of your builds and detect errors as soon as possible, which helps you prevent integration problems before completing your development. SAP Continuous Integration and Delivery has a ready-to-use pipeline for CAP, that is applicable to Node.js, Java and multitarget application (MTA) based projects. It does not require you to host your own Jenkins instance and it provides an easy, UI-guided way to configure your pipelines. Try the tutorial [Get Started with SAP Continuous Integration and Delivery](https://developers.sap.com/tutorials/cicd-start-cap.html) to configure a CI/CD pipeline that builds, tests, and deploys your code changes. -[Learn more about SAP Continuous Integration and Delivery.](https://help.sap.com/viewer/SAP-Cloud-Platform-Continuous-Integration-and-Delivery){.learn-more} - - ## CI/CD Pipelines with SAP Piper -You can set up continuous delivery in your software development project, applicable to both SAP Business Technology Platform (BTP) and SAP on-premise platforms. SAP implements tooling for continuous delivery in project [Piper](https://www.project-piper.io/). +For more flexibility you can set up continuous delivery in your software development project, applicable to both SAP Business Technology Platform (BTP) and SAP on-premise platforms. SAP implements tooling for continuous delivery in project [Piper](https://www.project-piper.io/). Try the tutorial [Create Automated System Tests for SAP Cloud Application Programming Model Projects](https://developers.sap.com/tutorials/cicd-wdi5-cap.html) to create system tests against a CAP-based sample application and automate your tests through a CI/CD pipeline. - -[Learn more about project Piper](https://www.project-piper.io/){.learn-more} +[See a comparison with SAP Continuous Integration and Delivery Service.](https://www.project-piper.io/){.learn-more} ## GitHub Actions GitHub offers continuous integration workflows using [GitHub Actions](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration). In our [SFlight sample,](https://github.com/SAP-samples/cap-sflight) we use GitHub Actions in two simple workflows to test our samples on [current Node.js and Java versions](https://github.com/SAP-samples/cap-sflight/tree/main/.github/workflows). -We also defined our [own actions](https://github.com/SAP-samples/cap-sflight/tree/main/.github/actions) and use them in a [custom workflow](https://github.com/SAP-samples/cap-sflight/blob/main/.github/workflows/deploy-btp.yml). diff --git a/guides/deployment/custom-builds.md b/guides/deployment/custom-builds.md index 19b519fe0a..0fb76a9dcc 100644 --- a/guides/deployment/custom-builds.md +++ b/guides/deployment/custom-builds.md @@ -19,20 +19,40 @@ status: released The CDS model folders and files used by `cds build` are determined as follows: -- Known root folders, by [default](../../get-started/#project-structure) the folders _db/, srv/, app/_, can also be configured by [_folders.db, folders.srv, folders.app_](../../get-started/#project-structure). +- Known root folders, by [default](../../get-started/#project-structure) the folders _db/, srv/, app/_, can also be configured by [`folders.db`, `folders.srv`, `folders.app`](../../get-started/#project-structure). - The _src_ folder configured for the individual build task. -- If [Feature toggles](../extensibility/feature-toggles#enable-feature-toggles) are enabled: subfolders of the folder _fts_. -- CDS Model folders and files defined by the [required services](../../node.js/cds-env#services) of your project. This also includes models used by required built-in CDS services, like [persistent outbox](../../node.js/outbox#persistent-outbox) or [MTX related services](../multitenancy/mtxs#mtx-services-reference). +- If [feature toggles](../extensibility/feature-toggles#enable-feature-toggles) are enabled: subfolders of the folder _fts_. +- CDS Model folders and files defined by the [required services](../../node.js/cds-env#services) of your project. This also includes models used by required built-in CDS services, like [persistent queue](../../node.js/queue#persistent-queue) or [MTX related services](../multitenancy/mtxs#mtx-services-reference). Feature toggle folders and required built-in service models will also be added if user defined models have already been configured as [_model_ option](#build-task-properties) in your build tasks. [Learn more about the calculation of the concrete list of CDS models.](../../node.js/cds-compile#cds-resolve){.learn-more} ::: tip If custom build tasks are configured, those properties have precedence -For example, you want to configure the _src_ folder and add the default models. To achieve this, do not define the _model_ option in your build task. That way, the model paths will still be dynamically determined, but the _src_ folder is taken from the build task configuration. So you benefit from the automatic determination of models, for example, when adding a new external services, or when CAP is changing any built-in service configuration values. +For example, you want to configure the _src_ folder and add the default models. To achieve this, do not define the _model_ option in your build task: + +::: code-group + +```jsonc [package.json] +{ + "build": { + "target": "gen", + "tasks": [ + { + "for": "nodejs", + "src": "srv", + "options": { /* no "model" entry here */ } + } + ] + } +} +``` + + + That way, the model paths will still be dynamically determined, but the _src_ folder is taken from the build task configuration. So you benefit from the automatic determination of models, for example, when adding a new external services, or when CAP is changing any built-in service configuration values. ::: -To control which tasks `cds build` executes, you can add them as part of your [project configuration](../../node.js/cds-env#project-settings) in _package.json_ or _.cdsrc.json_. +To control which tasks `cds build` executes, you can add them as part of your [project configuration](../../node.js/cds-env#project-settings) in _package.json_ or _.cdsrc.json_, as outlined [in the following chapter](#build-task-properties). ## Properties @@ -40,7 +60,9 @@ To control which tasks `cds build` executes, you can add them as part of your [p The following build tasks represent the default configuration dynamically determined by `cds build` for a minimal Node.js project when executing `cds build --production` or `cds build --profile production` : -```json +::: code-group + +```json [package.json] { "build": { "target": "gen", @@ -52,6 +74,8 @@ The following build tasks represent the default configuration dynamically determ } ``` +::: + ::: tip The executed build tasks are logged to the command line. You can use them as a blue print – copy & paste them into your CDS configuration and adapt them to your needs. See also the command line help for further details using `cds build --help`. ::: @@ -65,11 +89,14 @@ The `for` property defines the executed build task type. Currently supported typ In this scenario the required services are implemented by the Node.js application itself which is the default for Node.js. - `mtx-sidecar`: Creates a deployment layout for Java or Node.js projects using multitenancy, feature toggles, extensibility or a combination of these _with_ sidecar architecture.
Java projects have to use a sidecar architecture. For Node.js this is optional, but allows for better scalability in multitenant scenarios. + [Learn more about **Multitenant Saas Application Deployment**](./to-cf){.learn-more} - `mtx-extension`: Creates a deployment layout (_extension.tgz_ file) for an MTX extension project, which is required for extension activation using `cds push`. Extension point restrictions defined by the SaaS app provider are validated by default. If any restriction is violated the build aborts and the errors are logged.
- The build task is created by default for projects that have `"cds": { "extends": "\" }` configured in their _package.json_.
+ The build task is created by default for projects that have `"cds": { "extends": "\" }` configured in their _package.json_. + [Learn more about **Extending and Customizing SaaS Solutions**](../extensibility/customization){.learn-more} -- Additional types may be supported by build plugin contributions.
+- Additional types may be supported by build plugin contributions. + [Learn more about **Running Build Plugins**](#run-the-plugin){.learn-more} Build tasks can be customized using the following properties: @@ -78,6 +105,7 @@ Build tasks can be customized using the following properties: - `dest`: Optional destination of the modules builds, relative to the enclosing project. The _src_ folder is used by default. - `options`: Sets the options according to the target technology.
- `model`: It has type _string_ or _array of string_. The given list of folders or individual _.cds_ file names is resolved based on the current working dir or the project folder passed to cds build. CDS built-in models (prefix _@sap/cds*_) are added by default to the user-defined list of models. + [Learn more about **Core Data Services (CDS)**](../../cds/){.learn-more} **Note:** @@ -85,18 +113,14 @@ Alternatively you can execute build tasks and pass the described arguments from ### Build Target Folder {#build-target-folder} -If you want to change the default target folder, use the `target` property in _.cdsrc.json_ or _package.json_. It is resolved based on the root folder of your project. +If you want to change the default target folder, use the cds.build.target=path/to/my/folder property. It is resolved based on the root folder of your project. + -```json -{ - "build": { "target" : "myfolder" } -} -``` #### Node.js Node.js projects use the folder _./gen_ below the project root as build target folder by default.
-Relevant source files from _db_ or _srv_ folders are copied into this folder, which makes it a self-contained folder that is ready for deployment. The default folder names can be changed with the _folders.db_, _folders.srv_, _folders.app_ configuration. Or you can go for individual build task configuration for full flexibility. +Relevant source files from _db_ or _srv_ folders are copied into this folder, which makes it self-contained and ready for deployment. The default folder names can be changed with the cds.folders.db, cds.folders.srv, cds.folders.app configuration. Or you can go for individual build task configuration for full flexibility. Project files like _.cdsrc.json_ or _.npmrc_ located in the _root_ folder or in the _srv_ folder of your project are copied into the application's deployment folder (default _gen/srv_). Files located in the _srv_ folder have precedence over the corresponding files located in the project root directory. As a consequence these files are used when deployed to production. Make sure that the folders do not contain one of these files by mistake. Consider using profiles `development` or `production` in order to distinguish environments. CDS configuration that should be kept locally can be defined in a file _.cdsrc-private.json_. @@ -108,9 +132,11 @@ Relevant source files from _db_ or _srv_ folders are copied into this folder, wh [Learn more about `cds env get`](../../node.js/cds-env#cli){.learn-more} **Note:** -`cds build` provides options you can use to switch on or off the copy behavior on build task level: +`cds build` provides `options` you can use to switch the copy behavior of specific files on or off on build task level: -```json +::: code-group + +```json [package.json] { "build": { "tasks": [ @@ -121,13 +147,26 @@ Relevant source files from _db_ or _srv_ folders are copied into this folder, wh } ``` +::: + #### npm Workspace Support {#build-ws} Use CLI option `--ws-pack` to enable tarball based deployment of [npm workspace](https://docs.npmjs.com/cli/using-npm/workspaces) dependencies. Workspaces are typically used to manage multiple local packages within a singular top-level root package. Such a setup is often referred to as a [monorepo](https://earthly.dev/blog/npm-workspaces-monorepo/). As an effect, your workspace dependencies can be deployed to SAP BTP without them being published to an npm registry before. -Behind the scenes, `cds build --ws-pack` creates a tarball in folder _gen/srv_ for each workspace dependency of your project that has a `*` version identifier. Dependencies in _gen/package.json_ will be adapted to point to the correct tarball file URL. +Behind the scenes, `cds build --ws-pack` creates a tarball in folder _gen/srv_ for each workspace dependency of your project that has a `*` version identifier. Dependencies in _gen/package.json_ will be adapted to point to the correct tarball file URL: + +::: code-group +```jsonc [package.json] +{ + "dependencies": { + "some-package": "^1", // regular package + "some-workspace": "*" // workspace dependency, marked as such via "*" + } +} +``` +::: Packaging of the tarball content is based on the rules of the [`npm pack`](https://docs.npmjs.com/cli/commands/npm-pack) command: @@ -141,7 +180,7 @@ This causes `cds build` to create the build output below the individual source f ## Implement a Build Plugin {#custom-build-plugins} -CDS already offers build plugins to create deployment layouts for the most use cases. However, you find cases where these plugins are not enough and you have to develop your own. This section shows how such a build plugin can be implemented and how it can be used in projects. +CDS already offers build plugins to create deployment layouts for the most use cases. However, you will find cases where these plugins are not enough and you have to develop your own. This section shows how such a build plugin can be implemented and how it can be used in projects. Build plugins are run by `cds build` to generate the required deployment artifacts. Build tasks hold the actual project specific configuration. The task's `for` property value has to match the build plugin ID. diff --git a/guides/deployment/microservices.md b/guides/deployment/microservices.md new file mode 100644 index 0000000000..12e87c5c80 --- /dev/null +++ b/guides/deployment/microservices.md @@ -0,0 +1,1109 @@ +--- +synopsis: > + A guide on deploying SAP Cloud Application Programming Model (CAP) applications as microservices to the SAP BTP Cloud Foundry environment. +status: released +--- + +# Microservices with CAP + +A comprehensive guide on deploying your CAP application as microservices. + +[[toc]] + + +## Create a Solution Monorepo + +Assumed we want to create a composite application consisting of two or more micro services, each living in a separate GitHub repository, for example: + +- https://github.com/capire/bookstore +- https://github.com/capire/reviews +- https://github.com/capire/orders + +With some additional repositories, used as dependencies in the same manner, like: + +- https://github.com/capire/common +- https://github.com/capire/bookshop +- https://github.com/capire/data-viewer + +This guide describes a way to manage development and deployment via *[monorepos](https://en.wikipedia.org/wiki/Monorepo)* using *[NPM workspaces](https://docs.npmjs.com/cli/using-npm/workspaces)* and *[git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules)* techniques. + +1. Create a new monorepo root directory using *NPM workspaces*: + + ```sh + mkdir capire + cd capire + echo "{\"name\":\"@capire/samples\",\"workspaces\":[\"*\"]}" > package.json + ``` + +2. Add the previously mentioned projects as `git` submodules: + + ```sh + git init + git submodule add https://github.com/capire/bookstore + git submodule add https://github.com/capire/reviews + git submodule add https://github.com/capire/orders + git submodule add https://github.com/capire/common + git submodule add https://github.com/capire/bookshop + git submodule add https://github.com/capire/data-viewer + git submodule update --init + ``` + + Add a _.gitignore_ file with the following content: + ```txt + node_modules + gen + ``` + > The outcome of this looks and behaves exactly as the monorepo layout in *[cap/samples](https://github.com/sap-samples/cloud-cap-samples)*, so we can exercise the subsequent steps in there... + +3. Test-drive locally: + ```sh + npm install + ``` + + ```sh + cds w bookshop + ``` + + ```sh + cds w bookstore + ``` + + Each microservice can be started independently. If you start each microservice, one after the other in a different terminal, the connection is already established. + + [Learn more about Automatic Bindings by `cds watch`](../extensibility/composition#bindings-via-cds-watch){.learn-more} + + +::: details The project structure + +The project structure used here is as follows: + +```txt +/ +├─ bookstore/ +├─ orders/ +├─ reviews/ +├─ ... +└─ package.json +``` + +The individual services (`bookstore`, `reviews`, `orders`) can be one of the following: + * folders, committed directly to the root project + * git submodules + +Links between the projects are established using NPM dependencies. +Since the root project defines workspaces, these dependencies are also found locally without the need for publishing or linking. +When one of the projects is cloned in isolation, it's still possible to fetch dependencies to other modules via the NPM registry. + +::: + +## Using a Shared Database + +You can deploy your model to a single database and then share it across applications, if you have one of the following scenarios: + +- multiple CAP applications relying on the same domain model +- a monolithic CAP application that you want to split up **on the service level only, while still sharing the underlying database layer** + +In the following steps, we create an additional project to easily collect the relevant models from these projects, and act as a vehicle to deploy these models to SAP HANA in a controlled way. + +::: details Why a shared database? + +A shared database is beneficial if the following are important for you: + + - **Query Performance:** Complex queries are executed much faster, for example `$expand` to an entity on another microservice, compared to calls across services with own data persistencies. + - **Independent Scalability** of application runtimes, compared to a monolithic application. + +These are the (not so beneficial) side effects you when using a shared persistence: + + - Accessing data directly (without an API) means any changes in the data model affect all applications directly. + - Every change in one of the services requires either one of the following: + - Redeployment of all microservices involved. + - Logic to decide which microservices need redeployment to avoid inconsistencies. + - Violates the 12 factors concept. + +::: + +### Add a Project For Shared Database + +1. Add another `cds` project to collect the models from these projects: + + ```sh + cds init shared-db --add hana + ``` + + ```sh + npm add --workspace shared-db @capire/bookstore + npm add --workspace shared-db @capire/reviews + npm add --workspace shared-db @capire/orders + ``` + + > Note how *NPM workspaces* allows us to use the package names of the projects, and nicely creates symlinks in *node_modules* accordingly. + +2. Add a `db/schema.cds` file as a mashup to actually collect the models: + + ::: code-group + ```cds [shared-db/db/schema.cds] + using from '@capire/bookstore'; + using from '@capire/reviews'; + using from '@capire/orders'; + ``` + ::: + + > Note: the `using` directives refer to `index.cds` files existing in the target packages. Your projects may have different entry points. + +::: details Try it out + +With that we're basically done with the setup of the collector project. In sum, it's just another CAP project with some cds models in it, which we can handle as usual. We can test whether it all works as expected, for example, we can test-compile and test-deploy it to sqlite and hana, build it, and deploy it to the cloud as usual: + +```sh +cds db -2 sql +``` +```sh +cds db -2 hana +``` + +```sh +cds deploy -2 sqlite +``` +```sh +cds build --for hana +``` + +> Note: As we can see in the output for `cds deploy` and `cds build`, it also correctly collects and adds all initial data from enclosed `.csv` files. +::: + +::: details Other project structures + +The project structure used here is as follows: + +```txt +/ +├─ bookstore/ +├─ reviews/ +├─ orders/ +└─ shared-db/ + └─ db/ + └─ schema.cds # references schemas of bookstore, reviews, orders + └─ package.json # npm dependencies to bookstore, reviews, orders +├─ ... +└─ package.json +``` + +The `shared-db` module is simply another CAP project, with only database content. The dependencies are installed via NPM, so it's still possible to install via an NPM registry if used outside of the monorepo setup. + +The database model could also be collected on root level instead of creating a separate `shared-db` module. When collecting on root level, the `cds build --ws` option can be used to collect the models of all NPM workspaces. + +::: + +## All-in-one Deployment + +This section is about how to deploy all 3+1 projects at once with a common _mta.yaml_. + +![component diagram with synchronous and event communication for orders](./assets/microservices/bookstore.excalidraw.svg) + +[cap-samples](https://github.com/SAP-samples/cloud-cap-samples?tab=readme-ov-file#welcome-to-capsamples) already has an all-in-one deployment implemented. Similar steps are necessary to convert projects with multiple CAP applications into a shared database deployment. + +### Deployment Descriptor + +Add initial multitarget application configuration for deployment to Cloud Foundry: + +```shell +cds add mta +``` + +[Learn more about **how to deploy to Cloud Foundry**.](../deployment/to-cf){.learn-more} + +### Database + +Add initial database configuration using the command: + +```shell +cds add hana +``` + +Delete the generated _db_ folder as we don't need it on the root level: + +```shell +rm -r db +``` + +Update the `db-deployer` path to use our `shared-db` project [created previously](#using-a-shared-database): + +::: code-group +```yaml [mta.yaml] + - name: samples-db-deployer + path: gen/db # [!code --] + path: shared-db/gen/db # [!code ++] +``` +::: + +Add build command for generation of the database artifacts: + +::: code-group +```yaml [mta.yaml] +build-parameters: + before-all: + - builder: custom + commands: + - npm ci + - npx cds build --production # [!code --] + - npx cds build ./shared-db --for hana --production # [!code ++] +``` +::: + + +::: info `cds build --ws` +If the CDS models of every NPM workspace contained in the monorepo should be considered, then instead of creating this `shared-db` folder, you can also use: +```shell +cds build --for hana --production --ws +``` +The `--ws` aggregates all models in the NPM workspaces. + +In this walkthrough, we only include a subset of the CDS models in the deployment. +::: + + +::: details Configure each app for cloud readiness +The preceding steps only added configuration to the workspace root. + +Additionally add database configuration to each module that we want to deploy - bookstore, orders, and reviews: + +```shell +npm i @cap-js/hana --workspace bookstore +npm i @cap-js/hana --workspace orders +npm i @cap-js/hana --workspace reviews +``` +::: + + +### Applications + +Replace the MTA module for `samples-srv` with versions for each CAP service and adjust `name`, `path`, and `provides[0].name` to match the module name. Also change the `npm-ci` builder to the `npm` builder. + +::: code-group +```yaml [mta.yaml] +modules: + - name: bookstore-srv # [!code focus] + type: nodejs + path: bookstore/gen/srv # [!code focus] + parameters: + instances: 1 + buildpack: nodejs_buildpack + build-parameters: + builder: npm # [!code focus] + provides: # [!code focus] + - name: bookstore-api # [!code focus] + properties: + srv-url: ${default-url} + requires: + - name: samples-db + - name: samples-auth + - name: samples-messaging + - name: samples-destination + + - name: orders-srv # [!code focus] + type: nodejs + path: orders/gen/srv # [!code focus] + parameters: + instances: 1 + buildpack: nodejs_buildpack + build-parameters: + builder: npm # [!code focus] + provides: # [!code focus] + - name: orders-api # [!code focus] + properties: + srv-url: ${default-url} + requires: + - name: samples-db + - name: samples-auth + - name: samples-messaging + - name: samples-destination + + - name: reviews-srv # [!code focus] + type: nodejs + path: reviews/gen/srv # [!code focus] + parameters: + instances: 1 + buildpack: nodejs_buildpack + build-parameters: + builder: npm # [!code focus] + provides: # [!code focus] + - name: reviews-api # [!code focus] + properties: + srv-url: ${default-url} + requires: + - name: samples-db + - name: samples-auth + - name: samples-messaging + - name: samples-destination +... +``` +::: + +Add build commands for each module to be deployed: + +::: code-group +```yaml [mta.yaml] +build-parameters: + before-all: + - builder: custom + commands: + - npm ci + - npx cds build ./shared-db --for hana --production + - npx cds build ./orders --for nodejs --production --ws-pack # [!code ++] + - npx cds build ./reviews --for nodejs --production # [!code ++] + - npx cds build ./bookstore --for nodejs --production --ws-pack # [!code ++] +``` +::: + +::: info --ws-pack +Note that we use the *--ws-pack* option for some modules. It's important for node modules referencing other repository-local node modules. +::: + + +### Authentication + +Add [security configuration](../security/authorization#xsuaa-configuration) using the command: + +```shell +cds add xsuaa --for production +``` + +Add the admin role + +::: code-group +```json [xs-security.json] +{ + "scopes": [ + { // [!code ++] + "name": "$XSAPPNAME.admin", // [!code ++] + "description": "admin" // [!code ++] + } // [!code ++] + ], + "role-templates": [ + { // [!code ++] + "name": "admin", // [!code ++] + "scope-references": [ // [!code ++] + "$XSAPPNAME.admin" // [!code ++] + ], // [!code ++] + "description": "cap samples multi-service shared-db" // [!code ++] + } // [!code ++] + ] +} +``` +::: + +::: details Configure each app for cloud readiness +Add NPM dependency `@sap/xssec`: + +```shell +npm i @sap/xssec --workspace bookstore +npm i @sap/xssec --workspace orders +npm i @sap/xssec --workspace reviews +``` +::: + +### Messaging + +The messaging service is used to organize asynchronous communication between the CAP services. + +```shell +cds add enterprise-messaging +``` + +Relax the publish filters for the message topics + +::: code-group +```json [event-mesh.json] +{ + ... + "rules": { + "topicRules": { + "publishFilter": [ + "${namespace}/*" // [!code --] + "*" // [!code ++] + ], + "subscribeFilter": [ + "*" + ] + }, + "queueRules": { + "publishFilter": [ + "${namespace}/*" + ], + "subscribeFilter": [ + "${namespace}/*" + ] + } + } +} +``` +::: + +Parameterize the properties `emname` and `namespace`: + +::: code-group +```json [event-mesh.json] + { + "emname": "samples-emname", // [!code --] + "version": "1.1.0", + "namespace": "default/samples/1", // [!code --] + ... + } +``` +::: +::: code-group +```yaml [mta.yaml] +resources: + - name: samples-messaging + type: org.cloudfoundry.managed-service + parameters: + service: enterprise-messaging + service-plan: default + path: ./event-mesh.json + config: # [!code ++] + emname: bookstore-${org}-${space} # [!code ++] + namespace: cap/samples/${space} # [!code ++] +``` +::: + +Add the *processed-after* property, so that the xsuaa instance is created after the messaging: + +::: code-group +```yaml [mta.yaml] +resources: + ... + - name: samples-auth + processed-after: #[!code ++] + - samples-messaging #[!code ++] +``` +::: + +::: details Configure each app for cloud readiness +Enable messaging for the modules that use it: + +::: code-group +```json [bookstore/package.json] +{ + "cds": { + "requires": { + "messaging": true // [!code ++] + } + } +} +``` +```json [orders/package.json] +{ + "cds": { + "requires": { + "messaging": true // [!code ++] + } + } +} +``` + +::: + + +### Destinations + +Add [destination configuration](https://cap.cloud.sap/docs/guides/using-services#using-destinations) for connectivity between the apps: + +```shell +cds add destination +``` + +Add destinations that point to the API endpoints of the orders and reviews applications: + +::: code-group +```yaml [mta.yaml] +modules: +... + - name: destination-content + type: com.sap.application.content + requires: + - name: orders-api + - name: reviews-api + - name: bookstore-api + - name: samples-auth + parameters: + service-key: + name: xsuaa_service-key + - name: samples-destination + parameters: + content-target: true + build-parameters: + no-source: true + parameters: + content: + instance: + existing_destinations_policy: update + destinations: + - Name: orders-dest + URL: ~{orders-api/srv-url} + Authentication: OAuth2ClientCredentials + TokenServiceInstanceName: samples-auth + TokenServiceKeyName: xsuaa_service-key + - Name: reviews-dest + URL: ~{reviews-api/srv-url} + Authentication: OAuth2ClientCredentials + TokenServiceInstanceName: samples-auth + TokenServiceKeyName: xsuaa_service-key +... +``` +::: + +Use the destinations in the bookstore application: + +::: code-group +```yaml [mta.yaml] +modules: + - name: bookstore-srv + ... + properties: # [!code ++] + cds_requires_ReviewsService_credentials: {"destination": "reviews-dest","path": "/reviews"} # [!code ++] + cds_requires_OrdersService_credentials: {"destination": "orders-dest","path": "/odata/v4/orders"} # [!code ++] +``` +::: + +::: details Configure each app for cloud readiness + +Add `@sap-cloud-sdk/http-client` and `@sap-cloud-sdk/resilience` for each module utilizing the destinations: + +```shell +npm i @sap-cloud-sdk/http-client --workspace bookstore +npm i @sap-cloud-sdk/resilience --workspace bookstore +``` +::: + +### Approuter + +Add [approuter configuration](../deployment/to-cf#add-app-router) using the command: + +```shell +cds add approuter +``` + +The approuter serves the UIs and acts as a proxy for requests toward the different apps. + +Since the approuter folder is only necessary for deployment, we move it into a `.deploy` folder. + +```shell +mkdir .deploy +mv app/router .deploy/app-router +``` + +::: code-group +```yaml +modules: + ... + - name: samples + type: approuter.nodejs + path: app/router # [!code --] + path: .deploy/app-router # [!code ++] + ... +``` +::: + +#### Static Content + +The approuter can serve static content. Since our UIs are located in different NPM workspaces, we create symbolic links to them as an easy way to deploy them as part of the approuter. + +```shell +mkdir .deploy/app-router/resources +cd .deploy/app-router/resources +ln -s ../../../bookshop/app/vue bookshop +ln -s ../../../orders/app/orders orders +ln -s ../../../reviews/app/vue reviews +cd ../../.. +``` + +::: warning Simplified Setup +This is a simplified setup which deploys the static content as part of the approuter. +See [Deploy to Cloud Foundry](./to-cf#add-ui) for a productive UI setup. +::: + +#### Configuration + +Add destinations for each app url: + +::: code-group +```yaml [mta.yaml] +modules: + ... + - name: samples + type: approuter.nodejs + .... + requires: + - name: service-api # [!code --] + group: destinations # [!code --] + properties: # [!code --] + name: service-api # [!code --] + url: ~{srv-url} # [!code --] + forwardAuthToken: true # [!code --] + - name: orders-api # [!code ++] + group: destinations # [!code ++] + properties: # [!code ++] + name: orders-api # [!code ++] + url: ~{srv-url} # [!code ++] + forwardAuthToken: true # [!code ++] + - name: reviews-api # [!code ++] + group: destinations # [!code ++] + properties: # [!code ++] + name: reviews-api # [!code ++] + url: ~{srv-url} # [!code ++] + forwardAuthToken: true # [!code ++] + - name: bookstore-api # [!code ++] + group: destinations # [!code ++] + properties: # [!code ++] + name: bookstore-api # [!code ++] + url: ~{srv-url} # [!code ++] + forwardAuthToken: true # [!code ++] +``` +::: + +The _xs-app.json_ file describes how to forward incoming request to the API endpoint / OData services and is located in the _.deploy/app-router_ folder. Each exposed CAP Service endpoint needs to be directed to the corresponding application which is providing this CAP service. + +::: code-group +```json [.deploy/app-router/xs-app.json] +{ + "routes": [ + { // [!code --] + "source": "^/(.*)$", // [!code --] + "target": "$1", // [!code --] + "destination": "srv-api", // [!code --] + "csrfProtection": true // [!code --] + } // [!code --] + { // [!code ++] + "source": "^/admin/(.*)$", // [!code ++] + "target": "/admin/$1", // [!code ++] + "destination": "bookstore-api", // [!code ++] + "csrfProtection": true // [!code ++] + }, // [!code ++] + { // [!code ++] + "source": "^/browse/(.*)$", // [!code ++] + "target": "/browse/$1", // [!code ++] + "destination": "bookstore-api", // [!code ++] + "csrfProtection": true // [!code ++] + }, // [!code ++] + { // [!code ++] + "source": "^/user/(.*)$", // [!code ++] + "target": "/user/$1", // [!code ++] + "destination": "bookstore-api", // [!code ++] + "csrfProtection": true // [!code ++] + }, // [!code ++] + { // [!code ++] + "source": "^/odata/v4/orders/(.*)$", // [!code ++] + "target": "/odata/v4/orders/$1", // [!code ++] + "destination": "orders-api", // [!code ++] + "csrfProtection": true // [!code ++] + }, // [!code ++] + { // [!code ++] + "source": "^/reviews/(.*)$", // [!code ++] + "target": "/reviews/$1", // [!code ++] + "destination": "reviews-api", // [!code ++] + "csrfProtection": true // [!code ++] + } // [!code ++] + ] +} +``` +::: + +Add routes for static content: + +::: code-group +```json [.deploy/app-router/xs-app.json] +{ + "routes": [ + ... + { // [!code ++] + "source": "^/app/(.*)$", // [!code ++] + "target": "$1", // [!code ++] + "localDir": "resources", // [!code ++] + "cacheControl": "no-cache, no-store, must-revalidate" // [!code ++] + } // [!code ++] + ] +} +``` +::: + +The `/app/*` route exposes our UIs, so bookstore is available as `/app/bookstore`, orders as `/app/orders` and reviews as `/app/reviews`. +Due to the `/app` prefix, make sure that static resources are accessed via relative paths inside the UIs. + +Add the `bookshop/index.html` as initial page when visiting the app: + +::: code-group +```json [.deploy/app-router/xs-app.json] +{ + "welcomeFile": "app/bookshop/index.html", // [!code ++] + "routes": { + ... + } +} +``` +::: + +Additionally, the welcomeFile is important for deployed Vue UIs as they obtain CSRF-Tokens via this url. + + +### Deploy + +To build, deploy, and undeploy easily, add these `npm` scripts: + +::: code-group +```json [package.json] + "scripts": { + "build": "mbt build -t gen --mtar mta.tar", // [!code ++] + "deploy": "cf deploy gen/mta.tar", // [!code ++] + "undeploy": "cf undeploy capire.samples --delete-services --delete-service-keys" // [!code ++] + } +``` +::: + +Before deploying you need to log in to Cloud Foundry. + +Build the apps locally: + +```shell +npm run build +``` + +Deploy the built artifacts to Cloud Foundry: + +```shell +npm run deploy +``` + +Once the app is deployed, you can get the url of the approuter via + +```shell +cf apps # [!code focus] + +name requested state processes routes +bookstore-srv started web:1/1 my-capire-bookstore-srv.cfapps.us10-001.hana.ondemand.com +orders-srv started web:1/1 my-capire-orders-srv.cfapps.us10-001.hana.ondemand.com +reviews-srv started web:1/1 my-capire-reviews-srv.cfapps.us10-001.hana.ondemand.com +samples started web:1/1 my-capire-samples.cfapps.us10-001.hana.ondemand.com # [!code focus] +samples-db-deployer stopped web:0/1 +``` + +You can then navigate to this url and the corresponding apps +```text +/ -> bookshop +/app/bookshop -> bookshop +/app/orders -> orders +/app/reviews -> reviews +``` + + +## Deployment as Separate MTA + +This is an alternative to the all-in-one deployment. Assume the applications each already have their own _mta.yaml_. For example by running `cds add mta` in the _reviews_, _orders_ and _bookstore_ folder. + +### Database + +We can add the [previously created](#using-a-shared-database) `shared-db` project as its own MTA deployment: + +::: code-group +```sh [shared-db/] +cds add mta +``` +::: + +This adds everything necessary for a full CAP application. +Since we only want the database and database deployment, remove everything else like the srv module and destination and messaging resources: + +::: details Diff +```yaml +_schema-version: 3.3.0 +ID: shared-db +version: 1.0.0 +description: "A simple CAP project." +parameters: + enable-parallel-deployments: true +build-parameters: + before-all: + - builder: custom + commands: + - npm ci + - npx cds build --production # [!code --] + - npx cds build --production --for hana # [!code ++] +modules: + - name: shared-db-srv # [!code --] + type: nodejs # [!code --] + path: gen/srv # [!code --] + parameters: # [!code --] + instances: 1 # [!code --] + buildpack: nodejs_buildpack # [!code --] + build-parameters: # [!code --] + builder: npm-ci # [!code --] + provides: # [!code --] + - name: srv-api # [!code --] + properties: # [!code --] + srv-url: ${default-url} # [!code --] + requires: # [!code --] + - name: shared-db-destination # [!code --] + - name: shared-db-messaging # [!code --] + - name: shared-db-db # [!code --] + + - name: shared-db-db-deployer + type: hdb + path: gen/db + parameters: + buildpack: nodejs_buildpack + requires: + - name: shared-db-db + +resources: + - name: shared-db-destination # [!code --] + type: org.cloudfoundry.managed-service # [!code --] + parameters: # [!code --] + service: destination # [!code --] + service-plan: lite # [!code --] + - name: shared-db-messaging # [!code --] + type: org.cloudfoundry.managed-service # [!code --] + parameters: # [!code --] + service: enterprise-messaging # [!code --] + service-plan: default # [!code --] + path: ./event-mesh.json # [!code --] + - name: shared-db-db + type: com.sap.xs.hdi-container + parameters: + service: hana + service-plan: hdi-shared +``` +::: + + + + +#### Binding to shared database + +The only thing left to care about is to ensure all 3+1 projects are bound and connected to the same database at deployment, subscription, and runtime. + +Configure the _mta.yaml_ of the other apps to bind to the existing shared database, for example, in the reviews module: + +```yaml [reviews/mta.yaml] +... +modules: + ... + + - name: reviews-db-deployer # [!code --] + type: hdb # [!code --] + path: gen/db # [!code --] + parameters: # [!code --] + buildpack: nodejs_buildpack # [!code --] + requires: # [!code --] + - name: reviews-db # [!code --] + +resources: + ... + - name: reviews-db + type: com.sap.xs.hdi-container # [!code --] + type: org.cloudfoundry.existing-service # [!code ++] + parameters: + service: hana # [!code --] + service-plan: hdi-shared # [!code --] + service-name: shared-db-db # [!code ++] +``` + + + +#### Subsequent updates + +Whenever one of the projects has changes affecting the database, the database artifacts need to be deployed prior to the application deployment. With a single _mta.yaml_, this is handled in the scope of the MTA deployment. When using multiple deployment units, ensure to first deploy the `shared-db` project before deploying the others. + +## Late-Cut Microservices + +Microservices have been attributed with a multitude of benefits like +- granular scalability, +- deployment agility, +- distributed development, and so on. + +While these benefits exist, they are accompanied by complexity and performance losses. True microservices each constitute their own deployment unit with their own database. The benefits attributed to microservices can be broken down into multiple aspects. + +| Aspect | Benefits | Drawbacks | +| ---------- | -------- | --------- | +| App Instances | Scalability, Resilience | Requires Statelessness | +| Modules | Distributed Development, Structure | | +| Applications | Independent Scalability, Fault Tolerance | Communication Overhead | +| Deployment Units | Faster Deploy Times, Independent Deployments | Configuration Complexity | +| Databases | Depends | Data Consistency, Fragmentation | + +### Flexibility in Deployments + +Instead of just choosing between a monolith and microservices, these aspects can be combined into an architecture that fits the specific product. + +Since each cut not only has benefits, but also drawbacks, it's important to choose which benefits actually help the overall product and which drawbacks can be accepted. + +![Multiple deployment units - one contains the UIs, one contains shared service instances, one contains a shared database, two each contain an app connected to the shared database, one contains a database and an app, which is also connected to the shared database](./assets/microservices/complex.excalidraw.svg) + +### A Late Cut + +When developing a product, it may initially not be apparent where the boundaries are. + +Keeping this in mind, an app can be developed as a modular application with use case specific CAP services. +It can first be deployed as a [monolith / modulith](#monolith-or-microservice). Once the boundaries are clear, it can then be split into multiple applications. + +Generally, the semantic separation and structure can be enforced using modules. The deployment configuration is then an independent step on top. In this way, the same application can be deployed as a monolith, as microservices with a shared database, as true microservices, or a combination of these, just via configuration change. + +![Modules which can be arranged in different deploy configurations, for example, as a monolith (bookshop, reviews, orders), as two apps (bookshop, orders in one, reviews in the other), and so on.](./assets/microservices/late-cut.excalidraw.svg) + +### Best Practices {.good} + +* Prefer a late cut +* Stay flexible in where to cut +* Prefer staying loosely coupled → for example, ReviewsService → reviewed events → UPDATE average ratings +* Leverage database-level integration selectively → Prefer referring to (public) service entities, not (private) database entities + +## Appendix + +### Monolith or Microservice + +A monolith is a single deployment unit with a single application. This is very convenient, because every part of the app is accessible in memory. + +![A diagram showing a monolithic application architecture. Three modules labeled bookshop, reviews, and orders are grouped together inside a single large container, representing one deployment unit. The modules are visually separated within the container but are part of the same application. The environment is clean and technical, focusing on modular structure within a unified deployment. The tone is neutral and informative. Text in the image includes bookshop, reviews, and orders.](./assets/microservices/monolith.excalidraw.svg) + +A modulith, even though the app is separated into multiple CAP services inside multiple modules, can still be deployed as a single monolithic application. +This combines the benefit of a clear structure and distributed development while keeping a simple deployment. + +![A single application visualized as a large container holding three labeled modules: bookshop, reviews, and orders. The modules are grouped together within the container, indicating they are part of the same deployment unit. The environment is clean and technical, focusing on modular structure within a unified application. The tone is neutral and informative. Text in the image includes bookshop, reviews, and orders.](./assets/microservices/modulith.excalidraw.svg) + +True microservices each consist of their own deployment unit with their own application and their own database. +Meaning that they're truly independent of each other. And it works well if they are actually independent. + +![Diagram showing a simplified microservices architecture with three separate deployment units. Each unit contains one application labeled App and one database labeled DU. The units are visually separated, emphasizing their independence. The environment is clean and technical, focusing on the modular structure of microservices. The tone is neutral and informative. No additional text is present in the image.](./assets/microservices/true-microservices.excalidraw.svg) + +What was mentioned earlier is a simplified view. In an actual microservice deployment, there are typically shared service instances and wiring needs to be provided so that apps can talk to each other, directly or via events. +If the microservices are not cut well, the communication overhead leads to high performance losses and often the need for data replication or caching. + +![Diagram showing a detailed microservices architecture with three separate deployment units. Each unit contains one application labeled App and one database labeled DU. The units are visually separated, emphasizing their independence. Between the applications, there are two types of communication: a solid line labeled Events connecting the rightmost and center units, and a solid line connecting the center and left units. The environment is clean and technical, focusing on the modular structure and event-driven communication between microservices. The tone is neutral and informative. Text in the image includes DU, App, and Events.](./assets/microservices/true-microservices-full.excalidraw.svg) + + +### Application Instances + +Having only a single virtual machine or container, the application can only be scaled vertically by increasing the CPU and memory resources. This typically has an upper limit and requires a restart when scaling. + +To improve scalability, we can start multiple instances of the same application. + +Benefits: +- Near unlimited scaling +- No downtimes when scaling +- Better resilience against failures in single app instances + +Requirement: +- The app needs to be stateless, state needs to be persisted + +Multiple app instances can be used for both monoliths and microservices. + +![A single app with multiple application instances](./assets/microservices/app-instances.excalidraw.svg) + +### Modules + +When many developers work on an app, a distribution of work is necessary. Nowadays this distribution is often reached by each team working on one or multiple microservices. +Also, microservices are potentially cut by which team is developing them. + +Instead, developers can work on single modules, which are later deployed and run as a single app... or as multiple apps. But this choice is then independent of who is developing the module. + +Benefits: +- Distributed Development +- Clear Structure + +![Modules: bookshop containing AdminService and CatalogService, reviews containing ReviewsService, orders containing OrdersService, common](./assets/microservices/modules.excalidraw.svg) + +### Multiple Applications + +As described above, [application instances](#application-instances) already have near unlimited scaling, even for a monolith. So why would you want multiple apps? + +Benefits: +- Resource Separation +- Independent Scaling +- Fault Tolerance + +Drawbacks: +- Latency for synchronous calls between dependent apps + +![Three applications connected to the same database, two of the applications communicate](./assets/microservices/multiple-apps.excalidraw.svg) + +#### Resource Separation + +One part of an application may do highly critical background processing, while another handles incoming requests. +The incoming requests take CPU cycles and consume memory, which should rather be used for the background processing. +To make sure that there are always enough resources for specific tasks, they can be split into their own app. + +#### Independent Scaling + +Similar to resource separation, different parts of the app may have different requirements and profiles for scaling. +For some parts, a 100% CPU utilization over an extended period is accepted for efficiency, while request handling apps need spare resources to handle user requests with low latency. + +#### Fault Tolerance + +While app instances already provide some resilience, there are failure classes (for example, bugs) which affect each app instance. + +Separating functionality into different apps means that when one app experiences issues, the functionality of the other apps is still available. +In the bookstore example, while reviews may be down, orders may still be possible. + +This benefit is null for apps with synchronous dependencies on each other. If A depends on synchronous calls to B, then if B is down, A is down as well. + +### Multiple Deployment Units + +With multiple apps, you can still deploy them together as one unit, for example as part of a multitarget application archive. +Once an application grows bigger, this takes a significant amount of time. +Deployments can then be split up either by type (for example, deploying UIs separately) or horizontally (for example, deploying each app via its own deployment unit). + +Benefits: +- Faster individual deploy times +- Independent deployments + +Drawbacks: +- Coordination between deployment units for updates with dependencies +- Configuration wiring to connect systems across deployment units + +![Diagram illustrating multiple deployment units in a microservices architecture. The image shows several distinct containers, each representing a deployment unit. One unit contains UIs, another contains shared service instances, a third contains a shared database, and two separate units each contain an app connected to the shared database. There is also a unit that contains both a database and an app, which is also connected to the shared database. The containers are visually separated, emphasizing modularity and independent deployment. The environment is technical and organized, focusing on the structure and relationships between deployment units. Text in the image includes DU, App, and UIs. The tone is neutral and informative, highlighting architectural flexibility and separation of concerns in microservices deployment.](./assets/microservices/multiple-deployment-units.excalidraw.svg) + + +With a single deployment unit, when a fix for one part needs to be deployed, the risk of redeploying the rest of the application needs to be considered. +For example, there may already been changes to other parts of the app in the same code line. +A restart / rolling restart may also lead to higher resource consumption due to startup activities and thus slightly degrade the performance during this time. + +Being able to deploy apps or other resources independently reduces the risk when a single part of the system needs to be updated. +The update decision needs less coordination and can be made by the team responsible for this part of the system. + +Coordination is still necessary when deploying changes that affect the whole system, for example when a feature needs implementations in multiple apps. + +### Multiple Databases + +Here we need to differentiate between two scenarios: +- Using multiple types of databases +- Using multiple databases of the same type + +A polyglot persistence can be used when the app has different requirements for the types of data it needs to store. +For example, there may be a large number of large files that can be stored in a document store, while corresponding administrative data is stored in a relational database. + +Benefits: +- Use suitable technology for different use cases + + +In contrast, using multiple databases of the same type may be suggested for +- Scalability +- Resource Separation +- Tenant Isolation +- Semantic Separation + +Scalability and resource separation need multiple database instances. Tenant isolation and semantic separation could also be achieved through multiple schemas or containers inside the same database instance. + + + +Drawbacks: +- Data consistency across databases +- Collecting data across databases + +![A single application connected to multiple databases](./assets/microservices/multiple-dbs.excalidraw.svg) + +#### Data Federation + +When data is distributed across multiple databases, strategies may be necessary to combine data from multiple sources. + +- Fetching on demand + - Caching +- HANA synonyms +- Data Replication diff --git a/guides/deployment/to-cf.md b/guides/deployment/to-cf.md index abe553dc91..bb5e1888a5 100644 --- a/guides/deployment/to-cf.md +++ b/guides/deployment/to-cf.md @@ -69,7 +69,7 @@ cd bookshop In addition, you need to prepare the following: -#### 1. SAP BTP with SAP HANA Cloud Database up and Running {#btp-and-hana} +#### 1. SAP BTP with SAP HANA Cloud Database Up and Running {#btp-and-hana} - Access to [SAP BTP, for example a trial](https://developers.sap.com/tutorials/hcp-create-trial-account.html) - An [SAP HANA Cloud database running](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/create-sap-hana-database-instance-using-sap-hana-cloud-central) in your subaccount @@ -126,17 +126,15 @@ npm i @sap/cds #> if necessary ## Prepare for Production {#prepare-for-production} -If you followed CAP's grow-as-you-go approach so far, you've developed your application with an in-memory database and basic/mock authentication. To prepare for production you need to ensure respective production-grade choices are configured: +If you followed CAP's grow-as-you-go approach, you've developed your application with an in-memory database and basic (mocked) authentication. In the cloud, you typically use production-grade services like SAP HANA and authentication providers. -![You need to add SAP HANA Cloud, an App Router and XSUAA.](assets/deploy-overview.drawio.svg){style="margin: 30px auto"} - -We'll use the `cds add ` CLI command for that, which ensures the required services are configured correctly and corresponding package dependencies are added to your _package.json_. +The `cds add ` command ensures required services are configured correctly and their dependencies are added to your _package.json_. ### 1. SAP HANA Database
-While we used SQLite as a low-cost stand-in during development, we're going to use an SAP HANA Cloud database for production: +While we used SQLite as a low-cost stand-in during development, we're using an SAP HANA Cloud database for production:
@@ -147,7 +145,7 @@ While we used SQLite or H2 as a low-cost stand-in during development, we're goin
```sh -cds add hana --for production +cds add hana ``` [Learn more about using SAP HANA for production.](../databases-hana){.learn-more} @@ -157,24 +155,13 @@ cds add hana --for production Configure your app for XSUAA-based authentication: ```sh -cds add xsuaa --for production +cds add xsuaa ``` ::: tip This will also generate an `xs-security.json` file The roles/scopes are derived from authorization-related annotations in your CDS models. Ensure to rerun `cds compile --to xsuaa`, as documented in the [_Authorization_ guide](/guides/security/authorization#xsuaa-configuration) whenever there are changes to these annotations. ::: -::: details For trial and extension landscapes, OAuth configuration is required -Add the following snippet to your _xs-security.json_ and adapt it to the landscape you're deploying to: - -```json -"oauth2-configuration": { - "redirect-uris": ["https://*.cfapps.us10-001.hana.ondemand.com/**"] -} -``` - -::: - [Learn more about SAP Authorization and Trust Management/XSUAA.](https://discovery-center.cloud.sap/serviceCatalog/authorization-and-trust-management-service?region=all){.learn-more} ### 3. MTA-Based Deployment { #add-mta-yaml} @@ -221,12 +208,14 @@ For **single-tenant applications**, you can use [SAP Build Work Zone, Standard E cds add workzone ``` +**Important:** This also requires you to set up SAP Build Work Zone, Standard Edition [according to the SAP Learning tutorial](https://developers.sap.com/tutorials/spa-configure-workzone.html). + ### 6. Optional: Multitenancy { #add-multitenancy } To enable multitenancy for production, run the following command: ```sh -cds add multitenancy --for production +cds add multitenancy ``` [Learn more about MTX services.](../multitenancy/#behind-the-scenes){.learn-more} @@ -242,24 +231,23 @@ The previous steps are required _only once_ in a project's lifetime. With that d ## Build and Deploy -You can now freeze dependencies, build, and deploy the application: - +Make sure you are logged in to Cloud Foundry and target the space you want to deploy to: ```sh -cds up +cf login --sso # to log on with SAP Universal ID +cf target ``` +[Learn more about `cf login`](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/7a37d66c2e7d401db4980db0cd74aa6b.html){.learn-more} -[You need to be logged in to Cloud Foundry.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/7a37d66c2e7d401db4980db0cd74aa6b.html){.learn-more} +::: tip Prevent outdated lock file issues +If your project already includes a _package-lock.json_, run `npm update` to make sure it’s in sync with your _package.json_ before proceeding. +::: - - - ::: details Essentially, this automates the following steps... ```sh @@ -267,8 +255,11 @@ ln -sf ../package-lock.json npm i app/browse npm i app/admin-books +# If project is monorepo +ln -sf ../package-lock.json + # If project is multitenant -npm i --package-lock-only mtx/sidecar +npm i --package-lock-only --prefix mtx/sidecar # If package-lock.json doesn't exist npm i --package-lock-only @@ -308,7 +299,7 @@ You can use this URL to access the approuter as the entry point of your applicat For **multitenant applications**, you have to subscribe a tenant first. The application is accessible via a tenant-specific URL after subscription. ::: tip No index page and SAP Fiori preview in the cloud -The default index page and [SAP Fiori preview](../../advanced/fiori#sap-fiori-preview), that you are used to see from local development, are only available for the development profile and not available in the cloud. For productive applications, you should add a proper SAP Fiori Elements application through on of the [user interface options](#add-ui) outlined before. +The default index page and [SAP Fiori preview](../../advanced/fiori#sap-fiori-preview), that you are used to seeing during local development, are only meant for the development profile and not available in the cloud. For productive applications, you should add a proper SAP Fiori elements application through on of the [user interface options](#add-ui) outlined before. ::: ### Inspect Apps in BTP Cockpit diff --git a/guides/deployment/to-kyma.md b/guides/deployment/to-kyma.md index e02c665201..c1d01c6965 100644 --- a/guides/deployment/to-kyma.md +++ b/guides/deployment/to-kyma.md @@ -85,9 +85,15 @@ The following diagram illustrates the deployment workflow: + Make sure your SAP HANA Cloud is [mapped to your namespace](https://community.sap.com/t5/technology-blogs-by-sap/consuming-sap-hana-cloud-from-the-kyma-environment/ba-p/13552718#toc-hId-569025164) + Ensure SAP HANA Cloud is accessible from your Kyma cluster by [configuring trusted source IPs](https://help.sap.com/docs/HANA_CLOUD/9ae9104a46f74a6583ce5182e7fb20cb/0610e4440c7643b48d869a6376ccaecd.html) +#### Configure Kubernetes + +Download the Kubernetes configuration from SAP BTP and move it to _$HOME/.kube/config_. + +[Learn more in the SAP BTP Kyma documentation](https://help.sap.com/docs/btp/sap-business-technology-platform/access-kyma-instance-using-kubectl){.learn-more} + #### Get Access to a Container Registry -SAP BTP doesn't provide a container registry, but you can choose from offerings of hosted open source and private container image registries, as well as solutions that can be run on premise or in your own cloud infrastructure. +SAP BTP doesn't provide a container image registry (or container repository), but you can choose from offerings of hosted open source and private container image registries, as well as solutions that can be run on premise or in your own cloud infrastructure. ::: tip Ensure network access @@ -102,26 +108,17 @@ To use a docker image from a private repository, you need to [create an image pu ::: details Use this script to create the docker pull secret... ```sh -echo -n "Your repository: "; read YOUR_REPOSITORY +echo -n "Your docker registry server: "; read YOUR_REGISTRY echo -n "Your user: "; read YOUR_USER echo -n "Your email: "; read YOUR_EMAIL -echo -n "Your API key: "; read -s YOUR_API_KEY +echo -n "Your API token: "; read -s YOUR_API_TOKEN kubectl create secret docker-registry \ - "$YOUR_REPOSITORY" \ + docker-registry \ "--docker-server=$YOUR_REGISTRY" \ "--docker-username=$YOUR_USER" \ - "--docker-password=$YOUR_API_KEY" \ + "--docker-password=$YOUR_API_TOKEN" \ "--docker-email=$YOUR_EMAIL" -``` -The `image` property needs to contain the full tag that was used to push the image to the repository: - -```yaml -spec: - imagePullSecrets: - - name: $YOUR_REPOSITORY - containers: - - name: cap-srv - image: $YOUR_REPOSITORY.docker.io/$YOUR_IMAGE:$YOUR_VERSION +# The 2nd 'docker-registry' above is our default secret name. ``` ::: @@ -135,11 +132,24 @@ It is recommended to use a technical user for this secret that has only read per Let's start with a new sample project and prepare it for production using an SAP HANA database and XSUAA for authentication: +
+ +```sh +cds init bookshop --java --add sample && cd bookshop +cds add hana,xsuaa +``` + +
+
+ ```sh cds init bookshop --add sample && cd bookshop -cds add hana,xsuaa --for production +cds add hana,xsuaa ``` +
+ + #### User Interfaces If you need a UI, you can also add SAP Build Work Zone support: @@ -169,6 +179,8 @@ They support the deployment of your CAP service, database, UI content, and the c #### Build and Deploy +**First, ensure the Docker daemon** is running, for example by starting Docker Desktop. + You can now quickly deploy the application like so: ```sh @@ -307,7 +319,7 @@ domain: # Container image registry image: - registry: + registry: ``` ::: @@ -321,14 +333,16 @@ srv: # [Service bindings](#configuration-options-for-service-bindings) bindings: - # [Kubernetes container resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) + # Kubernetes container resources + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: # Map of additional env variables env: MY_ENV_VAR: 1 - # [Kubernetes Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + # Kubernetes Liveness, Readiness and Startup Probes + # https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ health: liveness: path: @@ -336,7 +350,7 @@ srv: path: startupTimeout: - # [Container image](#configuration-options-for-container-images) + # Container image image: ``` ::: @@ -435,7 +449,6 @@ kyma.operator.kyma-project.io/default edited [Learn more about adding modules from the Kyma Dashboard.](https://help.sap.com/docs/btp/sap-business-technology-platform/enable-and-disable-kyma-module?version=Cloud#loio1b548e9ad4744b978b8b595288b0cb5c){.learn-more style="margin-top:10px"} -::: #### Configuration Options for Services diff --git a/guides/i18n.md b/guides/i18n.md index 2cb527c157..245e1d417d 100644 --- a/guides/i18n.md +++ b/guides/i18n.md @@ -186,7 +186,6 @@ Upon incoming requests at runtime, the user's preferred language is determined a 1. The value of the `sap-locale` URL parameter, if present. 2. The value of the `sap-language` URL parameter, but only if it's `1Q`, `2Q` or `3Q` as described below. 3. The first entry from the request's `Accept-Language` header. - 4. The `default_language` configured on the app level. 2. Narrow to normalized locales as described below. ::: tip CAP Node.js accepts formats following the available standards of POSIX and RFC 1766, and transforms them into normalized locales. CAP Java only accepts language codes following the standard of RFC 1766 (or [IETF's BCP 47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt)). diff --git a/guides/messaging/task-queues.md b/guides/messaging/task-queues.md new file mode 100644 index 0000000000..49953114bc --- /dev/null +++ b/guides/messaging/task-queues.md @@ -0,0 +1,55 @@ +--- +synopsis: > + Task Queues allow to schedule the processing of workloads in a resilient fashion. +status: released +--- + +# Task Queues + +The _Outbox Pattern_ is a reliable strategy used in distributed systems to ensure that messages or events are consistently recorded and delivered, even in the face of failures. +This pattern, however, can not only be applied to outbound messages, but to inbound messages and server-internal background tasks as well. +The core principle remains the same: +1. Persist the message (or _task_) in the database -- using the same transaction as the triggering action, if applicable +2. Process it asynchronously afterwards -- incl. retries, if necessary + +Over the next months, CAP will evolve its outbox to generic _Task Queues_ with the following four components: +1. Outbox → for outbound calls to remote services +2. Inbox → for asynchronously handling inbound requests +3. Background tasks → e.g., scheduled periodically +4. Callbacks → implement SAGA patterns + +This guide will grow with the functionality. + + + +## Outbox { #outbox } + +Regarding the _outbox_, please see the following existing documentation: +- [Transactional Outbox](../../java/outbox) in CAP Java +- [Outboxing with `cds.queued`](../../node.js/queue) in CAP Node.js + + + +## Inbox { #inbox } + +Through the _inbox_, inbound messages can be accepted as asynchronous tasks. +That is, the messaging service persists the message to the database, acknowledges it to the message broker, and schedules its processing. + +Simply configure your messaging service for Node.js as cds.requires.messaging.inboxed = true and for CAP Java as cds.messaging.services=[{"name": "messaging-name", "inbox": {"enabled": true}}] + +**Inboxing moves the dead letter queue into your CAP app❗️** + +With the inbox, all messages are acknowledged towards the message broker regardless of whether they can be processed or not. +Hence, failures need to be managed via the dead letter queue built on `cds.outbox.Messages`. + +[Learn more about the dead letter queue in Node.js.](../../node.js/queue#managing-the-dead-letter-queue){.learn-more} +[Learn more about the dead letter queue in Java.](../../java/outbox#outbox-dead-letter-queue){.learn-more} + +Inboxing is especially beneficial in case the message broker does not allow to trigger redelivery and/ or "fix" the message payload. + + + +## More to Come + +This documentation is not complete yet, or the APIs are not released for general availability. +Stay tuned to upcoming releases for further updates. diff --git a/guides/multitenancy/index.md b/guides/multitenancy/index.md index d97fc95c10..fe5e895c63 100644 --- a/guides/multitenancy/index.md +++ b/guides/multitenancy/index.md @@ -53,7 +53,7 @@ cd bookshop Now, you can run this to enable multitenancy for your CAP application: ```sh -cds add multitenancy --for production +cds add multitenancy ```
@@ -274,6 +274,11 @@ After adding multitenancy, Maven build should be used to generate the model rela mvn install ``` +:::warning Error message: 'Invalid MTX sidecar configuration' +If you get the message 'Invalid MTX sidecar configuration', you need to add the dependency to `@sap/cds-mtxs` also to the `package.json` in your project root. +This is a known issue in CDS 9. +::: +
## Test-Drive Locally {#test-locally} @@ -715,7 +720,7 @@ In order to get your multitenant application deployed, follow this excerpt from Once: Add SAP HANA Cloud, XSUAA, and [App Router](../deployment/to-cf#add-app-router) configuration. The App Router acts as a single point-of-entry gateway to route requests to. In particular, it ensures user login and authentication in combination with XSUAA. ```sh -cds add hana,xsuaa --for production +cds add hana,xsuaa ``` If you intend to serve UIs you can easily set up the SAP Cloud Portal service: @@ -751,33 +756,15 @@ Add the following snippet to your _xs-security.json_ and adapt it to the landsca [Learn more about configured BTP services for SaaS applications.](#behind-the-scenes){.learn-more} -[Freeze the `npm` dependencies](../deployment/to-cf#freeze-dependencies) for server and MTX sidecar. - -```sh -npm update --package-lock-only -npm update --package-lock-only --prefix mtx/sidecar -``` - -In addition, you need install and freeze dependencies for your UI applications: -```sh -npm i --prefix app/browse -npm i --prefix app/admin-books -``` - -**Build and deploy**: ::: code-group ```sh [Cloud Foundry] -mbt build -t gen --mtar mta.tar -cf deploy gen/mta.tar +cds up ``` ```sh [Kyma] -# Omit `--push` flag for testing, otherwise `ctz` -# will push images to the specified repository -ctz containerize.yaml --push -helm upgrade --install bookshop ./chart +cds up --to k8s ``` ::: @@ -993,14 +980,14 @@ cds watch --profile dev ## SaaS Dependencies {#saas-dependencies} Some of the xsuaa-based services your application consumes need to be registered as _reuse services_ to work in multitenant environments. This holds true for the usage of both the SaaS Registry service and the Subscription Manager Service (SMS). -CAP Java as well as `@sap/cds-mtxs`, each offer an easy way to integrate these dependencies. They support some services out of the box and also provide a simple API for applications. Most notably, you need such dependencies for the following SAP BTP services: [Audit Log](https://discovery-center.cloud.sap/serviceCatalog/audit-log-service), [Event Mesh](https://discovery-center.cloud.sap/serviceCatalog/event-mesh), [Destination](https://discovery-center.cloud.sap/serviceCatalog/destination), [HTML5 Application Repository](https://discovery-center.cloud.sap/serviceCatalog/html5-application-repository-service), and [Cloud Portal](https://discovery-center.cloud.sap/serviceCatalog/cloud-portal-service). +CAP Java as well as `@sap/cds-mtxs`, each offer an easy way to integrate these dependencies. They support some services out of the box and also provide a simple API for applications. Most notably, you need such dependencies for the following SAP BTP services: [Audit Log](https://discovery-center.cloud.sap/serviceCatalog/audit-log-service), [Event Mesh](https://discovery-center.cloud.sap/serviceCatalog/event-mesh), [Destination](https://discovery-center.cloud.sap/serviceCatalog/destination), [HTML5 Application Repository](https://discovery-center.cloud.sap/serviceCatalog/html5-application-repository-service), and [Cloud Portal](https://discovery-center.cloud.sap/serviceCatalog/cloud-portal-service). For CAP Java, all these services are supported natively and SaaS dependencies are automatically created if such a service instance is bound to the CAP Java application, that is, the `srv` module. :::tip Explicitly activate the Destination service SaaS dependency for Destination service needs to be activated explicitly in the `application.yaml` due to security reasons. SaaS dependencies for some of the other services can be **de**activated by setting the corresponding property to `false` in the `application.yaml`. -Refer to the `cds.multiTenancy.dependencies` section in the [CDS properties](/java/developing-applications/properties#cds-properties). +Refer to the `cds.multiTenancy.dependencies` section in the [CDS properties](/java/developing-applications/properties#cds-properties). ::: For CAP Node.js, all these services are supported natively and can be activated individually by providing configuration in `cds.requires`. In the most common case, you simply activate service dependencies like so: @@ -1049,10 +1036,11 @@ The Boolean values in the _mtx/sidecar/package.json_ activate the default config ### Additional Services -If your CAP Java application uses a service that isn't supported out of the box, you can add a custom dependency by providing a custom handler. Refer to [Define Dependent Services](../../java/multitenancy#define-dependent-services) for details. +In **CAP Java**, if your application uses a service that isn't supported out of the box, you can define dependencies by providing a custom handler. +[Learn more about defining dependent services](../../java/multitenancy#define-dependent-services){.learn-more} -In Node.js, you can use the `subscriptionDependency` setting to provide a dependency configuration similar to the standard configuration shown before. Use your application's or CAP plugin's _package.json_: +In **CAP Node.js**, you can use a custom `subscriptionDependency` entry in your application's or CAP plugin's _package.json_: ```json [package.json] "cds": { @@ -1068,7 +1056,6 @@ In Node.js, you can use the `subscriptionDependency` setting to provide a depend Alternatively, overriding the [`dependencies`](./mtxs#get-dependencies) handler gives you full flexibility for any custom implementation. -
## Add Custom Handlers diff --git a/guides/multitenancy/mtxs.md b/guides/multitenancy/mtxs.md index 1c96b8c0c9..178530494e 100644 --- a/guides/multitenancy/mtxs.md +++ b/guides/multitenancy/mtxs.md @@ -1140,7 +1140,6 @@ The _SaasProvisioningService_ is a façade for the _DeploymentService_ to adapt - `workerSize` — max number of parallel asynchronous jobs per database - `clusterSize` — max number of database clusters, running `workerSize` jobs each - `queueSize` — max number of jobs waiting to run in the job queue -- `dependencies` — SAP BTP SaaS Provisioning service dependencies #### HTTP Request Options diff --git a/guides/multitenancy/old-mtx-migration.md b/guides/multitenancy/old-mtx-migration.md index 59d8042927..fdc81580db 100644 --- a/guides/multitenancy/old-mtx-migration.md +++ b/guides/multitenancy/old-mtx-migration.md @@ -20,8 +20,14 @@ Towards new multitenancy capabilities -::: warning -Make sure that you always use the latest version of the CAP modules using `npm outdated`. For Java, also check the versions configured in `pom.xml` files. +::: warning Separate model changes from migration +We strongly recommended to separate any model changes from the migration. If you need to do model changes for the migration, please deploy the application +based on `@sap/cds-mtx` and upgrade all tenants using the [upgrade endpoint](./old-mtx-apis.md#upgrade-base-model-from-filesystem-asynchronous) before you do the migration. +::: +::: warning Deprecated! Update all modules +Make sure that you always use the latest version of the CAP modules using `npm outdated`. For Java, also check the versions configured in `pom.xml` files. Since +`@sap/cds-mtx` is deprecated for quite some time now and will no longer run with, for example, the latest version of `@sap/cds`, updating the versions and adapting your application to it +can only be done together with the migration to `@sap/cds-mtxs`. Please also read all release notes carefully and check it for changes that need to be made to the configuration. ::: ## Functional Differences @@ -75,7 +81,6 @@ Some of the roles have changed with `@sap/cds-mtxs`. ### Temporary Limitations - Diagnose API isn't available. -- Upload of extension only works synchronously. ### Permanent Limitations @@ -371,6 +376,7 @@ In `@sap/cds-mtxs`, you can do the same configuration for the `cds.xt.Deployment }, } ``` +See also [Deployment configuration](./mtxs.md#deployment-config) ##### Extension Restrictions @@ -409,6 +415,13 @@ With `@sap/cds-mtxs`, the same configuration has moved to the `cds.xt.Extensibil } ``` +See also [Extensibility configuration](./mtxs.md#extensibility-config) + +### Verify Application Locally + +As first verification of your configuration changes, you can try to run your application locally in [hybrid mode](../../advanced/hybrid-testing#run-with-service-bindings). To bind all the service +that are bound to your existing application, you can call `cds bind --to-app-services `. Afterwards, you can run `cds run --profile hybrid --resolve-bindings`. + ### Migrate Tenant Content of Existing Applications Depending on the MTX features that your existing application has used, you need to execute some steps to move your data to the persistence used by `@sap/cds-mtxs`. diff --git a/guides/providing-services.md b/guides/providing-services.md index a7c39045cd..5cfd2d8511 100644 --- a/guides/providing-services.md +++ b/guides/providing-services.md @@ -736,7 +736,7 @@ The records are locked until the end of the transaction by commit or rollback st Here's an overview table: | State | Select Without Lock | Select With Shared Lock | Select With Exclusive Lock/Update | -| --------------- | ----------------------- | -------------------------- | ------------------------------------- | +| --------------- | ----------------------- | -------------------------- | ------------------------------------- | | not locked | passes | passes | passes | | shared lock | passes | passes | waits | | exclusive lock | passes | waits | waits | @@ -746,12 +746,11 @@ Here's an overview table: [Learn more about using the `Select.lock()` method in the Java runtime.](../java/working-with-cql/query-api#write-lock){.learn-more} -::: warning -Pessimistic locking is not supported by SQLite. H2 supports exclusive locks only. -::: - - +::: warning Restrictions +- Pessimistic locking is supported for domain entities (DB table rows). The locking is not possible for projections and views. +- Pessimistic locking is not supported by SQLite. H2 supports exclusive locks only. +::: ## Input Validation @@ -773,6 +772,7 @@ Do not use the `@readonly` annotation on keys in all variants.
+ ### `@mandatory` Elements marked with `@mandatory` are checked for nonempty input: `null` and (trimmed) empty strings are rejected. @@ -796,42 +796,49 @@ In addition to server-side input validation as introduced above, this adds a cor
-### `@Common.FieldControl` -{#common-fieldcontrol} -The input validation for `@Common.FieldControl: #Mandatory` and `@Common.FieldControl: #ReadOnly` is done from the CAP runtimes automatically. -::: warning -Custom validations are required when using static or dynamic numeric values, for example, `@Common.FieldControl: 1` or `@Common.FieldControl: integer_field`. -::: +### `@assert .format` + +Allows you to specify a regular expression string (in ECMA 262 format in CAP Node.js and java.util.regex.Pattern format in CAP Java) that all string input must match. +```cds +entity Foo { + bar : String @assert.format: '[a-z]ear'; +} +``` -### `@assert .unique` +### `@assert .range` -Annotate an entity with `@assert.unique.`, specifying one or more element combinations to enforce uniqueness checks on all CREATE and UPDATE operations. For example: +Allows you to specify `[ min, max ]` ranges for elements with ordinal types — that is, numeric or date/time types. For `enum` elements, `true` can be specified to restrict all input to the defined enum values. ```cds -@assert.unique: { - locale: [ parent, locale ], - timeslice: [ parent, validFrom ], -} -entity LocalizedTemporalData { - key record_ID : UUID; // technical primary key - parent : Association to Data; - locale : String; - validFrom : Date; validTo : Date; +entity Foo { + bar : Integer @assert.range: [ 0, 3 ]; + boo : Decimal @assert.range: [ 2.1, 10.25 ]; + car : DateTime @assert.range: ['2018-10-31', '2019-01-15']; + zoo : String @assert.range enum { high; medium; low; }; } ``` -{.indent} +#### ... with open intervals -This annotation is applicable to entities, which result in tables in SQL databases only. +By default, specified `[min,max]` ranges are interpreted as closed intervals, that means, the performed checks are `min ≤ input ≤ max`. You can also specify open intervals by wrapping the *min* and/or *max* values into parenthesis like that: -The value of the annotation is an array of paths referring to elements in the entity. These elements may be of a scalar type, structs, or managed associations. Individual foreign keys or unmanaged associations are not supported. + +```cds +@assert.range: [(0),100] // 0 < input ≤ 100 +@assert.range: [0,(100)] // 0 ≤ input < 100 +@assert.range: [(0),(100)] // 0 < input < 100 +``` +In addition, you can use an underscore `_` to represent *Infinity* like that: + +```cds +@assert.range: [(0),_] // positive numbers only, _ means +Infinity here +@assert.range: [_,(0)] // negative number only, _ means -Infinity here +``` +> Basically values wrapped in parentheses _`(x)`_ can be read as _excluding `x`_ for *min* or *max*. Note that the underscore `_` doesn't have to be wrapped into parenthesis, as by definition no number can be equal to *Infinity* . -If structured elements are specified, the unique constraint will contain all columns stemming from it. If the path points to a managed association, the unique constraint will contain all foreign key columns stemming from it. -::: tip -You don't need to specify `@assert.unique` constraints for the primary key elements of an entity as these are automatically secured by a SQL `PRIMARY KEY` constraint. -::: +Support for open intervals and infinity is available for CAP Node.js since `@sap/cds` version **8.5** and in CAP Java since version **3.5.0**. @@ -912,57 +919,13 @@ Cross-service checks are not supported. It is expected that the associated entit The `@assert.target` check constraint relies on database locks to ensure accurate results in concurrent scenarios. However, locking is a database-specific feature, and some databases don't permit to lock certain kinds of objects. On SAP HANA, for example, views with joins or unions can't be locked. Do not use `@assert.target` on such artifacts/entities. ::: -### `@assert .format` -Allows you to specify a regular expression string (in ECMA 262 format in CAP Node.js and java.util.regex.Pattern format in CAP Java) that all string input must match. - -```cds -entity Foo { - bar : String @assert.format: '[a-z]ear'; -} -``` - -### `@assert .range` - -Allows you to specify `[ min, max ]` ranges for elements with ordinal types — that is, numeric or date/time types. For `enum` elements, `true` can be specified to restrict all input to the defined enum values. - -```cds -entity Foo { - bar : Integer @assert.range: [ 0, 3 ]; - boo : Decimal @assert.range: [ 2.1, 10.25 ]; - car : DateTime @assert.range: ['2018-10-31', '2019-01-15']; - zoo : String @assert.range enum { high; medium; low; }; -} -``` -#### ... with open intervals - -By default, specified `[min,max]` ranges are interpreted as closed intervals, that means, the performed checks are `min ≤ input ≤ max`. You can also specify open intervals by wrapping the *min* and/or *max* values into parenthesis like that: - - -```cds -@assert.range: [(0),100] // 0 < input ≤ 100 -@assert.range: [0,(100)] // 0 ≤ input < 100 -@assert.range: [(0),(100)] // 0 < input < 100 -``` -In addition, you can use an underscore `_` to represent *Infinity* like that: - -```cds -@assert.range: [(0),_] // positive numbers only, _ means +Infinity here -@assert.range: [_,(0)] // negative number only, _ means -Infinity here -``` -> Basically values wrapped in parentheses _`(x)`_ can be read as _excluding `x`_ for *min* or *max*. Note that the underscore `_` doesn't have to be wrapped into parenthesis, as by definition no number can be equal to *Infinity* . - -Support for open intervals and infinity is available for CAP Node.js since `@sap/cds` version **8.5** and in CAP Java since version **3.5.0**. +
-### `@assert .notNull` -Annotate a property with `@assert.notNull: false` to have it ignored during the generic not null check, for example if your persistence fills it automatically. +### Database Constraints -```cds -entity Foo { - bar : String not null @assert.notNull: false; -} -``` +Next to input validation, you can add [database constraints](databases#database-constraints) to prevent invalid data from being persisted.
@@ -1090,6 +1053,8 @@ service Sue { action order (x:Integer) returns Integer; //bound to the collection and not a specific instance of Foo action customCreate (in: many $self, x: String) returns Foo; + // All parameters are optional by default, unless marked with `not null`: + action discard (reason: String not null); } } ``` diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 5381f4e48f..fc5c35db17 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -492,7 +492,7 @@ The condition defined in the `where`-clause typically associates domain data wit - `UPDATE` (as reject condition2) - `DELETE` (as reject condition2) - > 1 Node.js supports _static expressions_ that *don't have any reference to the model* such as `where: $user.level = 2` for all events. + > 1 Node.js supports _static expressions_ that *don't have any reference to the model* such as `where: $user.level = 2` for all events. > 2 CAP Java uses a filter condition by default. For instance, a user is allowed to read or edit `Orders` (defined with the `managed` aspect) that they have created: @@ -516,13 +516,6 @@ Supported features are: * Value references to constants, [user attributes](#user-attrs), and entity data (elements including [paths](#association-paths)) * [Exists predicate](#exists-predicate) based on subselects. - -
- -CAP Java offers the option to enable rejection conditions for `UPDATE`, `DELETE` and custom events. Enable it using the configuration option cds.security.authorization.instance-based.reject-selected-unauthorized-entity.enabled: true. - -
- ::: info Avoid enumerable keys In case the filter condition is not met in an `UPDATE` or `DELETE` request, the runtime rejects the request (response code 403) even if the user is not even allowed to read the entity. To avoid to disclosure the existence of such entities to unauthorized users, make sure that the key is not efficiently enumerable. ::: @@ -899,7 +892,7 @@ Information about roles and attributes has to be made available to the UAA platf Derive scopes, attributes, and role templates from the CDS model: ```sh -cds add xsuaa --for production +cds add xsuaa ``` This generates an _xs-security.json_ file: diff --git a/guides/using-services.md b/guides/using-services.md index 37d5d7c5f8..847616b79a 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -92,7 +92,7 @@ If you want to learn about this topic based on the [Incident Management](https:/ ## Install Dependencies { .node } ```sh -npm add @sap-cloud-sdk/http-client@3.x @sap-cloud-sdk/connectivity@3.x @sap-cloud-sdk/resilience@3.x +npm add @sap-cloud-sdk/http-client@4.x @sap-cloud-sdk/connectivity@4.x @sap-cloud-sdk/resilience@4.x ``` ## Get and Import an External Service API { #external-service-api } @@ -986,7 +986,7 @@ Since you don't want to use the destination for local testing, but only for prod } ``` -Additionally, you can provide [destination options](https://sap.github.io/cloud-sdk/api/v3/interfaces/sap-cloud-sdk_connectivity.DestinationFetchOptions.html) inside a `destinationOptions` object: +Additionally, you can provide [destination options](https://sap.github.io/cloud-sdk/api/v4/interfaces/sap-cloud-sdk_connectivity.DestinationFetchOptions.html) inside a `destinationOptions` object: ```jsonc "cds": { @@ -1394,7 +1394,7 @@ The MTA-based deployment is described in [the deployment guide](deployment/). Yo ```sh -cds add xsuaa,destination,connectivity --for production +cds add xsuaa,destination,connectivity ``` ::: details Learn what this does in the background... @@ -1661,7 +1661,7 @@ This list specifies the properties for application defined destinations. | `queries` | Map of URL parameters | | `forwardAuthToken` | [Forward auth token](#forward-auth-token) | -[Destination Type in SAP Cloud SDK for JavaScript](https://sap.github.io/cloud-sdk/api/v3/interfaces/sap_cloud_sdk_connectivity.Destination.html){.learn-more .node} +[Destination Type in SAP Cloud SDK for JavaScript](https://sap.github.io/cloud-sdk/api/v4/interfaces/sap-cloud-sdk_connectivity.Destination.html){.learn-more .node} [HttpDestination Type in SAP Cloud SDK for Java](https://help.sap.com/doc/82a32040212742019ce79dda40f789b9/1.0/en-US/index.html){.learn-more .java} #### Authentication Types diff --git a/index.md b/index.md index e3f23d0d7e..e14dee3470 100644 --- a/index.md +++ b/index.md @@ -36,7 +36,7 @@ features: details: •  Capture intent ⇒ What, not how!
•  Minimized boilerplate coding
- •  Developers + domain experts
+ •  Minimized technical debt
link: about/ linkText: Read the Primer @@ -44,7 +44,7 @@ features: icon: 🌀 details: •  Jumpstart with minimal setup
- •  Rapid dev at minimized costs
+ •  Fast inner loop dev & tests
•  Grow as you go...
link: get-started/in-a-nutshell linkText: Get Started in a Nutshell diff --git a/java/_menu.md b/java/_menu.md index d69dd1f514..04e7b8b858 100644 --- a/java/_menu.md +++ b/java/_menu.md @@ -16,14 +16,14 @@ ## [Indicating Errors](event-handlers/indicating-errors) ## [Request Contexts](event-handlers/request-contexts) ## [ChangeSet Contexts](event-handlers/changeset-contexts) -# [Fiori Drafts](../../java/fiori-drafts) +# [Fiori Drafts](fiori-drafts) # [Messaging](messaging) # [Audit Logging](auditlog) -# [Change Tracking](../../java/change-tracking) +# [Change Tracking](change-tracking) # [State Transitions](../../java/flows) # [Transactional Outbox](outbox) -# [Multitenancy](../../java/multitenancy) - ## [Multitenancy (Classic)](../../java/multitenancy-classic) +# [Multitenancy](multitenancy) + ## [Multitenancy (Classic)](multitenancy-classic) # [Security](security) ## [IAS and AMS](../../java/ams) # [Spring Boot Integration](spring-boot-integration) @@ -37,5 +37,7 @@ ## [Optimizing](operating-applications/optimizing) ## [Observability](operating-applications/observability) ## [Developer Dashboard](../../java/operating-applications/dashboard) +# [Integrating Applications](../../java/integrating-applications/) + ## [Unified Customer Landscape](../../java/integrating-applications/ucl) # [Building Plugins](../../java/building-plugins) # [Migration Guides](migration) \ No newline at end of file diff --git a/java/cds-data.md b/java/cds-data.md index e24f4fa9e0..a897eea7ae 100644 --- a/java/cds-data.md +++ b/java/cds-data.md @@ -21,47 +21,49 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ The [predefined CDS types](../cds/types) are mapped to Java types and as follows: -| CDS Type | Java Type | Remark | -|--------------------|------------------------|--------------------------------------------------------------------------| -| `cds.UUID` | `java.lang.String` | | -| `cds.Boolean` | `java.lang.Boolean` | | -| `cds.UInt8` | `java.lang.Short` | | -| `cds.Int16` | `java.lang.Short` | | -| `cds.Int32` | `java.lang.Integer` | | -| `cds.Integer` | `java.lang.Integer` | | -| `cds.Int64` | `java.lang.Long` | | -| `cds.Integer64` | `java.lang.Long` | | -| `cds.Decimal` | `java.math.BigDecimal` | | -| `cds.DecimalFloat` | `java.math.BigDecimal` | deprecated | -| `cds.Double` | `java.lang.Double` | | -| `cds.Date` | `java.time.LocalDate` | date without a time-zone (year-month-day) | -| `cds.Time` | `java.time.LocalTime` | time without a time-zone (hour-minute-second) | -| `cds.DateTime` | `java.time.Instant` | instant on the time-line with _sec_ precision | -| `cds.Timestamp` | `java.time.Instant` | instant on the time-line with _µs_ precision | -| `cds.String` | `java.lang.String` | | -| `cds.LargeString` | `java.lang.String` | `java.io.Reader` (1) if annotated with `@Core.MediaType` | -| `cds.Binary` | `byte[]` | | -| `cds.LargeBinary` | `byte[]` | `java.io.InputStream` (1) if annotated with `@Core.MediaType` | -| `cds.Vector` | `com.sap.cds.CdsVector`| for [vector embeddings](#vector-embeddings) | +| CDS Type | Java Type | Remark | +| ------------------ | ----------------------- | ------------------------------------------------------------------------ | +| `cds.UUID` | `java.lang.String` | | +| `cds.Boolean` | `java.lang.Boolean` | | +| `cds.UInt8` | `java.lang.Short` | | +| `cds.Int16` | `java.lang.Short` | | +| `cds.Int32` | `java.lang.Integer` | | +| `cds.Integer` | `java.lang.Integer` | | +| `cds.Int64` | `java.lang.Long` | | +| `cds.Integer64` | `java.lang.Long` | | +| `cds.Decimal` | `java.math.BigDecimal` | | +| `cds.DecimalFloat` | `java.math.BigDecimal` | deprecated | +| `cds.Double` | `java.lang.Double` | | +| `cds.Date` | `java.time.LocalDate` | date without a time-zone (year-month-day) | +| `cds.Time` | `java.time.LocalTime` | time without a time-zone (hour-minute-second) | +| `cds.DateTime` | `java.time.Instant` | instant on the time-line with _sec_ precision | +| `cds.Timestamp` | `java.time.Instant` | instant on the time-line with _µs_ precision | +| `cds.String` | `java.lang.String` | | +| `cds.LargeString` | `java.lang.String` | `java.io.Reader` (1) if annotated with `@Core.MediaType` | +| `cds.Binary` | `byte[]` | | +| `cds.LargeBinary` | `byte[]` | `java.io.InputStream` (1) if annotated with `@Core.MediaType` | +| `cds.Vector` | `com.sap.cds.CdsVector` | for [vector embeddings](#vector-embeddings) | +| `cds.Map` | `java.util.Map` | for arbitrary [structured data](#structured-data)(2) | ### SAP HANA-Specific Data Types To facilitate using legacy CDS models, the following [SAP HANA-specific data types](../advanced/hana#hana-types) are supported: -| CDS Type | Java Type | Remark | -| --- | --- | --- | -| `hana.TINYINT` | `java.lang.Short` | | -| `hana.SMALLINT` | `java.lang.Short` | | -| `hana.SMALLDECIMAL` | `java.math.BigDecimal` | | -| `hana.REAL` | `java.lang.Float` | | -| `hana.CHAR` | `java.lang.String` | | -| `hana.NCHAR` | `java.lang.String` | | -| `hana.VARCHAR` | `java.lang.String` | | -| `hana.CLOB` | `java.lang.String` | `java.io.Reader` (1) if annotated with `@Core.MediaType` | -| `hana.BINARY` | `byte[]` | | +| CDS Type | Java Type | Remark | +| ------------------- | ---------------------- | ------------------------------------------------------------------- | +| `hana.TINYINT` | `java.lang.Short` | | +| `hana.SMALLINT` | `java.lang.Short` | | +| `hana.SMALLDECIMAL` | `java.math.BigDecimal` | | +| `hana.REAL` | `java.lang.Float` | | +| `hana.CHAR` | `java.lang.String` | | +| `hana.NCHAR` | `java.lang.String` | | +| `hana.VARCHAR` | `java.lang.String` | | +| `hana.CLOB` | `java.lang.String` | `java.io.Reader` (1) if annotated with `@Core.MediaType` | +| `hana.BINARY` | `byte[]` | | > (1) Although the API to handle large objects is the same for every database, the streaming feature, however, is supported (and tested) in **SAP HANA**, **PostgreSQL**, and **H2**. See section [Database Support in Java](./cqn-services/persistence-services#database-support) for more details on database support and limitations. +> (2) Serialized as JSON to a CLOB column or JSONB column (on Postgres) ::: warning The framework isn't responsible for closing the stream when writing to the database. You decide when the stream is to be closed. If you forget to close the stream, the open stream can lead to a memory leak. @@ -275,6 +277,8 @@ person.toJson(); // { "salutation" : "Mr.", name : { "first" : "Frank" } } Avoid cyclic relationships between CdsData objects when using toJson. ::: +
+ ## Vector Embeddings { #vector-embeddings } @@ -1290,7 +1294,7 @@ For the download scenario, as well, you don't need to implement any custom handl If you want to override the default logic to process the uploaded stream with custom logic (for example, to parse a stream of CSV data), the best place to do that is in a custom `On` handler, as the following examples shows: ```java -@On(event = CdsService.EVENT_UPDATE) +@On(event = CqnService.EVENT_UPDATE) public void processCoverImage(CdsUpdateEventContext context, List books) { books.forEach(book -> { InputStream is = book.getCoverImage(); @@ -1355,7 +1359,7 @@ For uploads, you can either use a custom `Before` or `On` handler to wrap the pr Using a custom `Before` handler makes sense if the stream's final destination is the persistence layer of the CAP Java SDK, which writes the content to the database. Note that the pre-processing logic in this example is implemented in the `read()` methods of the `FilterInputStream` and is only called when the data is streamed, during the `On` phase of the request: ```java -@Before(event = CdsService.EVENT_UPDATE) +@Before(event = CqnService.EVENT_UPDATE) public void preProcessCoverImage(CdsUpdateEventContext context, List books) { books.forEach(book -> { book.setCoverImage(new CoverImagePreProcessor(book.getCoverImage())); @@ -1368,7 +1372,7 @@ The original `InputStream` is replaced by the proxy implementation in the `cover Using a custom `On` handler makes sense if you want to prevent that the default `On` handler is executed and to control the final destination for the stream. You then have the option to pass the streamed data on to some other service for persistence: ```java -@On(event = CdsService.EVENT_UPDATE) +@On(event = CqnService.EVENT_UPDATE) public Result processCoverImage(CdsUpdateEventContext context, List books) { books.forEach(book -> { book.setCoverImage(new CoverImagePreProcessor(book.getCoverImage())); diff --git a/java/change-tracking.md b/java/change-tracking.md index d55584ecb5..a55918d0ac 100644 --- a/java/change-tracking.md +++ b/java/change-tracking.md @@ -119,33 +119,36 @@ The level where you annotate your elements with the annotation `@changelog` is v the elements on the _domain_ level, every change made through every projection of the entity is tracked. If you annotate the elements on the _service_ level, only the changes made through that projection are tracked. -In case of the books example above, the changes made through the service entity `Bookshop.Books` are tracked, but the changes +Using the previous books example, the changes made through the service entity `Bookshop.Books` are tracked, but the changes made on the domain entity are omitted. That can be beneficial if you have a service that is used for data replication or mass changes where change tracking can be a very expensive operation, and you do not want to generate changes from such operations. Change tracking also works with the entities that have compositions and tracks the changes made to the items of the compositions. -For example, if you have an entity that represents the order with a composition that represents the items of the order, -you can annotate the elements of both and track the changes made through the order and the items in a deep update. +In the following example you have an entity that represents the order with a composition that represents the items of the order. You can annotate the elements of both and track the changes made through the order and the items in a deep update. ```cds -entity OrderItems { - key ID: UUID; +entity OrderItems : cuid { + parent : Association to Orders; [...] quantity: Integer @changelog; } -entity Orders { - key ID: UUID; +entity Orders : cuid { customerName: String @changelog; [...] - items: Composition of many OrderItems; + items: Composition of many OrderItems on items.parent = $self; } ``` -### Identifiers for Changes +:::tip Remember: Extend `Orders` entity +You must extend the `Orders` with the aspect `changelog.changeTracked` and not the `OrderItems`. With this, all changes in the `Orders`, even deep ones, are associated with the `Orders`. +::: + +### Identifiers for Entities + +You can store some elements of the entity together with the changes in the change log to produce a user-friendly identifier that annotates changes. -You can store some elements of the entity together with the changes in the change log to produce a user-friendly identifier. You define this identifier by annotating the entity with the `@changelog` annotation and including the elements that you want to store together with the changed value: @@ -156,56 +159,74 @@ annotate Bookshop.Book with @changelog: [ ``` This identifier can contain the elements of the entity or values of to-one associations that are reachable via path. -For example, for a book you can store an author name if you have an association from the book to the author. +For example, for a book you can store an author name if you have an association from the book to the author. The best candidates for identifier are the elements that are insert-only or that don't change often. -When you define the identifier for an entity, keep in mind that the projections of the annotated entity -will inherit the annotation `@changelog`. If you change the structure of the projection, -for example, exclude or rename the elements that are used in the identifier, you must annotate the projection again -to provide updated element names in the identifier. +### Identifiers for Compositions -The best candidates for identifier elements are the elements that are insert-only or that don't change often. - -:::warning Stored as-is -The values of the identifier are stored together with the change log as-is. They are not translated and some data types might -not be formatted per user locale or some requirements, for example, different units of measurement or currencies. -You should consider this when you decide what to include in the identifier. -::: +For compositions, no special annotations are required. The identifiers of the target entity are used instead. -### Identifiers for Associated Entities +For example, given the following model: -When your entity has an association to an other entity, you might want to log the changes in their relationship. - -Given the `Orders` entity with an association to a `Customer` instead of the element with customer name: ```cds -entity Orders { - key ID: UUID; +entity Orders : cuid { + OrderNo : String; customer: Association to Customer; [...] + items: Composition of many OrderItems on items.parent = $self; +} + +entity OrderItems : cuid { + parent : Association to Orders; + supplierName: String; + [...] + quantity : Integer; } ``` -If you annotate such an association with `@changelog`, by default, the change log stores the value of the associated entity key. -If you want, you can store some human-readable identifier instead. You define this by annotating the association with an own identifier: +You can annotate your model as follows to define identifiers for both entities. + +```cds +annotate Orders with @changelog: [OrderNo]; + +annotate OrderItems with @changelog: [ + parent.OrderNo, + supplierName, +]; +``` + +Changes for `Orders` and `OrderItems` will have their own respective target or root identifiers filled. + +### Human-readable values for associations + +For associations, the value of the foreign key is stored in the changelog by default. You can change this and store the values of the associated entity instead. +This kind of identifier changes the values stored in the changelog, while [entity identifiers](#identifiers-for-entities) annotate changed values. + +You annotate your entity like this: ```cds annotate Orders { customer @changelog: [ customer.name ] } ``` - -Elements from the `@changelog` annotation value must always be prefixed by the association name. The same caveats as for the identifiers for the entities apply here. - -If you annotate a composition with an identifier, the change log will contain an entry with the identifier's value. Additionally, it will include change log entries for all annotated elements of the composition's target entity. +Elements from the `@changelog` annotation value must always be prefixed by the association name. :::warning Validation required If the target of the association is missing, for example, when an entity is updated with the ID for a customer -that does not exists, the changelog entry will not be created. You need to validate +that does not exist, the changelog entry is not created. You need to validate such cases in the custom code or use annotations, for example, [`@assert.target`](/guides/providing-services#assert-target). ::: -This feature can also be used for to-many compositions, when you don't need to track the deep changes, but still want to track the additions and removals in the composition. +### Caveats of Identifiers + +Consider the following important points that are relevant for all kinds of identifiers and human-readable values: -With association identifiers you also must consider the changes in your entities structure along the projections. In case your target entity is exposed using different projections with removed or renamed elements, you also need to adjust the identifier accordingly in the source entity. +- When you define the identifier for an entity, keep in mind that the projections of the annotated entity +inherit the annotation `@changelog`. If you change the structure of the projection, +for example, exclude or rename the elements that are used in the identifier, you must annotate the projection again +to provide updated element names in the identifier. This is one additional benefit of annotating the top-most projection for change tracking. + +- The values of the identifier are stored together with the change log as-is. They are not translated and some data types might +not be formatted per user locale or some requirements, for example, different units of measurement or currencies. ### Displaying Changes @@ -234,7 +255,7 @@ If you want to have a common UI for all changes, you need to expose the change l your own presentation for it as the changes are exposed only as part of the change-tracked entity. This projection must be read-only and shouldn't be writable via OData requests. -The change log is extended with the texts for your entities from the `@title` annotation and the element. Otherwise, the change log contains only the technical names of the entities and the elements. +The change log is extended with the texts coming from your entities' `@title` annotation and the element. Otherwise, the change log contains only the technical names of the entities and the elements. Titles are translated, if they're annotated as translatable. See [Externalizing Texts Bundles](../guides/i18n#localization-i18n) for more information. ## How Changes are Stored @@ -265,18 +286,15 @@ are considered as modified. Each change detected by the change tracking feature In the case of the deeply structured documents, for example, entities with the compositions, the change tracking feature detects the changes across the complete document and stores them in the change log with the metadata reflecting the structure of the change. -For example, given the order and item model from above, if you change values for the tracked elements with -the deep update, for example, the customer name in the order and the quantity of the item, the change log contains -two entries: one for the order and one for the item. The change log entry for the item will also reflect that -the root of the change is an order. +Take the order and item model used previously in this guide as an example. If you change values for the tracked elements with the deep update, for example, the customer name in the order and the quantity of the item, the change log contains two entries: one for the order and one for the item. The change log entry for the item will also reflect that the root of the change is an order. Both changes will be reachable through the association `changes` of the order entity. -:::warning Prefer deep updates for change tracked entities -If you change the values of the `OrderItems` entity directly via an OData request or a CQL statement, the change log contains only one entry for the item and won't be associated with an order. +:::warning Prefer deep updates for change-tracked entities +If you change the values of the `OrderItems` entity directly via an OData request or a CQL statement, the change log contains only one entry for the item. The change won't be associated with an order and will not be reachable through the `changes` association. While the updates of composition targets directly are possible, the change tracking feature does not attempt to resolve the parent entity by itself, it requires that either OData request or a CQL statement provide the reference to the parent, for example, `Orders`. ::: ## Reacting on Changes -You can write an event handler to observe the change log entries. Keep in mind, that the change log entries +You can write an event handler to observe the change log entries. Keep in mind that the change log entries are created for each statement and this event will not be bound to any kind of transaction or a batch operation. First, update the dependency's scope to `compile` in the `srv/pom.xml` file of your service: diff --git a/java/cqn-services/persistence-services.md b/java/cqn-services/persistence-services.md index 6e8f4ba979..ec2c0250f6 100644 --- a/java/cqn-services/persistence-services.md +++ b/java/cqn-services/persistence-services.md @@ -141,7 +141,7 @@ Advise the CDS Compiler to generate _tables without associations_, as associatio #### SQL Optimization Mode -By default, the SAP HANA adapter in CAP Java generates SQL that is optimized for the new [HEX engine](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-performance-guide-for-developers/query-execution-engine-overview) in SAP HANA Cloud. To generate SQL that is compatible with SAP HANA 2.x ([HANA Service](https://help.sap.com/docs/HANA_SERVICE_CF/6a504812672d48ba865f4f4b268a881e/08c6e596b53843ad97ae68c2d2c237bc.html)) and [SAP HANA Cloud](https://www.sap.com/products/technology-platform/hana.html), set the [CDS property](../developing-applications/properties#cds-properties): +By default, the SAP HANA adapter in CAP Java generates SQL that is optimized for the new [HEX engine](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-performance-guide-for-developers/query-execution-engine-overview) in SAP HANA Cloud. To generate SQL that is compatible with SAP HANA 2.x ([HANA Service](https://help.sap.com/docs/HANA_SERVICE_CF/6a504812672d48ba865f4f4b268a881e/08c6e596b53843ad97ae68c2d2c237bc.html)) and [SAP HANA Cloud](https://www.sap.com/products/technology-platform/hana.html), set the (deprecated) [CDS property](../developing-applications/properties#cds-properties): ```yaml cds.sql.hana.optimizationMode: legacy diff --git a/java/cqn-services/remote-services.md b/java/cqn-services/remote-services.md index e9fe3e85f7..58e0bffe8f 100644 --- a/java/cqn-services/remote-services.md +++ b/java/cqn-services/remote-services.md @@ -486,3 +486,7 @@ OAuth2DestinationBuilder .property("name", "my-destination") .build(); ``` + +### Limitations + +Streaming of media content is not supported. This means that elements of an entity annotated with `@Core.MediaType` cannot be accessed via Remote OData Services and will cause an error. It is recommended to exclude these elements in your projections on the imported entity. diff --git a/java/event-handlers/indicating-errors.md b/java/event-handlers/indicating-errors.md index a0cd1fd6d0..9d3ed726fc 100644 --- a/java/event-handlers/indicating-errors.md +++ b/java/event-handlers/indicating-errors.md @@ -45,7 +45,7 @@ The OData adapters turn all exceptions into an OData error response to indicate ## Messages -The [Messages](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/messages/Messages.html) API allows event handlers to add errors, warnings, info, or success messages to the currently processed request. Adding info, warning or success messages doesn't affect the event processing or the transaction. For error messages by default a `ServiceException` is thrown at the end of the `Before` handler phase. You can change this by setting [`cds.errors.combined`](../developing-applications/properties#cds-errors-combined) to `false`. +The [Messages](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/messages/Messages.html) API allows event handlers to add errors, warnings, info, or success messages to the currently processed request. Adding info, warning or success messages doesn't affect the event processing or the transaction. For error messages by default a `ServiceException` is thrown at the end of the `Before` handler phase. The `Messages` interface provides a logger-like API to collect these messages. Additional optional details can be added to the [Message](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/messages/Message.html) using a builder API. You can access the `Messages` API from the Event Context: @@ -82,7 +82,7 @@ messages.throwIfError(); If there are any collected error messages, this method creates a [ServiceException](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/ServiceException.html) from _one_ of these error messages. The OData adapter turns this exception into an OData error response to indicate the error to the client. The remaining error messages are written into the `details` section of the error response. -If the CDS property [`cds.errors.combined`](../developing-applications/properties#cds-errors-combined) is set to true (default), `Messages.throwIfError()` is automatically called at the end of the `Before` handler phase to abort the event processing in case of errors. It is recommended to use the Messages API for validation errors and rely on the framework calling `Messages.throwIfError()` automatically, instead of throwing a `ServiceException`. +`Messages.throwIfError()` is automatically called at the end of the `Before` handler phase to abort the event processing in case of errors. It is recommended to use the Messages API for validation errors and rely on the framework calling `Messages.throwIfError()` automatically, instead of throwing a `ServiceException`. ## Formatting and Localization @@ -127,28 +127,12 @@ throw new ServiceException(ErrorStatuses.BAD_REQUEST, "my.message.key", paramNum CAP Java provides out-of-the-box translation for error messages that originate from input validation annotations such as `@assert...` or `@mandatory` and security annotations `@requires` and `@restrict`. The error messages are optimized for UI scenarios and avoid any technical references to entity names or element names. Message targets are used where appropriate to allow the UI to show the error message next to the affected UI element. -You can enable these translated error messages by setting [cds.errors.defaultTranslations.enabled: true](../developing-applications/properties#cds-errors-defaultTranslations-enabled). +You can disable these translated error messages by setting [cds.errors.defaultTranslations.enabled: false](../developing-applications/properties#cds-errors-defaultTranslations-enabled). -### Exporting the Default Messages - -As of CAP Java 1.10.0, you can extract the available default messages as a resource bundle file for further processing (for example, translation). Therefore, the delivery artifact [cds-services-utils](https://search.maven.org/artifact/com.sap.cds/cds-services-utils) contains a resource bundle `cds-messages-template.properties` with all available error codes and default messages. Application developers can use this template to customize error messages thrown by the CAP Java SDK in the application. - -1. [Download](https://search.maven.org/artifact/com.sap.cds/cds-services-utils) the artifact or get it from the local Maven repository in `~/.m2/repository/com/sap/cds/cds-services-utils//cds-services-utils-.jar`. -1. Extract the file. - ```sh - jar -f cds-services-utils-.jar -x cds-messages-template.properties - ``` - ::: tip - \ is the version of CAP Java you're using in your project. - ::: - -1. Rename the extracted file `cds-messages-template.properties` appropriately (for example, to `cds-messages.properties`) and move it to the resource directory of your application. -1. In your Spring Boot application, you have to register this additional [resource bundle](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.internationalization) accordingly. - -> Now, you're able to customize the stack error messages in your application. - -With new CAP Java versions, there could be also new or changed error messages in the stack. To identify these changes, export `cds-messages-template.properties` from the new CAP Java version and compare it with the previous version using a diff tool. +### Provide custom error messages +By default, CAP Java provides error messages in several languages. If an error message or translation isn't sufficient for an application, it can be overwritten with a custom error message. Applications can provide the new error message under the respective error code in the application's `messages.properties` resource bundle under `src/main/resources`. +To know which error codes and messages are available by default, you can have a look at the Java enumeration `com.sap.cds.services.utils.CdsErrorStatuses` with your favorite IDE. This enumeration shows all available error codes and messages that are used by the CAP Java runtime. ## Target diff --git a/java/fiori-drafts.md b/java/fiori-drafts.md index 8652fa3e90..45539af590 100644 --- a/java/fiori-drafts.md +++ b/java/fiori-drafts.md @@ -94,6 +94,49 @@ The `DRAFT_CREATE` is an internal event that is not triggered by OData requests For more examples, see the [Bookshop sample application](https://github.com/SAP-samples/cloud-cap-samples-java/tree/master/srv/src/main/java/my/bookshop/handlers/AdminServiceHandler.java). +## Validating Drafts + +CAP Java supports persisting (error) messages for draft-enabled entities and providing _state messages_ to the UI5 OData V4 model. +This enables validations of drafts and giving feedback about errors to users faster in the UI. + +To enable this feature, set the following properties in your `.cdsrc.json`: + +::: code-group +```json [.cdsrc.json] +{ + "cdsc": { + "beta": { + "draftMessages": true + } + } +} +``` +::: + +::: warning Document-based URLs +The state messages feature relies on UI5 to use _document URLs_. CAP sets the annotation `@Common.AddressViaNavigationPath` to instruct UI5 to use _document URLs_. This requires UI5 version >= 1.135.0. +::: + +Setting this property adds additional elements to your draft-enabled entities and `DraftAdministrativeData`, which are required to store and serve state messages. + +If this feature is activated, you can observe the following improvements, without any changes to the application code: + +- Error messages for annotation-based validations (for example, `@mandatory` or `@assert...`) already appear while editing the draft. +- Custom validations can now be bound to the `DRAFT_PATCH` event and can write (error) messages. It's ensured that the invalid value is still persisted, as expected by the draft choreography. +- Messages no longer unexpectedly vanish from the UI after editing another field. +- Messages are automatically loaded when reopening a previously edited draft. + +By default, side-effect annotations are generated in the EDMX that instruct UI5 to fetch state messages after every `PATCH` request. +In case more precise side-effect annotations are required you can disable the default side-effect annotation per entity: + +```cds +annotate MyService.MyEntity with @Common.SideEffects #alwaysFetchMessages: null; +``` + +::: warning Requires Schema Update +Enabling draft messages requires a database schema update, as it adds an additional element to `DraftAdministrativeData`. +::: + ## Activating Drafts When you finish editing drafts by pressing the *Save* button, a draft gets activated. That means, either a single `CREATE` or `UPDATE` event is triggered to create or update the active entity with all of its compositions through a deeply structured document. You can register to these events to validate the activated data. diff --git a/java/index.md b/java/index.md index a9b24dd65a..739495dc3a 100644 --- a/java/index.md +++ b/java/index.md @@ -16,8 +16,8 @@ Reference Documentation { .subtitle} - - + + @@ -27,6 +27,7 @@ const { theme } = useData() const { versions } = theme.value.capire import { data as pages } from './index.data.ts' +const enc = s => s.replace(/-/g,'--')
diff --git a/java/messaging.md b/java/messaging.md index 0893f3954b..ff064a2dd0 100644 --- a/java/messaging.md +++ b/java/messaging.md @@ -68,11 +68,11 @@ With the availability of a messaging service, you can now use it to send message MessagingService messagingService; // Sending via the technical API of the messaging service -messagingService.emit("My/Destination/Messaging/Topic", "raw message payload"); +messagingService.emit("My/Topic", Map.of("message", "hello world")); // Sending by emitting a context via CAP service API -TopicMessageEventContext context = TopicMessageEventContext.create("My/Destination/Messaging/Topic"); -context.setData("raw message payload"); +TopicMessageEventContext context = TopicMessageEventContext.create("My/Topic"); +context.setDataMap(Map.of("message", "hello world")); messagingService.emit(context); ``` @@ -99,11 +99,11 @@ To receive messages matching a desired topic from a message broker, you just nee Example: ```java -@On(service = "messaging-name", event = "My/Destination/Messaging/Topic") +@On(service = "messaging-name", event = "My/Topic") public void receiveMyTopic(TopicMessageEventContext context) { // get ID and payload of message String msgId = context.getMessageId(); - String payload = context.getData(); + Map payload = context.getDataMap(); // ... } ``` @@ -305,6 +305,27 @@ cds: Support for SAP Cloud Application Event Hub is provided via [plugin](../plugins/#event-hub). +#### Configuring SAP Integration Suite, Advanced Event Mesh Support : +{ #configuring-advanced-event-mesh-support} + +::: code-group +```xml [srv/pom.xml] + + com.sap.cds + cds-feature-advanced-event-mesh + ${version} + +``` +```yaml [srv/src/main/resources/application.yaml] +cds: + messaging.services: + - name: "messaging-name" + kind: "aem" +``` +::: + +[Support for SAP Integration Suite, advanced event mesh is provided via plugin.](../plugins/#advanced-event-mesh){.learn-more} + #### Configuring Redis PubSub Support : { #configuring-redis-pubsub-support-beta} @@ -483,11 +504,11 @@ cds: routes: - service: "em-instance-01" events: - - "My/Destination/Messaging/A" + - "My/Topic/A" - service: "em-instance-02" events: - - "My/Destination/Messaging/B" - - "My/Destination/Messaging/C*" + - "My/Topic/B" + - "My/Topic/C*" ``` ::: @@ -498,11 +519,11 @@ To use such a configuration, you need to use the composite messaging service for @Qualifier(MessagingService.COMPOSITE_NAME) MessagingService messagingService; -messagingService.emit("My/Destination/Messaging/A", "raw message payload to em-instance-01"); -messagingService.emit("My/Destination/Messaging/B", "raw message payload to em-instance-02"); +messagingService.emit("My/Topic/A", Map.of("hello", "instance 01")); +messagingService.emit("My/Topic/B", Map.of("hello", "instance 02")); ``` -As you can see in the configuration, the usage and routing of two messaging services is defined (`em-instance-01`, and `em-instance-02`), each with different topics that should be routed via the service (for example, topic `My/Destination/Messaging/A` will be sent/received via `em-instance-01`, and topic `My/Destination/Messaging/B` will be sent/received via `em-instance-02`). The composite service uses the routing configuration in order to dispatch messages as well as subscriptions to the appropriate messaging service. As shown in the sample code, you can simply use the composite message service and submit messages to topics as desired. The messages will be routed to according messaging services as defined in the configuration automatically. To change the routing of messages you can simply change the configuration, without the need of changing your code. +As you can see in the configuration, the usage and routing of two messaging services is defined (`em-instance-01`, and `em-instance-02`), each with different topics that should be routed via the service (for example, topic `My/Topic/A` will be sent/received via `em-instance-01`, and topic `My/Topic/B` will be sent/received via `em-instance-02`). The composite service uses the routing configuration in order to dispatch messages as well as subscriptions to the appropriate messaging service. As shown in the sample code, you can simply use the composite message service and submit messages to topics as desired. The messages will be routed to according messaging services as defined in the configuration automatically. To change the routing of messages you can simply change the configuration, without the need of changing your code. ::: tip If you emit messages with a topic to the composite messaging service that isn't defined in its routing configuration, then the delivery will fail. Consider careful review of your configuration, when you start sending/receiving messages from/to new topics. @@ -511,12 +532,12 @@ If you emit messages with a topic to the composite messaging service that isn't Example for receiving messages with a given topic via the composite messaging service: ```java -@On(service = MessagingService.COMPOSITE_NAME, event = "My/Destination/Messaging/A") +@On(service = MessagingService.COMPOSITE_NAME, event = "My/Topic/A") public void receiveA(TopicMessageEventContext context) { ... } -@On(service = MessagingService.COMPOSITE_NAME, event = "My/Destination/Messaging/B") +@On(service = MessagingService.COMPOSITE_NAME, event = "My/Topic/B") public void receiveB(TopicMessageEventContext context) { ... } @@ -616,7 +637,7 @@ cds: @On(service = "messaging-name", event = "my-custom-queue") public void receiveMyCustomQueueMessage(TopicMessageEventContext context) { // access the message as usual - String payload = context.getData(); + Map payload = context.getDataMap(); } ``` @@ -638,7 +659,7 @@ cds: @On(service = "messaging-name") public void receiveMyCustomQueueAllMessages(TopicMessageEventContext context) { // access the message as usual - String payload = context.getData(); + Map payload = context.getDataMap(); // ... } ``` @@ -691,8 +712,9 @@ private void handleError(MessagingErrorEventContext ctx) { // how to access the event context of the raised exception: // ctx.getException().getEventContexts().stream().findFirst().ifPresent(e -> { + // TopicMessageEventContext errorContext = e.as(TopicMessageEventContext.class); // String event = e.getEvent()); - // String payload = e.get("data")); + // Map payload = errorContext.getDataMap(); // }); ctx.setResult(true); // acknowledge @@ -700,7 +722,7 @@ private void handleError(MessagingErrorEventContext ctx) { } ``` -In a multi-tenant setup with several microservices, messages of a tenant not yet subscribed to the own microservice would be already received from the message queue. In this case, the message cannot be processed for the tenant because the tenant context is not yet available. By default, the standard error handler still acknowledges the message to prevent it from getting stuck in the message sequence. To change this behavior, the custom error handler from the example above can be extended by checking the exception type of the unknown tenant. +In a multi-tenant setup with several microservices, messages of a tenant not yet subscribed to the own microservice would be already received from the message queue. In this case, the message cannot be processed for the tenant because the tenant context is not yet available. By default, the standard error handler still acknowledges the message to prevent it from getting stuck in the message sequence. To change this behavior, the custom error handler from the example above can be extended by checking the exception type of the unknown tenant. ```java @@ -712,15 +734,15 @@ private void handleError(MessagingErrorEventContext ctx) { errorCode.equals(CdsErrorStatuses.INVALID_DATA_FORMAT.getCodeString())) { // error handling for infrastructure error ctx.setResult(false); // no acknowledgement - + } else if (errorCode.equals(CdsErrorStatuses.TENANT_NOT_EXISTS.getCodeString())) { // error handling for unknown tenant context - + // tenant of the received message String tenant = ctx.getTenant(); // received message - Map headers = ctx.getMessageHeaders(); + Map headers = ctx.getMessageHeaders(); Map message = ctx.getMessageData(); ctx.setResult(true); // acknowledge @@ -729,8 +751,9 @@ private void handleError(MessagingErrorEventContext ctx) { // how to access the event context of the raised exception: // ctx.getException().getEventContexts().stream().findFirst().ifPresent(e -> { + // TopicMessageEventContext errorContext = e.as(TopicMessageEventContext.class); // String event = e.getEvent()); - // String payload = e.get("data")); + // Map payload = errorContext.getDataMap(); // }); ctx.setResult(true); // acknowledge @@ -768,7 +791,7 @@ If you want to consume the message purely locally, and prevent the message from You can register your handler with a higher priority than the default handler like this: ```java -@On(service = "messaging-name", event = "My/Destination/Messaging/Topic") +@On(service = "messaging-name", event = "My/Topic") @HandlerOrder(HandlerOrder.EARLY) public void receiveMyTopic(TopicMessageEventContext context) { // Check if message is outgoing to message broker, or incoming from message broker @@ -808,99 +831,20 @@ When using SAP Event Mesh, the placeholder `$namespace` can be used to dynamical Besides these kinds of topic manipulations, additional topic manipulations might occur, depending on the used message broker or the chosen format of the event message. -### Enhanced Messages Representation - -The configuration property `structured` determines if messages are represented as a plain String (`false`) or always structured as two separate maps, representing data and headers (`true`). Setting this property enables handling of message headers, like `cloudevents` headers, separately from the message itself. This works for all messaging brokers supported by CAP. If using a message broker that supports native headers, for example Kafka, the headers are separated from the business data. On incoming messages the flag determines the internal representation of the message either as a plain string or two maps of message data and message headers. Having header data separated, avoids adding extra information or metadata as part of the business data when sending them to the message broker. Additionally the header data is clearly separated on the consumer side, because they provided by different data and headers maps. - -The default value for the configuration property `structured` is `true`. - -Configuration example: -::: code-group -```yaml [srv/src/main/resources/application.yaml] -cds: - messaging.services: - - name: "messaging-name" - kind: "enterprise-messaging" - structured: true -``` -::: -#### Emitting Events - -The interface `MessagingService` provides a new method for emitting events with a data map and a headers map: - -```java -void emit(String topic, Map data, Map headers); -``` - -This method takes a (`cloudevents`) message, separated into data and headers and sends it to the specified topic of this message broker. It produces the same final message, regardless of the structured flag. Usually data and headers are combined into a final JSON string message following the rule: `{...headers, data: data}`. Brokers that natively support headers, for example Kafka, are able to separate headers from data, when using this method. - -```java -String topic; -MessagingService messagingService; - -messagingService.emit(topic, Map.of("firstname", "John", "lastname", "Doe"), Map.of("timestamp", Instant.now())); -``` - -The semantics of the method `MessagingService.emit(String topic, String message)` has been changed depending on the structured flag: If the service is not configured with the structured flag (default), the message is sent to the specified topic of this message broker as is. If the service is configured with the structured flag, the message string is converted into a map following the rule: `{message: message}`. The map is then interpreted as data map and passed to `MessagingService.emit(String topic, Map dataMap)`. Usually this results in a final message like: `{data: {message: message}}`. - -Example: - -```java -String topic; -MessagingService messagingService; - -messagingService.emit(topic, "hello world"); -``` +### Messages Representation +{#enhanced-messages-representation} -If the service is not configured with the structured flag, the message is sent as is and on the consumer side `TopicMessageEventContext.getData()` returns: - -```txt -hello world -``` - -If the service is configured with the structured flag, the message is converted to a map and on the consumer side `TopicMessageEventContext.getData()` returns: - -```json -{"data": {"message": "hello world"}} -``` +Messages are provided as two separate `Map` objects, representing data and headers. This enables handling of message headers, like `cloudevents` headers, separately from the message data itself. If using a message broker that supports native headers, for example Kafka, the headers are separated from the business data in the message broker. For message brokers that don't natively support headers, headers and data are merged into a combined structure by following the rule `{...headers, data: data}`. -#### Handling events - -The structured flag of the consumer determines how the event payload is provided by the event context. - -If set to `false`, the event payload can be accessed using `getData()` as a string; `getDataMap()` and `getHeadersMap()` return `null`: - -```java -@On(event = "myEvent") -public void handleMyEvent(EventContext context) { - TopicMessageEventContext ctx = context.as(TopicMessageEventContext.class); - String data = ctx.getData(); - - // ... -} -``` - -If set to `true`, the event payload can be accessed using `getDataMap()` and `getHeadersMap()`; `getData()` returns null: - -```java -@On(event = "myEvent") -public void handleMyEvent(EventContext context) { - TopicMessageEventContext ctx = context.as(TopicMessageEventContext.class); - Map data = ctx.getDataMap(); - Map headers = ctx.getHeadersMap(); - - // ... -} -``` +Message data is always structured and when sent to message brokers serialized to JSON. In case a message data is received, that is not a JSON string and can't be parsed as a `Map`, the plain message data is wrapped into a `Map` structure with key `message`. ::: tip -Handling of CDS-defined events is independent of value of the `structured` property. +[Events declared in the CDS model](#cds-declared-events) always result in a structured message data. ::: - ### CloudEvents -CAP is able to produce event messages compatible with the [CloudEvents](https://cloudevents.io/) standard in JSON format. To enable this feature, set the configuration parameter `format` of the messaging service to `cloudevents`, for example, like: +CAP is able to produce event messages compatible with the [CloudEvents](https://cloudevents.io/) standard. To enable this feature, set the configuration parameter `format` of the messaging service to `cloudevents`, for example, like: Excerpt from _application.yaml_: ::: code-group @@ -913,11 +857,11 @@ cds: format: "cloudevents" ``` ::: -With this setting, basic header fields (like `type`, `source`, `id`, `datacontenttype`, `specversion`, `time`) of the JSON-based message format will be populated with sensible data (if they have not been set manually before). The event name will be used as-is (without prefixing or any other modifications) as `type` and set in the according CloudEvents header field. +With this setting, basic header fields (like `type`, `source`, `id`, `datacontenttype`, `specversion`, `time`) will be populated with sensible data (if they have not been set manually before). The event name will be used as-is (without prefixing or any other modifications) as `type` and set in the according CloudEvents header field. When using CloudEvents format with SAP Event Mesh, the following default prefixing of topics will be applied (if not manually declared differently): Default for `publishPrefix` is set to `$namespace/ce/` and default for `subscribePrefix` is set to `+/+/+/ce/`. Make sure that these prefixes are allowed topic prefixes in your SAP Event Mesh service configuration (especially its topic rules section). -When using a CAP service that has [events declared in its CDS model](#cds-declared-events), then the event's payload structure will automatically be embedded in the `data` attribute of a valid JSON-based CloudEvents message. +The message data will automatically be embedded in the `data` attribute of the CloudEvents message. ::: tip Headers of the CloudEvents message can be accessed using the EventContext generated for this event by using its generic `get(String)` API. @@ -935,8 +879,6 @@ private void ratingChanged(ReviewedContext context) { } ``` -When using a CAP messaging service directly to emit the raw message payload as a String, please make sure to emit a valid JSON object representation in this String that has the message payload embedded in the `data` attribute (for example `messagingService.emit("sap.cap.reviews.v1.ReviewService.changed.v1", "{"data":{"subject":"4711","rating":3.6}}");`). Then all missing header fields that are required for a valid CloudEvents message will be extended. If you emit a String that is not a valid JSON, then the message cannot be extended and the String will be emitted as-is as the event message content. - [Learn more about **CloudEvents**.](../guides/messaging/#cloudevents){.learn-more} diff --git a/java/migration.md b/java/migration.md index 66772f9491..1697c152b1 100644 --- a/java/migration.md +++ b/java/migration.md @@ -23,6 +23,116 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ [[toc]] +## CAP Java 3.10 to CAP Java 4.0 { #three-to-four } + +### New License + +The license of CAP Java 4.0 has changed and it's released under the new [SAP Developer License Agreement for CAP](https://cap.cloud.sap/resources/license/developer-license-3_2_CAP.txt). + +### Minimum Versions + +CAP Java 4.0 increased some minimum required versions: + +| Dependency | Minimum Version | +| --- | --- | +| @sap/cds-dk | ^8 | + +CAP Java 4.0 no longer supports @sap/cds-dk ^7. + +### Removed feature `cds-feature-event-hub` + +The feature `cds-feature-event-hub` has been removed from CAP Java. The successor of this feature is the [CDS Plugin for SAP Cloud Application Event Hub](https://github.com/cap-java/cds-feature-event-hub). This new CAP Java Plugin is published independently of CAP Java and is open source. It however has the same Maven group ID and artifact ID as the previous CAP Java feature and starts versioning with `4.0.0`. + +### Changes in goal `generate` of the `cds-maven-plugin` + +1. Removed already deprecated properties: +- sharedInterfaces +- uniqueEventContexts + +2. Deprecated properties and marked for removal: +- eventContext +- cqnService + +3. Changed default value of properties: +- excludes: "localized.**" -> null + +### Removed unstructured messages from MessagingService { #removed-unstructured } + +The deprecated `cds.messaging.services..structured` property, which allowed to handle messages as (unstructured) plain strings has now been completely removed. +Messaging services always represent data in a structured way by using a `Map`. Headers are represented in a separate `Map`. + +With this change the following deprecated methods have been removed: +- `void MessagingService.emit(String, String)` +- `String TopicMessageEventContext.getData()` + +When publishing a unstructured String-based message, wrap the message into a Map: + +```java +// instead of +messagingService.emit("myTopic", "unstructured message"); +// use +messagingService.emit("myTopic", Map.of("message", "unstructured message")); // [!code focus] +``` + +In case you receive a unstructured String-based message from a message broker, it is also wrapped into a structured message with a `message` property: + +```java +@On(event = "myTopic") +void handleMyTopic(TopicMessageEventContext context) { + // instead of + String message = context.getData(); + // use + String message = (String) context.getDataMap().get("message"); // [!code focus] +} +``` + +This change has no effect on CDS modelled events, as these have always been structured. + +### Adjusted Property Defaults + +Some property defaults have been adjusted: + +| Property | Old Value | New Value | Explanation | +| --- | --- | --- | --- | +| `cds.security.authorization.deep.enabled` | false | true | [Deep Authorization](./security#deep-auth) is now enabled by default. | +| `cds.security.authorization.instanceBased.rejectSelectedUnauthorizedEntity.enabled` | false | true | Requests that violate instance-based authorization conditions now fail with 403, instead of 404. | +| `cds.security.authorization.instanceBased.checkInputData.enabled` | false | true | [Authorization Checks On Input Data](./security#input-data-auth) are now enabled by default. | +| `cds.errors.defaultTranslations.enabled` | false | true | [Translations for Validation Error Messages](./event-handlers/indicating-errors#ootb-translated-messages) are now enabled by default. | +| `cds.sql.runtimeView.mode` | resolve | cte | [Runtime views](./working-with-cql/query-execution#runtimeviews) are now by default translated into Common Table Expressions | + +### Deprecated Properties + +The following properties have been deprecated and might be removed in a future major version: + +- `cds.errors.combined` +- `cds.sql.hana.optimizationMode` +- `cds.outbox.services..storeLastError.enabled` + +The functionality provided by these properties is enabled by default and there is no reason to switch these off. + +### Removed Properties + +The following table gives an overview about the removed properties: + +| Removed Property | Replacement / Explanation | +| --- | --- | +| `cds.messaging.services.`
`.structured` | [Use structured messages.](#removed-unstructured) | +| `cds.security.authorization.`
`emptyAttributeValuesAreRestricted` | [Use conditions with explicit checks for empty attributes.](../guides/security/authorization#user-attrs) | +| `cds.odataV4.serializer.buffered` | OData V4 responses are now always streamed while serialized. | +| `cds.environment.k8s` | Use service bindings from SAP BTP Operator on Kyma. | +| `cds.multiTenancy.subscriptionManager.`
`clientCertificateHeader` | `cds.security.authentication.`
`clientCertificateHeader` | +| `cds.multiTenancy.security.`
`internalUserAccess.enabled` | `cds.security.authentication.`
`internalUserAccess.enabled` | + +### Removed Java APIs + +- Removed deprecated classes: + - `com.sap.cds.ql.cqn.CqnSearchPredicate`, use `CqnSearchTermPredicate` instead + +- Removed deprecated methods: + - `com.sap.cds.ql.cqn.Modifier.search(String term)`, use `searchTerm(CqnSearchTermPredicate)` instead + - `com.sap.cds.ql.cqn.Modifier.selectListValue(Value value, String alias)`, use `selectListValue(SelectableValue value, String alias)` instead + - `com.sap.cds.ql.SelectableValue.withoutAlias()`, use `as(String alias)` instead + ## CAP Java 2.10 to CAP Java 3.0 { #two-to-three } ### Minimum Versions @@ -174,7 +284,7 @@ Some property defaults have been adjusted: | `cds.sql.hana.optimizationMode` | `legacy` | `hex` | SQL for SAP HANA is optimized for the HEX engine. | | `cds.odataV4.lazyI18n.enabled` | `null` | `true` | Lazy localization is now enabled by default in multitenant scenarios. | | `cds.auditLog.personalData.`
`throwOnMissingDataSubject` | `false` | `true` | Raise errors for incomplete personal data annotations by default. | -| `cds.messaging.services..structured` | `false` | `true` | [Enhanced message representation](./messaging.md#enhanced-messages-representation) is now enabled by default. | +| `cds.messaging.services..structured` | `false` | `true` | [Structured message representation](./messaging.md#messages-representation) is now enabled by default. | ### Adjusted Property Behavior @@ -309,12 +419,20 @@ Since version 1.27 CAP Java is running with Spring Boot 2.7, which uses Spring S Make sure that all libraries used in your project are either compatible with Spring Boot 3 / Jakarta EE 10 or alternatively offer a new version which you can adopt. -CAP Java 2.0 itself requires updated [dependency versions](./versions#dependencies-version-2) of: -- `@sap/cds-dk` -- `@sap/cds-compiler` -- XSUAA library -- SAP Cloud SDK -- Java Logging (replace `cf-java-logging-support-servlet` with `cf-java-logging-support-servlet-jakarta`) +CAP Java 2.0 itself requires updated minimum dependency versions: + +| Dependency | Minimum Version | Recommended Version | +| --- | --- | --- | +| JDK | 17 | 21 | +| Maven | 3.5.0 | 3.9.8 | +| @sap/cds-dk | 6 | 7 | +| @sap/cds-compiler | 3 | 4 | +| Spring Boot | 3.0 | latest | +| XSUAA | 3.0 | latest | +| SAP Cloud SDK | 4.24 | latest | +| Java Logging | 3.7 | latest | + +Java Logging (replace `cf-java-logging-support-servlet` with `cf-java-logging-support-servlet-jakarta`) ::: warning The Cloud SDK BOM `sdk-bom` manages XSUAA until version 2.x, which isn't compatible with CAP Java 2.x. diff --git a/java/operating-applications/observability.md b/java/operating-applications/observability.md index 9e7e92c5d2..863d47c9f3 100644 --- a/java/operating-applications/observability.md +++ b/java/operating-applications/observability.md @@ -166,6 +166,34 @@ Most of the loggers are used on DEBUG level by default as they produce quite som Spring comes with its own [standard logger groups](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/html/boot-features-logging.html#boot-features-custom-log-groups). For instance, `web` is useful to track HTTP requests. However, HTTP access logs gathered by the Cloud Foundry platform router are also available in the application log. ::: +#### Log CDS Configuration + +Upon start-up, you can get an overview of the configured CDS properties. Use this feature to: +- list all accepted CDS properties and double-check the running configuration +- check for warnings of usage of deprecated properties +- check for warnings of usage of undocumented properties + +Please note that secrets are masked. + +Turn it on by setting the log level com.sap.cds.properties = DEBUG. + + +::: details Sample output: + +```sh +... DEBUG ... com.sap.cds.properties : 'cds.dataSource.autoConfig.enabled': 'false' (default: 'true') +... DEBUG ... com.sap.cds.properties : 'cds.dataSource.embedded': 'true' (default: 'false') +... WARN ... com.sap.cds.properties : 'cds.security.authorization.emptyAttributeValuesAreRestricted': 'false' (default: 'true', deprecated, not documented) +... DEBUG ... com.sap.cds.properties : 'cds.security.mock.users.admin.name': 'admin' +... DEBUG ... com.sap.cds.properties : 'cds.security.mock.users.admin.password': '***' (sensitive) +... DEBUG ... com.sap.cds.properties : 'cds.security.mock.users.admin.roles[0]': 'admin' +... DEBUG ... com.sap.cds.properties : 'cds.security.mock.users.admin.roles[1]': 'cds.Developer' +... DEBUG ... com.sap.cds.properties : 'cds.security.mock.users.admin.attributes.businessPartner[0]': '10401010' +... DEBUG ... com.sap.cds.properties : 'cds.odataV4.endpoint.path': '/api' (default: '/odata/v4') +... DEBUG ... com.sap.cds.properties : 'cds.errors.defaultTranslations.enabled': 'true' (default: 'false') +``` +::: + ### Logging Service { #logging-service} The SAP BTP platform offers the [SAP Application Logging service for SAP BTP](https://help.sap.com/docs/r/product/APPLICATION_LOGGING) diff --git a/java/outbox.md b/java/outbox.md index a031f62993..7a400f9830 100644 --- a/java/outbox.md +++ b/java/outbox.md @@ -70,20 +70,18 @@ cds: services: DefaultOutboxOrdered: maxAttempts: 10 - storeLastError: true # ordered: true DefaultOutboxUnordered: maxAttempts: 10 - storeLastError: true # ordered: false ``` ::: You have the following configuration options: - `maxAttempts` (default `10`): The number of unsuccessful emits until the message is ignored. It still remains in the database table. -- `storeLastError` (default `true`): If this flag is enabled, the last error that occurred, when trying to emit the message - of an entry, is stored. The error is stored in the element `lastError` of the entity `cds.outbox.Messages`. - `ordered` (default `true`): If this flag is enabled, the outbox instance processes the entries in the order they have been submitted to it. Otherwise, the outbox may process entries randomly and in parallel, by leveraging outbox processors running in multiple application instances. This option can't be changed for the default persistent outboxes. +The persistent outbox stores the last error that occurred, when trying to emit the message of an entry. The error is stored in the element `lastError` of the entity `cds.outbox.Messages`. + ### Configuring Custom Outboxes { #custom-outboxes} Custom persistent outboxes can be configured using the `cds.outbox.services` section, for example in the _application.yaml_: @@ -94,10 +92,8 @@ cds: services: MyCustomOutbox: maxAttempts: 5 - storeLastError: false MyOtherCustomOutbox: maxAttempts: 10 - storeLastError: true ``` ::: Afterward you can access the outbox instances from the service catalog: @@ -160,7 +156,7 @@ To do this, the Maven `resource.filtering` configuration in the `srv/pom.xml` mu To be sure that the deployment version has been set correctly, you can find a log entry at startup that shows the configured version: ```bash -2024-12-19T11:21:33.253+01:00 INFO 3420 --- [main] cds.serviceces.impl.utils.BuildInfo : application.deployment.version: 1.0.0-SNAPSHOT +2024-12-19T11:21:33.253+01:00 INFO 3420 --- [main] cds.services.impl.utils.BuildInfo : application.deployment.version: 1.0.0-SNAPSHOT ``` And finally, if for some reason you don't want to use a version check for a particular outbox collector, you can switch it off via the outbox configuration [cds.outbox.services.MyCustomOutbox.checkVersion: false](../java/developing-applications/properties#cds-outbox-services--checkVersion). diff --git a/java/security.md b/java/security.md index 5de271c902..93bf887709 100644 --- a/java/security.md +++ b/java/security.md @@ -65,9 +65,9 @@ Choose an appropriate XSUAA service plan to fit the requirements. For instance, #### Proof-Of-Possession for IAS { #proof-of-possession} -Proof-Of-Possession is a technique for additional security where a JWT token is **bound** to a particular OAuth client for which the token was issued. On BTP, Proof-Of-Possession is supported by IAS and can be used by a CAP Java application. +Proof-Of-Possession is a technique for additional security where a JWT token is **bound** to a particular OAuth client for which the token was issued. On BTP, Proof-Of-Possession is supported by IAS and can be used by a CAP Java application. -Typically, a caller of a CAP application provides a JWT token issued by IAS to authenticate a request. With Proof-Of-Possession in place, a mutual TLS (mTLS) tunnel is established between the caller and your CAP application in addition to the JWT token. Clients calling your CAP application need to send the certificate provided by their `identity` service instance in addition to the IAS token. +Typically, a caller of a CAP application provides a JWT token issued by IAS to authenticate a request. With Proof-Of-Possession in place, a mutual TLS (mTLS) tunnel is established between the caller and your CAP application in addition to the JWT token. Clients calling your CAP application need to send the certificate provided by their `identity` service instance in addition to the IAS token. On Cloud Foundry, the CAP application needs to be exposed under an additional route which accepts client certificates and forwards them to the application as `X-Forwarded-Client-Cert` header (for example, the `.cert.cfapps.` domain). @@ -106,11 +106,10 @@ service BooksService @(requires: 'any') { | `/BooksService` | | | `/BooksService/$metadata` | | | `/BooksService/Books` | | -| `/BooksService/Reviews` | 1 | +| `/BooksService/Reviews` | | | `/BooksService/Orders` | | -> 1 Since version 1.25.0 ::: tip For multitenant applications, it's required to authenticate all endpoints as the tenant information is essential for processing the request. ::: @@ -242,6 +241,13 @@ In the example, the `CustomUserInfoProvider` defines an overlay on the default X ### Mock User Authentication with Spring Boot { #mock-users} By default, CAP Java creates a security configuration, which accepts _mock users_ for test purposes. + +::: details Requirement + +Mock users are only initialized if the `org.springframework.boot:spring-boot-starter-security` dependency is present in the `pom.xml` file of your service. + +::: + #### Preconfigured Mock Users For convenience, the runtime creates default mock users reflecting the [pseudo roles](../guides/security/authorization#pseudo-roles). They are named `authenticated`, `system` and `privileged` and can be used with an empty password. For instance, requests sent during a Spring MVC unit test with annotation `@WithMockUser("authenticated")` will pass authorization checks that require `authenticated-user`. The privileged user will pass any authorization checks. `cds.security.mock.defaultUsers = false` prevents the creation of default mock users at startup. @@ -338,33 +344,110 @@ The mock user `Alice` is assigned to the mock tenant `CrazyCars` for which the f CAP Java SDK provides a comprehensive authorization service. By defining authorization rules declaratively via annotations in your CDS model, the runtime enforces authorization of the requests in a generic manner. Two different levels of authorization can be distinguished: -- [Role-based authorization](#role-based-auth) allows to restrict resource access depending on user roles. -- [Instance-based authorization](#instance-based-auth) allows to define user privileges even on entity instance level, that is, a user can be restricted to instances that fulfill a certain condition. +- [Role-based authorization](../guides/security/authorization#requires) allows to restrict resource access depending on user roles. +- [Instance-based authorization](../guides/security/authorization#instance-based-auth) allows to define user privileges even on entity instance level, that is, a user can be restricted to instances that fulfill a certain condition. It's recommended to configure authorization declaratively in the CDS model. If necessary, custom implementations can be built on the [Authorization API](#enforcement-api). A precise description of the general authorization capabilities in CAP can be found in the [Authorization](../guides/security/authorization) guide. -### Role-Based Authorization { #role-based-auth} +In addition to standard authorization, CAP Java provides additional out of the box capabilities to reduce custom code: -Use CDS annotation `@requires` to specify in the CDS model which role a user requires to access the annotated CDS resources such as services, entities, actions, and functions (see [Restricting Roles with @requires](../guides/security/authorization#requires)). The generic authorization handler of the runtime rejects all requests with response code 403 that don't match the accepted roles. -More specific access control is provided by the `@restrict` annotation, which allows to combine roles with the allowed set of events. For instance, this helps to distinguish between users that may only read an entity from those who are allowed to edit. See section [Control Access with @restrict](../guides/security/authorization#restrict-annotation) to find details about the possibilities. +#### Deep Authorization { #deep-auth} -### Instance-Based Authorization { #instance-based-auth} +Queries to Application Services are not only authorized by the target entity which has a `@restrict` or `@requires` annotation, but also for all __associated entities__ that are used in the statement. +__Compositions__ are neither checked nor extended with additional filters. +For instance, consider the following model: + +```cds +@(restrict: [{ grant: 'READ', to: 'Manager' }]) +entity Books {...} + +@(restrict: [{ grant: 'READ', to: 'Manager' }]) +entity Orders { + key ID: String; + items: Composition of many { + key book: Association to Books; + quantity: Integer; + } +} +``` -Whereas role-based authorization applies to whole entities only, [Instance-Based Authorization](../guides/security/authorization#instance-based-auth) allows to add more specific conditions that apply on entity instance level and depend on the attributes that are assigned to the request user. A typical use case is to narrow down the set of visible entity instances depending on user properties (for example, `CountryCode` or `Department`). Instance-based authorization is also basis for [domain-driven authorizations](../guides/security/authorization#domain-driven-authorization) built on more complex model constraints. +For the following OData request `GET Orders(ID='1')/items?$expand=book`, authorizations for `Orders` and for `Books` are checked. +If the entity `Books` has a `where` clause for [instance-based authorization](/java/security#instance-based-auth), +it will be added as a filter to the sub-request with the expand. + +Custom CQL statements submitted to the [Application Service](/java/cqn-services/application-services) instances +are also authorized by the same rules including the path expressions and subqueries used in them. + +For example, the following statement checks role-based authorizations for both `Orders` and `Books`, +because the association to `Books` is used in the select list. + +```java +Select.from(Orders_.class, + f -> f.filter(o -> o.ID().eq("1")).items()) + .columns(c -> c.book().title()); +``` + +For modification statements with associated entities used in infix filters or where clauses, +role-based authorizations are checked as well. Associated entities require `READ` authorization, in contrast to the target of the statement itself. + +The following statement requires `UPDATE` authorization on `Orders` and `READ` authorization on `Books` +because an association from `Orders.items` to the book is used in the where condition. + +```java +Update.entity(Orders_.class, f -> f.filter(o -> o.ID().eq("1")).items()) + .data("quantity", 2) + .where(t -> t.book().ID().eq(1)); +``` +:::tip Modification of Statements +Be careful when you modify or extend the statements in custom handlers. +Make sure you keep the filters for authorization. +::: + +Starting with CAP Java `4.0`, deep authorization is on by default. +It can be disabled by setting cds.security.authorization.deep.enabled: false. + +[Learn more about `@restrict.where` in the instance-based authorization guide.](/guides/security/authorization#instance-based-auth){.learn-more} + +#### Forbidden on Rejected Entity Selection { #reject-403 } + +Entities that have an instance-based authorization condition, that is [`@restrict.where`](/guides/security/authorization#restrict-annotation), +are guarded by the CAP Java runtime by adding a filter condition to the DB query **excluding not matching instances from the result**. +Hence, if the user isn't authorized to query an entity, requests targeting a *single* entity return *404 - Not Found* response and not *403 - Forbidden*. + +To allow the UI to distinguish between *not found* and *forbidden*, CAP Java can detect this situation and rejects`PATCH` and `DELETE` requests to single entities with forbidden accordingly. +The additional authorization check may affect performance. + +::: warning +To avoid to disclosure the existence of such entities to unauthorized users, make sure that the key is not efficiently enumerable or add custom code to overrule the default behaviour otherwise. +::: + +Starting with CAP Java `4.0`, the reject behaviour is on by default. +It can be disabled by setting cds.security.authorization.instance-based.reject-selected-unauthorized-entity.enabled: false. + +[Learn more about `@restrict.where` in the instance-based authorization guide.](/guides/security/authorization#instance-based-auth){.learn-more} + +#### Authorization Checks On Input Data { #input-data-auth } + +Input data of `CREATE` and `UPDATE` events is also validated with regards to instance-based authorization conditions. +Invalid input that does not meet the condition is rejected with response code `400`. + +Let's assume an entity `Orders` which restricts access to users classified by assigned accounting areas: + +```cds +annotate Orders with @(restrict: [ + { grant: '*', where: 'accountingArea = $user.accountingAreas' } ]); +``` - +A user with accounting areas `[Development, Research]` is not able to send an `UPDATE` request, that changes `accountingArea` from `Research` or `Development` to `CarFleet`, for example. +Note that the `UPDATE` on instances _not matching the request user's accounting areas_ (for example, `CarFleet`) are rejected by standard instance-based authorization checks. -#### Current Limitations +Starting with CAP Java `4.0`, deep authorization is on by default. +It can be disabled by setting cds.security.authorization.instanceBased.checkInputData: false. -The CAP Java SDK translates the `where`-condition in the `@restrict` annotation to a predicate, which is appended to the `CQN` statement of the request. This applies only to `READ`,`UPDATE`, and `DELETE` events. In the current version, the following limitations apply: -* For `UPDATE` and `DELETE` events no paths in the `where`-condition are supported. -* Paths in `where`-conditions with `to-many` associations or compositions can only be used with an [`exists` predicate](../guides/security/authorization#exists-predicate). -* `UPDATE` and `DELETE` requests that address instances that aren't covered by the condition (for example, which aren't visible) aren't rejected, but work on the limited set of instances as expected. -As a workaround for the limitations with paths in `where`-conditions, you may consider using the `exists` predicate instead. +[Learn more about `@restrict.where` in the instance-based authorization guide.](/guides/security/authorization#instance-based-auth){.learn-more} -CAP Java SDK supports [User Attribute Values](../guides/security/authorization#user-attrs) that can be referred by `$user.` in the where-clause of the `@restrict`-annotation. Currently, only comparison predicates with user attribute values are supported (`<,<=,=,=>,>`). Note that generally a user attribute represents an *array of strings* and *not* a single value. A given value list `[code1, code2]` for `$user.code` in predicate `$user.code = Code` evaluates to `(code1 = Code) or (code2 = Code)` in the resulting statement. ### Enforcement API & Custom Handlers { #enforcement-api} diff --git a/java/versions.md b/java/versions.md index f3dc700e0a..25073edc17 100644 --- a/java/versions.md +++ b/java/versions.md @@ -61,39 +61,33 @@ CAP Java uses various dependencies that are also used by the applications themse If the applications decide to manage the versions of these dependencies, it's helpful to know the minimum versions of these dependencies that CAP Java requires. The following table lists these minimum versions for various common dependencies, based on the latest release: - -#### Active Version 3.x { #dependencies-version-3 } +#### Active Version 4.x { #dependencies-version-4 } | Dependency | Minimum Version | Recommended Version | | --- | --- | --- | | JDK | 17 | 21 | -| Maven | 3.6.3 | 3.9.8 | -| @sap/cds-dk | 7 | latest | -| @sap/cds-compiler | 4 | latest | +| Maven | 3.6.3 | 3.9.9 | +| @sap/cds-dk | 8 | latest | +| @sap/cds-compiler | 5 | latest | | Spring Boot | 3.0 | latest | -| XSUAA | 3.0 | latest | +| XSUAA | 3.1 | latest | | SAP Cloud SDK | 5.9 | latest | | Java Logging | 3.7 | latest | +| Node.js | 20 | 22 | -#### Maintenance Version 2.10.x { #dependencies-version-2 } +#### Maintenance Version 3.10.x { #dependencies-version-3 } | Dependency | Minimum Version | Recommended Version | | --- | --- | --- | | JDK | 17 | 21 | -| Maven | 3.5.0 | 3.9.8 | -| @sap/cds-dk | 6 | 7 | -| @sap/cds-compiler | 3 | 4 | +| Maven | 3.6.3 | 3.9.9 | +| @sap/cds-dk | 7 | latest | +| @sap/cds-compiler | 4 | latest | | Spring Boot | 3.0 | latest | | XSUAA | 3.0 | latest | -| SAP Cloud SDK | 4.24 | latest | +| SAP Cloud SDK | 5.9 | latest | | Java Logging | 3.7 | latest | - -::: warning -The Cloud SDK BOM `sdk-bom` manages XSUAA until version 2.x, which isn't compatible with CAP Java 2.x. -You have two options: -* Replace `sdk-bom` with `sdk-modules-bom`, which [manages all Cloud SDK dependencies but not the transitive dependencies.](https://sap.github.io/cloud-sdk/docs/java/guides/manage-dependencies#the-sap-cloud-sdk-bill-of-material) -* Or, add [dependency management for XSUAA](https://github.com/SAP/cloud-security-services-integration-library#installation) before Cloud SDK's `sdk-bom`. -::: +| Node.js | 18 | 20 | ### Consistent Versions diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md index e7f61c100e..264a0347e5 100644 --- a/java/working-with-cql/query-api.md +++ b/java/working-with-cql/query-api.md @@ -1474,8 +1474,14 @@ These comparison operators are supported: + + + + - + - + - + - - + @@ -1564,9 +1560,21 @@ GT + + + + + + @@ -1580,9 +1588,7 @@ LT - + @@ -1596,7 +1602,7 @@ LE -
Operator +
+ CAP Java + - Predicate + CDL Description @@ -1488,11 +1494,9 @@ These comparison operators are supported:
EQ= -EQ - - Test if this value equals a given value. NULL values might be treated as unknown resulting in a three-valued logic as in SQL. + Test if this value equals a given value. NULL values might be treated as unknown resulting in a three-valued logic as in SQL. Select.from("bookshop.Books") @@ -1502,11 +1506,9 @@ EQ
NE<> -NE - - Test if this value is NOT equal to a given value. NULL values might be treated as unknown resulting in a three-valued logic as in SQL. + Test if this value is NOT equal to a given value. NULL values might be treated as unknown resulting in a three-valued logic as in SQL. Select.from("bookshop.Books") @@ -1516,11 +1518,9 @@ NE
IS== -IS - - Test if this value equals a given value. NULL values are treated as any other value. + Test if this value equals a given value. NULL values are treated as any other value (Boolean logic). @@ -1532,11 +1532,9 @@ IS
IS NOT!= -IS NOT - - Test if this value is NOT equal to a given value. NULL values are treated as any other value. + Test if this value is NOT equal to a given value. NULL values are treated as any other value (Boolean logic). @@ -1548,9 +1546,7 @@ IS NOT
-GT -GT> Test if this value is greater than a given value.
GE>= -LT + Test if this value is greater than or equal to a given value. + +Select.from("bookshop.Books") + .where(b -> b.get("stock") + .ge(5)); + +
LT< Test if this value is less than a given value.
-LE -LE<= Test if this value is less than or equal to a given value.
+ BETWEEN diff --git a/java/working-with-cql/query-execution.md b/java/working-with-cql/query-execution.md index 8f206e1764..af60b19a27 100644 --- a/java/working-with-cql/query-execution.md +++ b/java/working-with-cql/query-execution.md @@ -400,11 +400,17 @@ The `lock()` method has an optional parameter `timeout` that indicates the maxim The parameter `mode` allows to specify whether an `EXCLUSIVE` or a `SHARED` lock should be set. -## Runtime Views { #runtimeviews} +## Runtime Views { #runtimeviews } -The CDS compiler generates [SQL DDL](../../guides/databases?impl-variant=java#generating-sql-ddl) statements based on your CDS model, which include SQL views for all CDS [views and projections](../../cds/cdl#views-projections). This means adding or changing CDS views requires a deployment of the database schema changes. +The CDS compiler generates [SQL DDL](../../guides/databases?impl-variant=java#generating-sql-ddl) statements from your CDS model, including SQL views for all CDS [views and projections](../../cds/cdl#views-projections). As a result, adding or modifying CDS views typically requires redeploying the database schema. -To avoid schema updates due to adding or updating CDS views, annotate them with [@cds.persistence.skip](../../guides/databases#cds-persistence-skip). In this case the CDS compiler won't generate corresponding static database views. Instead, the CDS views are dynamically resolved by the CAP Java runtime. +To avoid schema redeployments when you add or update CDS views, annotate them with [@cds.persistence.skip](../../guides/databases#cds-persistence-skip). This annotation tells the CDS compiler to skip generating database views for these entities. Instead, the CAP Java runtime dynamically resolves such views at runtime. + +::: warning Limitations +Runtime views support only simple [CDS projections](../../cds/cdl#as-projection-on). They do not support complex views that use aggregations, unions, joins, or subqueries in the `FROM` clause. To read [draft-enabled](../fiori-drafts#reading-drafts) entities, set `cds.drafts.persistence` to `split`. [Calculated elements](../../cds/cdl#calculated-elements) are not yet supported in runtime views. +::: + +For example, consider the following CDS model and query: ```cds entity Books { @@ -413,33 +419,47 @@ entity Books { stock : Integer; author : Association to one Authors; } -@cds.persistence.skip // [!code focus] -entity BooksWithLowStock as projection on Books { // [!code focus] - id, title, author.name as author // [!code focus] -} where stock < 10; // [!code focus] +@cds.persistence.skip +entity BooksWithLowStock as projection on Books { + id, title, author.name as author +} where stock < 10; ``` - -At runtime, CAP Java resolves queries against runtime views until an entity is reached that isn't annotated with *@cds.persistence.skip*. For example, the CQL query - ```sql Select BooksWithLowStock where author = 'Kafka' ``` -is executed against SQL databases as - -```SQL -SELECT B.ID, B.TITLE, A.NAME as "author" FROM BOOKS AS B - LEFT OUTER JOIN AUTHORS AS A ON B.AUTHOR_ID = A.ID -WHERE B.STOCK < 10 AND A.NAME = ? -``` +CAP Java provides two modes for resolving runtime views: -::: warning Limitations -Runtime views are supported for simple [CDS projections](../../cds/cdl#as-projection-on). Expands of [filtered associations](../../cds/cdl#publish-associations-with-filter) are only supported since `3.7.0`. Constant values and expressions in runtime views are supported since `3.8.0`. +**`cte` mode**: The runtime translates the view definition into a _Common Table Expression_ (CTE) and sends it with the query to the database. -Complex views using aggregations or union/join/subqueries in `FROM` are not supported and for reading [draft-enabled](../fiori-drafts#reading-drafts) entities, `cds.drafts.persistence` needs to be set to `split`. +```sql +WITH BOOKSWITHLOWSTOCK_CTE AS ( + SELECT B.ID, + B.TITLE, + A.NAME AS "AUTHOR" + FROM BOOKS B + LEFT OUTER JOIN AUTHOR A ON B.AUTHOR_ID = A.ID + WHERE B.STOCK < 10 +) +SELECT ID, TITLE, AUTHOR AS "author" + FROM BOOKSWITHLOWSTOCK_CTE + WHERE A.NAME = ? +``` + +::: tip +CAP Java 4.x uses `cte` mode by default. In 3.10, enable it with **cds.sql.runtimeView.mode: cte**. ::: -### Using I/O Streams in Queries +**`resolve` mode**: The runtime _resolves_ the view definition to the underlying persistence entities and executes the query directly against them. + +```sql +SELECT B.ID, B.TITLE, A.NAME AS "author" + FROM BOOKS AS B + LEFT OUTER JOIN AUTHORS AS A ON B.AUTHOR_ID = A.ID + WHERE B.STOCK < 10 AND A.NAME = ? +``` + +## Using I/O Streams in Queries As described in section [Predefined Types](../cds-data#predefined-types) it's possible to stream the data, if the element is annotated with `@Core.MediaType`. The following example demonstrates how to allocate the stream for element `coverImage`, pass it through the API to an underlying database and close the stream. @@ -473,7 +493,7 @@ try (InputStream resource = getResource("IMAGE.PNG")) { // Transaction finished ``` -### Using Native SQL +## Using Native SQL CAP Java doesn't have a dedicated API to execute native SQL Statements. However, when using Spring as application framework you can leverage Spring's features to execute native SQL statements. See [Execute SQL statements with Spring's JdbcTemplate](../cqn-services/persistence-services#jdbctemplate) for more details. diff --git a/menu.md b/menu.md index e373bfe69b..716409ff24 100644 --- a/menu.md +++ b/menu.md @@ -46,6 +46,7 @@ ### [SAP Event Mesh](guides/messaging/event-mesh) ### [Apache Kafka](../guides/messaging/apache-kafka) ### [Events from S/4](guides/messaging/s4) + ### [Task Queues](guides/messaging/task-queues) ## [Protocols/APIs](advanced/publishing-apis/) @@ -91,7 +92,7 @@ ### [Deploy to Cloud Foundry](../guides/deployment/to-cf) ### [Deploy to Kyma/K8s](../guides/deployment/to-kyma) - ### [Deploy to Shared DB](../guides/deployment/shared-db) + ### [Microservices with CAP](../guides/deployment/microservices) ### [Deploy with Confidence](../guides/deployment/dwc) ### [Deploy with CI/CD](../guides/deployment/cicd) ### [Custom Builds](../guides/deployment/custom-builds) @@ -165,3 +166,4 @@ ## [Open Resource Discovery](plugins/#ord-open-resource-discovery) ## [CAP Operator for K8s](plugins/#cap-operator-plugin) ## [SAP Cloud Appl. Event Hub](plugins/#event-hub) +## [Advanced Event Mesh](plugins/#advanced-event-mesh) diff --git a/node.js/_menu.md b/node.js/_menu.md index eec9275676..609d8aae34 100644 --- a/node.js/_menu.md +++ b/node.js/_menu.md @@ -57,7 +57,7 @@ # [cds. utils](cds-utils) # [cds. test()](cds-test) # [cds. plugins](cds-plugins) -# [cds. outboxed()](outbox) +# [cds. queued()](queue) # [TypeScript](typescript) # [Fiori Support](fiori) # [Best Practices](best-practices) diff --git a/node.js/authentication.md b/node.js/authentication.md index 8d76c59113..2394829ea9 100644 --- a/node.js/authentication.md +++ b/node.js/authentication.md @@ -369,6 +369,28 @@ npm add @sap/xssec ``` ::: +#### Token Validation + +For tokens issued by SAP Cloud Identity Service, `@sap/xssec` offers two additional validations: (1) token ownership via x5t thumbprint and (2) proof-of-possession. +These validations are enabled by default for requests to the app's `cert` route (`.cert` segment in the domain). + +The default behavior can be overwritten using additional configuration as follows: + +```json +"requires": { + "auth": { + "kind": "ias", + "config": { // passed to @sap/xssec as is + "validation": { + "x5t": { "enabled": false }, + "proofToken": { "enabled": false } + } + } + } +} +``` + +Please see [`@sap/xssec` documentation](https://www.npmjs.com/package/@sap/xssec) for more details. ### Custom Authentication { #custom } @@ -461,7 +483,7 @@ If you don't know the API endpoint, have a look at section [Regions and API Endp ```json "oauth2-configuration": { "redirect-uris": [ - "http://localhost:5000/" + "http://localhost:5000/login/callback" ] } ``` diff --git a/node.js/cds-compile.md b/node.js/cds-compile.md index db25936742..b28c6f36d9 100644 --- a/node.js/cds-compile.md +++ b/node.js/cds-compile.md @@ -183,15 +183,6 @@ for (let [edm,{file,suffix}] of all) console.log (file,suffix,edm) ``` - -### .hdbcds() {.method .deprecated} - -Generates `hdbcds` output. - -Current SAP HANA Cloud versions do no longer support `.hdbcds`. The command is supported for backward compatibility with older versions of [SAP HANA Service for SAP BTP](https://help.sap.com/docs/HANA_SERVICE). - -Use [`cds.compile.to.hana`](#hana) instead. - ### .hdbtable() {.method .deprecated} Use [`cds.compile.to.hana`](#hana) instead. diff --git a/node.js/cds-connect.md b/node.js/cds-connect.md index e1c3062e94..26b1147e09 100644 --- a/node.js/cds-connect.md +++ b/node.js/cds-connect.md @@ -13,15 +13,72 @@ The latter include **database** services. In all cases use `cds.connect` to conn [[toc]] +## Connecting to Required Services { #cds-connect-to } + + + +### cds. connect.to () {.method} + +Use `cds.connect.to()` to connect to services configured in a project's `cds.requires` configuration. + +```js +const ReviewsService = await cds.connect.to('ReviewsService') +``` + +The method returns a _Promise_ resolving to a _[Service](../cds/cdl#services)_ instance which acts as a client proxy to the service's API, allowing you to call its methods and access its data using common [`cds.Service`](core-services#consuming-services) methods, e.g.: + +```js +let reviews = await ReviewsService.read ('Reviews') +``` + + +**Arguments** are as follows: + +```ts:no-line-numbers +async function cds.connect.to ( + name? : string, // reference to an entry in `cds.requires` config + options? : { + kind : string // reference to a preset in `cds.requires.kinds` config + impl : string // module name of the implementation + } +) : Promise +``` + +Argument `name` is used to look up connect options from [configured services](#cds-env-requires), which are defined in the `cds.requires` section of your _package.json_ or _.cdsrc.json_ or _.yaml_ files. + +Argument `options` also allows to pass additional options such as `credentials` programmatically, and thus create services without configurations and [service bindings](#service-bindings), for example, you could connect to a local SQLite database in your tests like this: + +```js +const db2 = await cds.connect.to ({ + kind: 'sqlite', credentials: { url: 'db2.sqlite' } +}) +``` + + +### cds. services {#cds-connect-caching .property} + +When connecting to a service using `cds.connect.to()`, the service instance is cached in [`cds.services`](cds-facade#cds-services) under the service name. This means that subsequent calls to `cds.connect.to()` with the same service name will all return the same instance. As services constructed by [`cds.serve`](cds-serve#cds-serve) are registered with [`cds.services`](cds-facade#cds-services) as well, a connect finds and returns them as local service connections. + +You can also access cached service instance like this: + +```js +const { ReviewsService } = cds.services +``` + +> Note: If _ad-hoc_ options are provided, the instance is not cached. + ## Configuring Required Services {#cds-env-requires } -To configure required remote services in Node.js, simply add respective entries to the `cds.requires` sections in your _package.json_ or in _.cdsrc.json_ (omitting the `cds.` prefix). These configurations are constructed as follows: +To configure required remote services in Node.js, simply add respective entries to the `cds.requires` sections in your _package.json_ or in _.cdsrc.json_ or _.yaml_. These configurations are constructed as follows: -```json -"cds": { +::: code-group + +```json [package.json] +{"cds":{ "requires": { + "db": { "kind": "sqlite", "credentials": { "url":"db.sqlite" }}, "ReviewsService": { "kind": "odata", "model": "@capire/reviews" }, @@ -29,9 +86,26 @@ To configure required remote services in Node.js, simply add respective entries "kind": "odata", "model": "@capire/orders" }, } -} +}} +``` + +```yaml [.cdsrc.yaml] +cds: + requires: + db: + kind: sqlite + credentials: + url: db.sqlite + ReviewsService: + kind: odata, + model: @capire/reviews + OrdersService: + kind: odata, + model: @capire/orders ``` +::: + Entries in this section tell the service loader to not serve that service as part of your application, but expects a service binding at runtime in order to connect to the external service provider. The options are as follows: @@ -104,147 +178,83 @@ If you specify a model, then a service definition for your required service must The example specifies `service: 'BusinessPartnerService'`, which results in a check for a service called `BusinessPartnerService` instead of `remote-service` in the model loaded from `some/imported/model`. -### cds.requires.\`.credentials` - -Specify the credentials to connect to the service. Credentials need to be kept secure and should not be part of a configuration file. - - - - - -## Connecting to Required Services { #cds-connect-to } +## Service Bindings {#service-bindings} -### cds. connect.to () {.method} - -Declaration: - -```ts:no-line-numbers -async function cds.connect.to ( - name : string, // reference to an entry in `cds.requires` config - options : { - kind : string // reference to a preset in `cds.requires.kinds` config - impl : string // module name of the implementation - } -) -``` - -Use `cds.connect.to()` to connect to services configured in a project's `cds.requires` configuration. Usually such services are remote services, which in turn can be mocked locally. Here's an example: - -::: code-group +A service binding connects an application with a cloud service. For that, the cloud service's credentials need to be injected in the CDS configuration: -```json [package.json] -{"cds":{ - "requires":{ - "db": { "kind": "sqlite", "credentials": { "url":"db.sqlite" }}, - "ReviewsService": { "kind": "odata-v4" } +```jsonc +{ + "requires": { + "db": { + "kind": "hana", + "credentials": { /* from service binding */ } + } } -}} -``` - -::: - -```js -const ReviewsService = cds.connect.to('ReviewsService') -const db = cds.connect.to('db') -``` - -Argument `options` allows to pass options programmatically, and thus create services without configurations, for example: - -```js -const db2 = cds.connect.to ({ - kind: 'sqlite', credentials: { url: 'db2.sqlite' } -}) +} ``` -In essence, `cds.connect.to()` works like that: -```js -let o = { ...cds.requires[name], ...options } -let csn = o.model ? await cds.load(o.model) : cds.model -let Service = require (o.impl) //> a subclass of cds.Service -let srv = new Service (name, csn, o) -return srv.init() ?? srv -``` +### cds.requires.\.credentials +All service binding information goes into this property. It's filled from the process environment when starting server processes, managed by deployment environments. Service bindings provide the details about how to reach a required service at runtime, that is, providing requisite credentials, most prominently the target service's `url`. +You specify the credentials to be used for a service by using one of the following: -### cds.connect.to (name, options?) → service +- Process environment variables +- Command line options +- File system +- Auto binding -Connects to a required service and returns a _Promise_ resolving to a corresponding _[Service](../cds/cdl#services)_ instance. -Subsequent invocations with the same service name all return the same instance. +For example, in development, you can add them to a _.env_ file as follows: -```js -const srv = await cds.connect.to ('some-service') -const { Books } = srv.entities -await srv.run (SELECT.from(Books)) +```properties +# .env file +cds.requires.remote-service.credentials = { "url":"http://...", ... } ``` +::: warning ❗ Never add secrets or passwords to _package.json_ or _.cdsrc.json_! +General rule of thumb: `.credentials` are always filled (and overridden) from process environment on process start. +::: -_**Arguments:**_ - -* `name` is used to look up connect options from [configured services](#cds-env-requires). -* `options` allows to provide _ad-hoc_ options, overriding [configured ones](#cds-env-requires). - - -_**Caching:**_ - -Service instances are cached in [`cds.services`](cds-facade#cds-services), thus subsequent connects with the same service name return the initially connected one. As services constructed by [`cds.serve`](cds-serve#cds-serve) are registered with [`cds.services`](cds-facade#cds-services) as well, a connect finds and returns them as local service connections. - -If _ad-hoc_ options are provided, the instance is not cached. - - - -### cds.connect.to (options) → service - -Ad-hoc connection (→ only for tests): - -```js -cds.connect.to ({ kind:'sqlite', credentials:{database:'my.db'} }) -``` +### Basic Mechanism {#bindings-via-cds-env} -### cds.connect.to ('\:\') → service -This is a shortcut for ad-hoc connections. +The CAP Node.js runtime expects to find the service bindings in `cds.env.requires`. -For example: -```js -cds.connect.to ('sqlite:my.db') -``` +1. Configured required services constitute endpoints for service bindings. -is equivalent to: + ```json + "cds": { + "requires": { + "ReviewsService": {...}, + } + } + ``` -```js -cds.connect.to ({kind: 'sqlite', credentials:{database:'my.db'}}) -``` +2. These are made available to the runtime via `cds.env.requires`. + ```js + const { ReviewsService } = cds.env.requires + ``` +3. Service Bindings essentially fill in `credentials` to these entries. -## Service Bindings {#service-bindings} + ```js + const { ReviewsService } = cds.env.requires + ReviewsService.credentials = { + url: "http://localhost:4005/reviews" + } + ``` -A service binding connects an application with a cloud service. For that, the cloud service's credentials need to be injected in the CDS configuration: +The latter is appropriate in test suites. In productive code, you never provide credentials in a hard-coded way. Instead, use one of the options presented in the following sections. -```jsonc -{ - "requires": { - "db": { - "kind": "hana", - "credentials": { /* from service binding */ } - } - } -} -``` -You specify the credentials to be used for a service by using one of the following: -- Environment variables -- File system -- Auto binding -What to use depends on your environment. ### In Cloud Foundry {#bindings-in-cloud-platforms} @@ -258,6 +268,8 @@ Cloud Foundry uses auto configuration of service credentials through the `VCAP_S [Learn more about environment variables on Cloud Foundry and `cf env`.](https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html){.learn-more} + + #### Through `VCAP_SERVICES` env var {#vcap_services} When deploying to Cloud Foundry, service bindings are provided in `VCAP_SERVICES` process environment variables, which is JSON-stringified array containing credentials for multiple services. The entries are matched to the entries in `cds.requires` as follows, in order of precedence: @@ -373,6 +385,8 @@ Here are a few examples:
+ + ### In Kubernetes / Kyma { #in-kubernetes-kyma} CAP supports [servicebinding.io](https://servicebinding.io/) service bindings and SAP BTP service bindings created by the [SAP BTP Service Operator](https://github.com/SAP/sap-btp-service-operator). @@ -608,79 +622,9 @@ The resulting `VCAP_SERVICES` env variable looks like this: ``` -## Hybrid Testing - - -In addition to the [static configuration of required services](#service-bindings), additional information, such as urls, secrets, or passwords are required to actually send requests to remote endpoints. These are dynamically filled into property `credentials` from process environments, as explained in the following. - - -### cds.requires.\.credentials - -All service binding information goes into this property. It's filled from the process environment when starting server processes, managed by deployment environments. Service bindings provide the details about how to reach a required service at runtime, that is, providing requisite credentials, most prominently the target service's `url`. - -For development purposes, you can pass them on the command line or add them to a _.env_ or _default-env.json_ file as follows: - -```properties -# .env file -cds.requires.remote-service.credentials = { "url":"http://...", ... } -``` -::: warning -❗ Never add secrets or passwords to _package.json_ or _.cdsrc.json_! -General rule of thumb: `.credentials` are always filled (and overridden) from process environment on process start. -::: - -One prominent exception of that, which you would frequently add to your _package.json_ is the definition of a database file for persistent sqlite database during development: -```json - "cds": { "requires": { - "db": { - "kind": "sql", - "[development]": { - "kind": "sqlite", - "credentials": { - "url": "db/bookshop.sqlite" - } - } - } - }} -``` - - - - -### Basic Mechanism {#bindings-via-cds-env} - - -The CAP Node.js runtime expects to find the service bindings in `cds.env.requires`. - -1. Configured required services constitute endpoints for service bindings. - - ```json - "cds": { - "requires": { - "ReviewsService": {...}, - } - } - ``` - -2. These are made available to the runtime via `cds.env.requires`. - - ```js - const { ReviewsService } = cds.env.requires - ``` - -3. Service Bindings essentially fill in `credentials` to these entries. - - ```js - const { ReviewsService } = cds.env.requires - ReviewsService.credentials = { - url: "http://localhost:4005/reviews" - } - ``` - -The latter is appropriate in test suites. In productive code, you never provide credentials in a hard-coded way. Instead, use one of the options presented in the following sections. -### Through _.cdsrc-private.json_ File for Local Testing +### Through _.cdsrc-private.json_ File for Hybrid Testing [Learn more about hybrid testing using _.cdsrc-private.json_.](../advanced/hybrid-testing#bind-to-cloud-services) diff --git a/node.js/cds-log.md b/node.js/cds-log.md index 56f7dba784..dfea244261 100644 --- a/node.js/cds-log.md +++ b/node.js/cds-log.md @@ -413,6 +413,11 @@ Some header values shall not appear in logs, for example when pertaining to auth In case your application shares any sensitive data (for example, secrets) via headers, please ensure that you adjust the configuration as necessary. ::: +::: tip +In the log entry, header field names are normalized to lowercase with `_` instead of `-`. +Make sure your matchers work on the original header name, for example, `"/Foo-Bar/"` instead of the normalized `"/foo_bar/"`. +::: + ### Custom Fields { #custom-fields } @@ -475,10 +480,6 @@ Without the additional custom field `query` and it's respective value, it would ::: -::: tip -Before `@sap/cds^7.5`, the configuration property was called `kibana_custom_fields`. As Kibana is the dashboard technology and the custom fields are actually a feature of the SAP Application Logging Service, we changed the name to `als_custom_fields`. `kibana_custom_fields` is supported until `@sap/cds^8`. -::: - For SAP Cloud Logging, the JSON formatter uses the following default configuration: ```jsonc diff --git a/node.js/cds-plugins.md b/node.js/cds-plugins.md index d26ea6f68a..445b456c32 100644 --- a/node.js/cds-plugins.md +++ b/node.js/cds-plugins.md @@ -128,4 +128,4 @@ Currently, the following schema contribution points are supported: #### Usage In a CAP Project -