From 367dd28d99fb8b2d7f873c14086efd47418bab0b Mon Sep 17 00:00:00 2001 From: Stefan <91stefan@gmail.com> Date: Mon, 17 Oct 2022 13:48:16 +0200 Subject: [PATCH 1/5] multitenancy documentation --- extensions/multitenancy.md | 138 +++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 extensions/multitenancy.md diff --git a/extensions/multitenancy.md b/extensions/multitenancy.md new file mode 100644 index 00000000..d5bfc951 --- /dev/null +++ b/extensions/multitenancy.md @@ -0,0 +1,138 @@ +#Multitenancy Extension + +Axon Framework Multitenancy Extension provides your application ability to serve multiple tenants (event-stores) at once. +Multi-tenancy is important in cloud computing and this extension will provide ability to connect to tenant dynamically, physical separate tenants data, scale tenants independently... + + +### Requirements + +Following requirements needs to be meet for extension to work: +- Use **Spring Framework** together with **Axon Framework 4.6+** +- Use **Axon Server EE 4.6+** or Axon Cloud as event store (*) +- This is not hard requirement but if you wish to enable multitenancy on projection side, only JPA is supported out-of-the box + +> ** Axon Cloud ** +> +> Axon Cloud works only with static tenant configuration. + +### Configuration + +Minimal configuration is needed to get extension up and running. +Choose to use **either** static or dynamic tenant configuration. + +#### Static tenants configuration + +If you have predefined list of contexts that your application should connect to, set following property: +`axon.axonserver.contexts=tenant-context-1,tenant-context-2,tenant-context-3` + +#### Dynamic tenants configuration + +If you plan to create tenants in runtime, you can define a predicate which will tell application to which contexts to connect to once they appear in runtime: + +```java + @Bean +public TenantConnectPredicate tenantFilterPredicate() { + return context -> context.tenantId().startsWith("tenant-"); + } +``` + +### Route message to specific tenant + +#### Using meta-data + +By default, to route message to specific tenant you need to tag initial message that enters your system with metadata. +This is done with meta-data helper, and to route message to specific tenant you should set tenant name to metadata with key `TenantConfiguration.TENANT_CORRELATION_KEY`. + +```java +message.andMetaData(Collections.singletonMap(TENANT_CORRELATION_KEY, "tenant-context-1") +``` + +Metadata needs to be added only to initial message that enters your system. Any message that is produced by consequence of initial message will have this metadata copied automatically using to `CorrelationProvider`. + +#### Custom tenant resolver + +If you wish to define custom tenant resolver set following property: + +`axon.multi-tenancy.use-metadata-helper=false` + +Then define custom tenant resolver bean. For example following bean can use message payload to route message to specific tenant: + +```java + @Bean + public TargetTenantResolver> customTargetTenantResolver() { + return (message, tenants) -> + TenantDescriptor.tenantWithId( + ((TenantTaggedMessage) message.getPayload()).getTenantName() + ); + } +``` + +### Multi-tenant projections + +If you wish to use distinct database to store projections and token store for each tenant, configure following bean: + +```java + @Bean + public Function tenantDataSourceResolver() { + return tenant -> { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUrl("jdbc:postgresql://localhost:5432/"+tenant.tenantId()); + properties.setDriverClassName("org.postgresql.Driver"); + properties.setUsername("postgres"); + properties.setPassword("postgres"); + return properties; + }; + } +``` + +Note that this works by using JPA multi-tenancy support, that means only SQL Databases are supported out of the box. +If you wish to implement multi-tenancy for a different type of databases (e.g. NoSQL) make sure that your projection database supports multi-tenancy. +While in transaction you may find out which tenant owns transaction by calling:` TenantWrappedTransactionManager.getCurrentTenant()`. + +> **Pre initialize schema** +> +> Schema migration tools like Liquibase or Flyway usually won't be able to initialise schemas for dynamically created data sources. +> Any datasource that will use needs to have pre-initialized schema. + +#### Resetting projections + +Resetting projections works a bit different because there are multiple instances of "same" event processor group (one per each tenant). + +Reset specific tenant event processor group: + +```java + TrackingEventProcessor trackingEventProcessor = configuration.eventProcessingConfiguration() + .eventProcessor("com.demo.query-ep@tenant-context-1", + TrackingEventProcessor.class) + .get(); +``` + +Convention for naming event processor is: `{even processor name}@{tenant name}` + +Access all tenant event processors by retrieving `MultiTenantEventProcessor` only. +`MultiTenantEventProcessor` acts as a proxy Event Processor that references all tenant event processors. + +### Supported multi-tenant components + +Currently, supported multi-tenants components are as follows: + +- MultiTenantCommandBus +- MultiTenantEventProcessor +- MultiTenantEventStore +- MultiTenantQueryBus +- MultiTenantQueryUpdateEmitter +- MultiTenantEventProcessorControlService +- MultiTenantDataSourceManager + +Not yet supported multi-tenants components are: + +- MultitenantDeadlineManager +- MultitenantEventScheduler + + +### Disable extension + +By default, extension is automatically enabled if found on class path. +If you wish to disable extension without removing extension use following property + +`axon.multi-tenancy.enabled=false` From ae04f69291f6faae765b416ece8ad51207ed0831 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 25 Oct 2022 15:02:37 +0200 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Steven van Beelen --- extensions/multitenancy.md | 126 +++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/extensions/multitenancy.md b/extensions/multitenancy.md index d5bfc951..b0f376b6 100644 --- a/extensions/multitenancy.md +++ b/extensions/multitenancy.md @@ -1,120 +1,122 @@ -#Multitenancy Extension - -Axon Framework Multitenancy Extension provides your application ability to serve multiple tenants (event-stores) at once. -Multi-tenancy is important in cloud computing and this extension will provide ability to connect to tenant dynamically, physical separate tenants data, scale tenants independently... +# Multitenancy Extension +The Axon Framework Multitenancy Extension provides your application with the ability to serve multiple tenants (event-stores) at once. +Multi-tenancy is important in cloud computing, as this extension provides the ability to connect tenants dynamically, physical separate tenant-data, and scale tenants independently. ### Requirements -Following requirements needs to be meet for extension to work: -- Use **Spring Framework** together with **Axon Framework 4.6+** -- Use **Axon Server EE 4.6+** or Axon Cloud as event store (*) -- This is not hard requirement but if you wish to enable multitenancy on projection side, only JPA is supported out-of-the box +The following requirements need to be met for the extension to work out-of-the-box: +- Use **Spring Framework** together with **Axon Framework 4.6+**. +- Use **Axon Server EE 4.6+** or Axon Cloud as event store (*). +- This is not hard requirement, but if you wish to enable multi-tenancy for your projections, note that only JPA is supported out-of-the box. -> ** Axon Cloud ** +> ** Axon Cloud and the Multi-Tenancy extension ** > > Axon Cloud works only with static tenant configuration. ### Configuration -Minimal configuration is needed to get extension up and running. -Choose to use **either** static or dynamic tenant configuration. +A minimal configuration is needed to get this extension up and running. +Please choose **either** the static or the dynamic tenant configuration. #### Static tenants configuration -If you have predefined list of contexts that your application should connect to, set following property: +If you have a predefined list of tenants that your application should connect to, set following property: `axon.axonserver.contexts=tenant-context-1,tenant-context-2,tenant-context-3` #### Dynamic tenants configuration -If you plan to create tenants in runtime, you can define a predicate which will tell application to which contexts to connect to once they appear in runtime: +If you plan to create tenants during runtime, you can define a predicate that will tell the application to which tenant-contexts to connect to once they appear: ```java - @Bean +@Bean public TenantConnectPredicate tenantFilterPredicate() { - return context -> context.tenantId().startsWith("tenant-"); - } -``` + return context -> context.tenantId().startsWith("tenant-"); +} -### Route message to specific tenant +### Route Messages to specific tenants #### Using meta-data -By default, to route message to specific tenant you need to tag initial message that enters your system with metadata. -This is done with meta-data helper, and to route message to specific tenant you should set tenant name to metadata with key `TenantConfiguration.TENANT_CORRELATION_KEY`. +By default, to route any `Message` to a specific tenant, you need to tag the initial message that enters your system with metadata. +This is done with a meta-data helper function, which should add the tenant name with key `TenantConfiguration.TENANT_CORRELATION_KEY`. ```java message.andMetaData(Collections.singletonMap(TENANT_CORRELATION_KEY, "tenant-context-1") ``` -Metadata needs to be added only to initial message that enters your system. Any message that is produced by consequence of initial message will have this metadata copied automatically using to `CorrelationProvider`. +Note that you only need to add metadata to the initial message entering your system. +Any message produced as a consequence of the initial message will have this metadata copied automatically using a `CorrelationDataProvider`. #### Custom tenant resolver -If you wish to define custom tenant resolver set following property: +If you wish to define a custom tenant resolver, please set following property: `axon.multi-tenancy.use-metadata-helper=false` -Then define custom tenant resolver bean. For example following bean can use message payload to route message to specific tenant: +Then define the custom tenant resolver bean. +The following example can use the message payload to route a message to specific tenant: ```java - @Bean - public TargetTenantResolver> customTargetTenantResolver() { - return (message, tenants) -> - TenantDescriptor.tenantWithId( - ((TenantTaggedMessage) message.getPayload()).getTenantName() - ); - } +@Bean +public TargetTenantResolver> customTargetTenantResolver() { + return (message, tenants) -> + TenantDescriptor.tenantWithId( + ((TenantTaggedMessage) message.getPayload()).getTenantName() + ); +} ``` ### Multi-tenant projections -If you wish to use distinct database to store projections and token store for each tenant, configure following bean: +If you wish to use distinct tenant-databases to store projections and tokens, please configure the following: ```java - @Bean - public Function tenantDataSourceResolver() { - return tenant -> { - DataSourceProperties properties = new DataSourceProperties(); - properties.setUrl("jdbc:postgresql://localhost:5432/"+tenant.tenantId()); - properties.setDriverClassName("org.postgresql.Driver"); - properties.setUsername("postgres"); - properties.setPassword("postgres"); - return properties; - }; - } +@Bean +public Function tenantDataSourceResolver() { + return tenant -> { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUrl("jdbc:postgresql://localhost:5432/"+tenant.tenantId()); + properties.setDriverClassName("org.postgresql.Driver"); + properties.setUsername("postgres"); + properties.setPassword("postgres"); + return properties; + }; +} ``` -Note that this works by using JPA multi-tenancy support, that means only SQL Databases are supported out of the box. -If you wish to implement multi-tenancy for a different type of databases (e.g. NoSQL) make sure that your projection database supports multi-tenancy. -While in transaction you may find out which tenant owns transaction by calling:` TenantWrappedTransactionManager.getCurrentTenant()`. +Note that this works by using the JPA multi-tenancy support provided in this extension. +That means that currently only SQL Databases are supported out of the box. + +If you wish to implement multi-tenancy for a different type of databases (e.g. NoSQL) make sure that your projection database supports multi-tenancy, too. +When doing so, you can find it which tenants own the transaction by invoking `TenantWrappedTransactionManager.getCurrentTenant()`. -> **Pre initialize schema** +> **Pre-initialized schema** > -> Schema migration tools like Liquibase or Flyway usually won't be able to initialise schemas for dynamically created data sources. -> Any datasource that will use needs to have pre-initialized schema. +> Schema migration tools like Liquibase or Flyway usually won't be able to initialize schemas for dynamically created data sources. +> Hence, any data source that you use needs to have a the schema pre-initialized. #### Resetting projections -Resetting projections works a bit different because there are multiple instances of "same" event processor group (one per each tenant). +Resetting projections works a bit different, because there are multiple instances of the "same" event processor. +Namely, one per tenant. -Reset specific tenant event processor group: +Regard the following sample to reset an Event Processor for a specific tenant: ```java - TrackingEventProcessor trackingEventProcessor = configuration.eventProcessingConfiguration() - .eventProcessor("com.demo.query-ep@tenant-context-1", - TrackingEventProcessor.class) - .get(); +TrackingEventProcessor trackingEventProcessor = configuration.eventProcessingConfiguration() + .eventProcessor("com.demo.query-ep@tenant-context-1", TrackingEventProcessor.class) + .get(); ``` -Convention for naming event processor is: `{even processor name}@{tenant name}` +Note that the convention for naming tenant-specific event processor is `{even processor name}@{tenant name}`. -Access all tenant event processors by retrieving `MultiTenantEventProcessor` only. -`MultiTenantEventProcessor` acts as a proxy Event Processor that references all tenant event processors. +If you need to access all tenant event processors in one go, you can retrieve the `MultiTenantEventProcessor` for a specific processing name. +The `MultiTenantEventProcessor` acts as a proxy event processor referencing all tenant-specific event processors. ### Supported multi-tenant components -Currently, supported multi-tenants components are as follows: +Currently, the following infrastructure components support multi-tenancy: - MultiTenantCommandBus - MultiTenantEventProcessor @@ -124,15 +126,15 @@ Currently, supported multi-tenants components are as follows: - MultiTenantEventProcessorControlService - MultiTenantDataSourceManager -Not yet supported multi-tenants components are: +The following components are not yet supported: - MultitenantDeadlineManager - MultitenantEventScheduler -### Disable extension +### Disabling this Extension -By default, extension is automatically enabled if found on class path. -If you wish to disable extension without removing extension use following property +By default, this extension is enabled if found on class path when utilizing Spring Boot. +If you wish to disable the extension without removing the dependency, you can set the following property to `false`: `axon.multi-tenancy.enabled=false` From e9c65e4f746bc9e33bd5ca07c3785e9b6ce9edf1 Mon Sep 17 00:00:00 2001 From: Stefan <91stefan@gmail.com> Date: Tue, 25 Oct 2022 15:51:44 +0200 Subject: [PATCH 3/5] multitenancy documentation - pr comments --- extensions/multitenancy.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/extensions/multitenancy.md b/extensions/multitenancy.md index b0f376b6..59dc2f0e 100644 --- a/extensions/multitenancy.md +++ b/extensions/multitenancy.md @@ -33,9 +33,14 @@ If you plan to create tenants during runtime, you can define a predicate that wi public TenantConnectPredicate tenantFilterPredicate() { return context -> context.tenantId().startsWith("tenant-"); } +``` ### Route Messages to specific tenants +Backbone of multitenancy is ability to route message to specific tenant. +This extension offers you meta-data based routing which is ready to be used with minimal configuration. +Also, one may wish to define stronger contract and include tenant information in message payload, which is also possible by defining custom tenant resolver. + #### Using meta-data By default, to route any `Message` to a specific tenant, you need to tag the initial message that enters your system with metadata. @@ -50,7 +55,7 @@ Any message produced as a consequence of the initial message will have this meta #### Custom tenant resolver -If you wish to define a custom tenant resolver, please set following property: +If you wish to define a custom tenant resolver, set following property: `axon.multi-tenancy.use-metadata-helper=false` @@ -62,11 +67,14 @@ The following example can use the message payload to route a message to specific public TargetTenantResolver> customTargetTenantResolver() { return (message, tenants) -> TenantDescriptor.tenantWithId( - ((TenantTaggedMessage) message.getPayload()).getTenantName() + ((TenantAwareMessage) message.getPayload()).getTenantName() ); } ``` +In example above, all messages should implement custom `TenantAwareMessage` interface that exposes tenant name. +Then we can use this interface to extract tenant name from the payload and define our tenant resolver. + ### Multi-tenant projections If you wish to use distinct tenant-databases to store projections and tokens, please configure the following: From c3cc200aa301ad9519dfb5240653b43a54278aad Mon Sep 17 00:00:00 2001 From: Stefan <91stefan@gmail.com> Date: Tue, 25 Oct 2022 16:06:16 +0200 Subject: [PATCH 4/5] multitenancy documentation - pr comments --- extensions/multitenancy.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/multitenancy.md b/extensions/multitenancy.md index 59dc2f0e..3029c7a6 100644 --- a/extensions/multitenancy.md +++ b/extensions/multitenancy.md @@ -5,14 +5,14 @@ Multi-tenancy is important in cloud computing, as this extension provides the ab ### Requirements -The following requirements need to be met for the extension to work out-of-the-box: -- Use **Spring Framework** together with **Axon Framework 4.6+**. -- Use **Axon Server EE 4.6+** or Axon Cloud as event store (*). -- This is not hard requirement, but if you wish to enable multi-tenancy for your projections, note that only JPA is supported out-of-the box. +- Currently, It's possible to configure extension using **Axon Framework 4.6+** together with **Spring Framework**. +- Minimal configuration and out-of-the box solution is available only for **Axon Server EE 4.6+** or Axon Cloud (*). +- Any other custom user solutions should implement [own factory beans for components and tenant provider](https://github.com/AxonFramework/extension-multitenancy/blob/main/multitenancy-spring-boot-autoconfigure/src/main/java/org/axonframework/extensions/multitenancy/autoconfig/MultiTenancyAxonServerAutoConfiguration.java) +- If you wish to enable multi-tenancy for your projections and token store, note that only JPA is supported out-of-the box. -> ** Axon Cloud and the Multi-Tenancy extension ** +> **Axon Cloud and the Multi-Tenancy extension** > -> Axon Cloud works only with static tenant configuration. +> Currently, Axon Cloud works only with static tenant configuration. ### Configuration From dd4a2723ead3566130c06b55984d16a8d30a94ba Mon Sep 17 00:00:00 2001 From: Stefan <91stefan@gmail.com> Date: Tue, 25 Oct 2022 16:08:30 +0200 Subject: [PATCH 5/5] multitenancy documentation - pr comments --- SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SUMMARY.md b/SUMMARY.md index f01a3361..c1ae9b1a 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -169,6 +169,7 @@ * [Reactor Gateways](extensions/reactor/reactive-gateways/reactive-gateways.md) * [Spring Cloud](extensions/spring-cloud.md) * [Tracing](extensions/tracing.md) +* [Multitenancy](extensions/multitenancy.md) ## Appendices