-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi-tenancy extension documentation #290
Open
schananas
wants to merge
53
commits into
4.6
Choose a base branch
from
feature/multitenancy
base: 4.6
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
dc3ae53
Added description of command line arguments valid for all commands
trimoq c2314fb
Merge pull request #280 from AxonIQ/feature/cli-option-description
trimoq a11e79c
Merge branch '4.6'
smcvb b49020a
Merge branch '4.6'
smcvb ef9ea1f
Merge branch '4.6'
MGathier b87c97b
Merge branch '4.6'
MGathier 6cad9ad
Update docs with (expected) changes.
408fc56
Update extensions/kafka.md
103f25c
Update docs with (expected) changes.
ad76572
Deadletter documentation added
e143023
Added remark about idem potency
ac0e8b9
Rephrased sentence
93aa49e
Rephrased sentence
e99595b
Review comments
ae06307
Added non Spring configuration example
1a0a57a
Update axon-framework/events/event-processors/README.md
b5a3c98
Update axon-framework/events/event-processors/README.md
aebe42e
Update axon-framework/events/event-processors/README.md
4ac3e4d
Update axon-framework/events/event-processors/README.md
a86feb6
Update axon-framework/events/event-processors/README.md
73f17b9
Update axon-framework/events/event-processors/README.md
18c0977
Update axon-framework/events/event-processors/README.md
a30300f
Added a section for deadletter attributes
c1d4125
Fixed tupos
1c34858
Revert "Review comments" which introduced indentation adjustments
smcvb 6bb860f
Adjust DLQ intro section
smcvb 17e1387
Adjust DLQ configuration section
smcvb c8c16e2
Adjust DLQ processing section
smcvb 2ffdecd
Adjust DLQ attributes section
smcvb 43ed042
Adjust DLQ policy section
smcvb 89b0a54
Adjust DLQ idempotency section
smcvb ad70236
Strengthen point the InMem is not production ready
smcvb 7e9cd11
Update axon-framework/events/event-processors/README.md
smcvb 110b947
Update axon-framework/events/event-processors/README.md
smcvb 171be4a
Update axon-framework/events/event-processors/README.md
smcvb dca166c
Update axon-framework/events/event-processors/README.md
smcvb 56d667f
Update axon-framework/events/event-processors/README.md
smcvb 145a261
Update axon-framework/events/event-processors/README.md
smcvb bcbc63b
Update axon-framework/events/event-processors/README.md
smcvb 825ee8a
Update axon-framework/events/event-processors/README.md
smcvb 8fff711
Update axon-framework/events/event-processors/README.md
smcvb 6f548b0
Update axon-framework/events/event-processors/README.md
smcvb 2d2db38
Update axon-framework/events/event-processors/README.md
smcvb 79f16af
Process review comments
smcvb 98a1055
Merge remote-tracking branch 'origin/4.6'
smcvb 3d5f344
Merge branch '4.6'
MGathier 82f9498
Merge branch '4.6'
smcvb a5c3560
Merge branch '4.6'
smcvb 367dd28
multitenancy documentation
schananas ae04f69
Apply suggestions from code review
schananas e9c65e4
multitenancy documentation - pr comments
schananas c3cc200
multitenancy documentation - pr comments
schananas dd4a272
multitenancy documentation - pr comments
schananas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,7 +60,7 @@ public class KafkaEventPublicationConfiguration { | |
} | ||
``` | ||
|
||
The second infrastructure component to introduce is the `KafkaPublisher`, which has a hard requirement on the `ProducerFactory`. Additionally, this would be the place to define the Kafka topic upon which Axon event messages will be published. Note that the `KafkaPublisher` needs to be `shutDown` properly, to ensure all `Producer` instances are properly closed. | ||
The second infrastructure component to introduce is the `KafkaPublisher`, which has a hard requirement on the `ProducerFactory`. Additionally, this would be the place to define the Kafka topics upon which Axon event messages will be published. You can set a function from event to `Optional<String>`. You can use this to only publish certain events, or put different events to different topics. Its not uncommon for Kafka topics to only contain one type of message. Note that the `KafkaPublisher` needs to be `shutDown` properly, to ensure all `Producer` instances are properly closed. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't expect these changes in your pull request either 🤔 |
||
|
||
```java | ||
public class KafkaEventPublicationConfiguration { | ||
|
@@ -71,7 +71,7 @@ public class KafkaEventPublicationConfiguration { | |
KafkaMessageConverter<String, byte[]> kafkaMessageConverter, | ||
int publisherAckTimeout) { | ||
return KafkaPublisher.<String, byte[]>builder() | ||
.topic(topic) // Defaults to "Axon.Events" | ||
.topicResolver(m -> Optional.of(topic)) // Defaults to "Axon.Events" for all events | ||
.producerFactory(producerFactory) // Hard requirement | ||
.messageConverter(kafkaMessageConverter) // Defaults to a "DefaultKafkaMessageConverter" | ||
.publisherAckTimeout(publisherAckTimeout) // Defaults to "1000" milliseconds; only used for "WAIT_FOR_ACK" mode | ||
|
@@ -250,7 +250,7 @@ public class KafkaEventConsumptionConfiguration { | |
} | ||
``` | ||
|
||
Note that as with any tracking event processor, the progress on the event stream is stored in a `TrackingToken`. Using the `StreamableKafkaMessageSource` means a `KafkaTrackingToken` containing topic-partition to offset pairs is stored in the `TokenStore`. | ||
Note that as with any tracking event processor, the progress on the event stream is stored in a `TrackingToken`. Using the `StreamableKafkaMessageSource` means a `KafkaTrackingToken` containing topic-partition to offset pairs is stored in the `TokenStore`. If no other `TokenStore` is provided, and auto-configuration is used, a `KafkaTokenStore` will be set instead of an `InMemoryTokenStore`. The `KafkaTokenStore` by default uses the `__axon_token_store_updates` topic. This should be a compacted topic, which should be created and configured automatically. | ||
|
||
## Customizing event message format | ||
|
||
|
@@ -278,27 +278,31 @@ public class KafkaMessageConversationConfiguration { | |
BiFunction<String, Object, RecordHeader> headerValueMapper, | ||
EventUpcasterChain upcasterChain) { | ||
return DefaultKafkaMessageConverter.builder() | ||
.serializer(serializer) // Hard requirement | ||
.sequencingPolicy(sequencingPolicy) // Defaults to a "SequentialPerAggregatePolicy" | ||
.upcasterChain(upcasterChain) // Defaults to empty upcaster chain | ||
.serializer(serializer) // Hard requirement | ||
.sequencingPolicy(sequencingPolicy) // Defaults to a "SequentialPerAggregatePolicy" | ||
.upcasterChain(upcasterChain) // Defaults to empty upcaster chain | ||
.headerValueMapper(headerValueMapper) // Defaults to "HeaderUtils#byteMapper()" | ||
.build(); | ||
} | ||
// ... | ||
} | ||
``` | ||
|
||
Make sure to use an identical `KafkaMessageConverter` on both the producing and consuming end, as otherwise exception upon deserialization should be expected. | ||
Make sure to use an identical `KafkaMessageConverter` on both the producing and consuming end, as otherwise exception upon deserialization should be expected. A `CloudEventKafkaMessageConverter` is also available using the [Cloud Events](https://cloudevents.io/) spec. | ||
|
||
## Configuration in Spring Boot | ||
|
||
This extension can be added as a Spring Boot starter dependency to your project using group id `org.axonframework.extensions.kafka` and artifact id `axon-kafka-spring-boot-starter`. When using the auto configuration, the following components will be created for you automatically: | ||
|
||
**Generic Components:** | ||
|
||
* A `DefaultKafkaMessageConverter` using the configured `eventSerializer` \(which defaults to `XStreamSerializer`\). | ||
* A `DefaultKafkaMessageConverter` using the configured `eventSerializer` \(which defaults to `XStreamSerializer`\), which is used by default to convert between Axon Event messages and Kafka records. | ||
|
||
Uses a `String` for the keys and a `byte[]` for the record's values | ||
Uses a `String` for the keys and a `byte[]` for the record's values. | ||
|
||
When the property `axon.kafka.message-converter-mode` is set to `cloud_event` a `CloudEventKafkaMessageConverter` will be used instead. This will use `String` for the keys and `CloudEvent`. | ||
|
||
For each the matching Kafka (de)serializers will also be set as default. | ||
|
||
**Producer Components:** | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# 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 | ||
|
||
- 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** | ||
> | ||
> Currently, Axon Cloud works only with static tenant configuration. | ||
|
||
### 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 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 during runtime, you can define a predicate that will tell the application to which tenant-contexts to connect to once they appear: | ||
|
||
```java | ||
@Bean | ||
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. | ||
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") | ||
``` | ||
|
||
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 a custom tenant resolver, set following property: | ||
|
||
`axon.multi-tenancy.use-metadata-helper=false` | ||
|
||
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<Message<?>> customTargetTenantResolver() { | ||
return (message, tenants) -> | ||
TenantDescriptor.tenantWithId( | ||
((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: | ||
|
||
```java | ||
@Bean | ||
public Function<TenantDescriptor, DataSourceProperties> 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 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-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 the "same" event processor. | ||
Namely, one per tenant. | ||
|
||
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(); | ||
``` | ||
|
||
Note that the convention for naming tenant-specific event processor is `{even processor name}@{tenant name}`. | ||
|
||
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, the following infrastructure components support multi-tenancy: | ||
|
||
- <span style="color:green">MultiTenantCommandBus</span> | ||
- <span style="color:green">MultiTenantEventProcessor</span> | ||
- <span style="color:green">MultiTenantEventStore</span> | ||
- <span style="color:green">MultiTenantQueryBus</span> | ||
- <span style="color:green">MultiTenantQueryUpdateEmitter</span> | ||
- <span style="color:green">MultiTenantEventProcessorControlService</span> | ||
- <span style="color:green">MultiTenantDataSourceManager</span> | ||
|
||
The following components are not yet supported: | ||
|
||
- <span style="color:red">MultitenantDeadlineManager</span> | ||
- <span style="color:red">MultitenantEventScheduler</span> | ||
|
||
|
||
### Disabling this Extension | ||
|
||
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` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't expect this change in your pull request to be honest 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Granted, the commit log as shown by GitHub strikes me as odd too.