-
Notifications
You must be signed in to change notification settings - Fork 2k
Spring Cloud Azure 4.0 API Review
- Why do we need spring-cloud-azure-core, spring-cloud-azure-service, and spring-cloud-azure-resourcemanager
- spring-cloud-azure-core
- spring-cloud-azure-service
- spring-cloud-azure-resourcemanager
Why do we need spring-cloud-azure-core, spring-cloud-azure-service, and spring-cloud-azure-resourcemanager
There are multiple Spring projects/abstractions we are implementing, such as Spring Data, Spring Boot, Spring Integration, Spring Cloud Stream and etc. In 3.x we don't have a spring-cloud-azure-core
, spring-cloud-azure-service
, or spring-cloud-azure-resourcemanager
artifact. Version 3.x has multiple issues, such as:
- Not supporting all the options an Azure SDK client offers.
- Not supporting all the authentication methods an Azure SDK client supports.
- Doesn't support the
DefaultAzureCredential
. - ...
Take how we auto-configure the BlobServiceClient
for example:
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("azure.storage.blob-endpoint")
public BlobServiceClientBuilder blobServiceClientBuilder(StorageProperties storageProperties) {
final String accountName = storageProperties.getAccountName();
final String accountKey = storageProperties.getAccountKey();
return new BlobServiceClientBuilder()
.endpoint(storageProperties.getBlobEndpoint())
.credential(new StorageSharedKeyCredential(accountName, accountKey))
.httpLogOptions(new HttpLogOptions().setApplicationId(ApplicationId.AZURE_SPRING_STORAGE_BLOB));
}
The BlobServiceClientBuilder
we create above has many problems, it only supports the Shared Key
credential and it only configures 3 options, but the BlobServiceClientBuilder
has 17 methods to configure a client. Of course, we can change the code here in the spring-cloud-azure-autoconfigure
module. But what if we want to construct a BlobServiceClientBuilder
in Spring modules other than the spring-cloud-azure-autoconfigure
module? We definitely don't want to write such code twice or many more times. So there has to be a module to put such construct an Azure service client builder
operations, it could be in spring-cloud-azure-core
or spring-cloud-azure-service
. To keep our spring-cloud-azure-core
as thin as possible, we chose to put them in the spring-cloud-azure-service
module.
The SDK clients can be categorized into three types, at least the ones we're supporting, the HTTP-based, the AMQP-based, the other. azure-core
abstracts the common options that could be configured to SDK clients, such as Configuration
, ClientOptions
, HttpPipelinePolicy
, and etc. But each SDK client builder doesn't have such a common pattern, which suits the builders themselves but is not very handy for a framework user like us. For example, the TokenCredential
is supposed to support by all Azure SDK clients, even if they are not now; the HttpClient
is also configurable for all HTTP-based clients.
In a Spring Boot application, it's natural for users to want to apply the same TokenCredential
or HttpClient
objects to some or all Azure SDK clients. If we extract the construct an Azure service client builder
to a method, the method would have to take such parameters. Such as:
public BlobServiceClientBuilder buildBlobServiceClientBuilder(StorageProperties storageProperties, TokenCredential defaultTokenCredential, HttpClient httpClient) {
}
And the methods are not convenient for us to use extension points provided by the Spring framework, such as BeanPostProcessor
and BeanFactoryPostProcessor
, when extending them we'll be able to configure the Spring beans we create in an easy and consistent way. Just like what we do to inject the default TokenCredential
to all *ClientBuilderFactorys:
static class AzureServiceClientBuilderFactoryPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory beanFactory;
@SuppressWarnings("rawtypes")
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractAzureCredentialBuilderFactory) {
return bean;
}
if (bean instanceof AbstractAzureServiceClientBuilderFactory
&& beanFactory.containsBean(DEFAULT_TOKEN_CREDENTIAL_BEAN_NAME)) {
((AbstractAzureServiceClientBuilderFactory) bean).setDefaultTokenCredential(
(TokenCredential) beanFactory.getBean(DEFAULT_TOKEN_CREDENTIAL_BEAN_NAME));
}
return bean;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
So the *ClientBuilderFactory are created for two main purposes:
- Provide methods for us to pass in the
shared objects
users want to configure for many or all Azure SDK clients. - Extract the common building logic based on the client type, HTTP, or AMQP.
❗❗❗ These *ClientBuilderFactorys are not APIs and won't be directly used by the users.
Quote @Strong Liu:
Using "optional" scope to delay the function in runtime is very common design in spring, for example https://github.com/spring-projects/spring-framework/blob/main/spring-core/spring-core.gradle And the "resource" we're extending here is at the core level of spring framework, and our "core" lib is built on top of spring-framework and azure-core, so I think it is proper place to have it here, and AWS is doing so as well, https://github.com/awspring/spring-cloud-aws/blob/2.3.x/spring-cloud-aws-core/pom.xml
The Azure Resource Manager
can help provide connection information for Azure services. But it should not be a hard dependency in our spring-cloud-azure-autoconfigure
module. In the spring-cloud-azure-resourcemanager
we provide two kinds of APIs, the first kind is to retrieve connection string for Azure services, and the second kind is to provision resources.
Most of classes in this module are SPIs instead of APIs, and they won't be directly used by application users.
https://gist.github.com/saragluna/562dde2a6b6b0e73a707333faf85bc82
User applications won't directly depend on this artifact, and the Spring Boot config file won't work only with this artifact. The config file will only function when the spring-cloud-azure-autoconfigure
is on the classpath.
- spring-context
- azure-identity
- azure-core
- azure-core-amqp
- azure-core-management
- azure-storage-blob (Optional)
- azure-storage-fileshare (Optional)
Explained in this section.
// 1. com.azure.spring.core.connectionstring.ConnectionStringProvider
public interface ConnectionStringProvider<T> {
String getConnectionString();
T getServiceType();
}
// 2. com.azure.spring.core.connectionstring.StaticConnectionStringProvider
public final class StaticConnectionStringProvider<T> implements ConnectionStringProvider<T> {
public StaticConnectionStringProvider(T serviceType, String connectionString) {
}
}
// 3. com.azure.spring.core.customizer.AzureServiceClientBuilderCustomizer
public interface AzureServiceClientBuilderCustomizer<T> {
void customize(T builder);
}
// 4. com.azure.spring.core.resource.AzureStorageFileProtocolResolver
This is a Azure Storage File Share implementation of Spring ProtocolResolver, ResourcePatternResolver
// 5. com.azure.spring.core.resource.AzureStorageBlobProtocolResolver
This is a Azure Storage Blob implementation of Spring ProtocolResolver, ResourcePatternResolver
AzureStorageFileProtocolResolver
and AzureStorageBlobProtocolResolver
are the two APIs that could have performance issue, tracked by this issue https://github.com/Azure/azure-sdk-for-java/issues/25916 and it's resolved now.
❗❗❗ These *ClientBuilderFactorys are not APIs and won't be directly used by the users.
Most of classes in this module are SPIs instead of APIs, and they won't be directly used by application users.
https://gist.github.com/saragluna/6c005fab01097f7495174e1c436d492d
User applications won't directly depend on this artifact, and the Spring Boot config file won't work only with this artifact. The config file will only function when the spring-cloud-azure-autoconfigure
is on the classpath.
- spring-cloud-azure-core
- azure-cosmos (Optional)
- azure-data-appconfiguration (Optional)
- azure-messaging-eventhubs (Optional)
- azure-messaging-servicebus (Optional)
- azure-security-keyvault-certificates (Optional)
- azure-security-keyvault-serets (Optional)
- azure-storage-blob (Optional)
- azure-storage-fileshare (Optional)
- azure-storage-queue (Optional)
Explained in this section.
// 1. com.azure.spring.service.eventhubs.processor.EventProcessingListener
public interface EventProcessingListener {
default EventHubsInitializationContextConsumer getInitializationContextConsumer() {
return initializationContextConsumer -> { };
}
default EventHubsCloseContextConsumer getCloseContextConsumer() {
return closeContext -> { };
}
default EventHubsErrorContextConsumer getErrorContextConsumer() {
return errorContext -> { };
}
}
// 2. com.azure.spring.service.eventhubs.processor.BatchEventProcessingListener
public interface BatchEventProcessingListener extends EventProcessingListener {
void onEventBatch(EventBatchContext eventBatchContext);
}
// 3. com.azure.spring.service.eventhubs.processor.RecordEventProcessingListener
public interface RecordEventProcessingListener extends EventProcessingListener {
void onEvent(EventContext eventContext);
}
// 4. com.azure.spring.service.servicebus.processor.MessageProcessingListener
public interface MessageProcessingListener {
default ServiceBusErrorContextConsumer getErrorContextConsumer() {
return errorContext -> { };
}
}
// 5. com.azure.spring.service.servicebus.processor.RecordMessageProcessingListener
public interface RecordMessageProcessingListener extends MessageProcessingListener {
void onMessage(ServiceBusReceivedMessageContext messageContext);
}
Since this module only contains the *ClientBuilderFactorys, so there should not be performance or scalability issues.
https://gist.github.com/saragluna/dec50631b1ffd0125a477b6565b205f3
User applications won't directly depend on this artifact, and the Spring Boot config file won't work only with this artifact. The config file will only function when the spring-cloud-azure-autoconfigure
is on the classpath.
- spring-cloud-azure-core
- azure-resourcemanager
Explained in this section.
// 1. com.azure.spring.resourcemanager.provisioner.eventhubs.EventHubsProvisioner
public interface EventHubsProvisioner {
void provisionNamespace(String namespace);
void provisionEventHub(String namespace, String eventHub);
void provisionConsumerGroup(String namespace, String eventHub, String consumerGroup);
}
// 2. com.azure.spring.resourcemanager.provisioner.servicebus.ServiceBusProvisioner
public interface ServiceBusProvisioner {
void provisionQueue(String namespace, String queue);
void provisionTopic(String namespace, String topic);
void provisionSubscription(String namespace, String topic, String subscription);
}
// 3. com.azure.spring.resourcemanager.connectionstring.EventHubsArmConnectionStringProvider
public class EventHubsArmConnectionStringProvider extends ArmConnectionStringProvider<AzureServiceType.EventHubs> {
public EventHubsArmConnectionStringProvider(AzureResourceManager resourceManager,
AzureResourceMetadata resourceMetadata,
String namespace) {
}
public String getConnectionString() {}
public AzureServiceType.EventHubs getServiceType() {}
}
// 4. com.azure.spring.resourcemanager.connectionstring.ServiceBusArmConnectionStringProvider
public class ServiceBusArmConnectionStringProvider extends ArmConnectionStringProvider<AzureServiceType.ServiceBus> {
public ServiceBusArmConnectionStringProvider(AzureResourceManager resourceManager,
AzureResourceMetadata resourceMetadata,
String namespace) {
}
public String getConnectionString() {}
public AzureServiceType.ServiceBus getServiceType() {}
}
// 5. com.azure.spring.resourcemanager.connectionstring.StorageQueueArmConnectionStringProvider
public class StorageQueueArmConnectionStringProvider extends ArmConnectionStringProvider<AzureServiceType.StorageQueue> {
public StorageQueueArmConnectionStringProvider(AzureResourceManager resourceManager,
AzureResourceMetadata resourceMetadata,
String accountName) {
}
public String getConnectionString() {}
public AzureServiceType.StorageQueue getServiceType() {}
}
This module will call the resource manager to provision resources, which will take some time.
- Spring Credential
- Spring Cloud Azure 4.0 Design
- Spring Cloud Azure AutoConfigure Design
- Spring Cloud Azure Core Design
- Spring Cloud Azure Messaging Design
- Spring Cloud Azure Service Bus Spring Jms Support Design
- Design for directory, module name and package path for Spring Cloud Azure messaging
- Design for Remove warning logs of unknown configs for Kafka Passwordless
- Design for Enhance AAD token authentication converter to customized granted authorities converter
- Design for Enhance the ObjectMapper to support Spring Boot's pattern to enable autoconfiguration
- Passwordless connection support for Spring Cloud Azure
- Passwordless connection support for MySQL
- Passwordless connection support for Event Hubs Kafka
- Remove warning logs of unknown configs for Kafka Passwordless