Seamless Multi‑OSS Switching with Adapter Pattern and Nacos Dynamic Configuration
The article demonstrates how to use the Adapter design pattern together with Nacos dynamic configuration to enable transparent switching among multiple OSS providers (MinIO, Alibaba Cloud OSS, etc.) in a Spring Cloud micro‑service, preserving low coupling and supporting hot reload without code changes.
Introduction
In a micro‑service project the OSS storage layer often needs to support several cloud providers such as Alibaba Cloud OSS, Tencent Cloud COS and MinIO, and new providers may be added later. Directly changing the concrete provider forces modifications in controller and service layers, violating low‑coupling principles.
Adapter Pattern Refactor
Define a common target interface StorageAdapter that declares the operations required by the business code:
public interface StorageAdapter {
void createBucket(String bucket);
void uploadFile(MultipartFile multipartFile, String bucket, String objectName);
String getUrl(String bucket, String objectName);
}Implement two adapters that translate the interface to the existing utility classes.
MinIO Adapter
@Component
public class MinioStorageAdapter implements StorageAdapter {
@Resource
private MinioUtil minioUtil;
@Value("${minio.url}")
private String url;
@Override
@SneakyThrows
public void createBucket(String bucket) {
minioUtil.createBucket(bucket);
}
@Override
@SneakyThrows
public void uploadFile(MultipartFile multipartFile, String bucket, String objectName) {
minioUtil.createBucket(bucket);
if (objectName != null) {
minioUtil.uploadFile(multipartFile.getInputStream(), bucket, objectName + "/" + multipartFile.getOriginalFilename());
} else {
minioUtil.uploadFile(multipartFile.getInputStream(), bucket, multipartFile.getOriginalFilename());
}
}
@Override
public String getUrl(String bucket, String objectName) {
return url + "/" + bucket + "/" + objectName;
}
}Aliyun Adapter
public class AliStorageAdapter implements StorageAdapter {
@Override
public void createBucket(String bucket) {
System.out.println("aliyun");
}
@Override
public void uploadFile(MultipartFile multipartFile, String bucket, String objectName) {
// implementation omitted for brevity
}
@Override
public String getUrl(String bucket, String objectName) {
return "aliyun";
}
}Dynamic Configuration with Nacos
Read the current storage type from Nacos at runtime using a @Value placeholder. The configuration class decides which adapter to instantiate.
@Configuration
public class StorageConfig {
@Value("${storage.service.type}")
private String storageType;
@Bean
@RefreshScope
public StorageAdapter storageAdapter() {
if ("minio".equals(storageType)) {
return new MinioStorageAdapter();
} else if ("aliyun".equals(storageType)) {
return new AliStorageAdapter();
} else {
throw new IllegalArgumentException("No matching storage adapter found");
}
}
}The @RefreshScope annotation ensures that when the Nacos configuration changes, the bean is re‑initialized automatically.
Domain‑Level Facade (FileService)
@Component
public class FileService {
private final StorageAdapter storageAdapter;
public FileService(StorageAdapter storageAdapter) {
this.storageAdapter = storageAdapter;
}
public void createBucket(String bucket) {
storageAdapter.createBucket(bucket);
}
public String uploadFile(MultipartFile multipartFile, String bucket, String objectName) {
storageAdapter.uploadFile(multipartFile, bucket, objectName);
String finalObjectName = (StringUtils.isEmpty(objectName) ? "" : objectName + "/") + multipartFile.getOriginalFilename();
return storageAdapter.getUrl(bucket, finalObjectName);
}
}Controller Layer
@RestController
public class FileController {
@Resource
private FileService fileService;
@PostMapping("/upload")
public Result<String> upload(MultipartFile uploadFile, String bucket, String objectName) throws Exception {
Preconditions.checkArgument(!ObjectUtils.isEmpty(uploadFile), "文件不能为空");
Preconditions.checkArgument(!StringUtils.isEmpty(bucket), "bucket桶名称不能为空");
String url = fileService.uploadFile(uploadFile, bucket, objectName);
return Result.ok(url);
}
}Nacos Deployment
Run a standalone Nacos server with Docker:
docker pull nacos/nacos-server
docker run -d \
--name nacos \
--privileged \
--cgroupns host \
--env JVM_XMX=256m \
--env MODE=standalone \
--env JVM_XMS=256m \
-p 8848:8848/tcp \
-p 9848:9848/tcp \
--restart=always \
-w /home/nacos \
nacos/nacos-serverKey flags: --privileged: grants the container extended privileges. --cgroupns host: makes the container use the host’s cgroup namespace. --env: sets JVM parameters and the running mode.
Port 8848 is the Nacos server port; 9848 is the gRPC client port.
Add Maven dependencies for Nacos config and Log4j2 (Spring Cloud Alibaba 2.2.6.RELEASE requires Spring Boot 2.3.8.RELEASE):
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.4.2</version>
</dependency>Configure Nacos in bootstrap.yml:
spring:
application:
name: jc-club-oss
profiles:
active: dev
cloud:
nacos:
server-addr: 117.72.118.73:8848
config:
file-extension: yamlCreate a data‑id file jc-club-oss-dev.yaml that contains the key storage.service.type (e.g., minio or aliyun). Spring reads the file using the pattern
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}.
Enable hot‑refresh by adding @RefreshScope to both the configuration class and the @Bean method; when the value of storage.service.type changes in Nacos, the corresponding adapter bean is recreated automatically.
Testing
Set storage.service.type=aliyun in Nacos and invoke the upload endpoint – the response URL is aliyun. Then change the value to minio; the next upload stores the file in MinIO and returns a URL built from the configured minio.url. Nacos logs a refresh event, e.g.:
2024-12-03 17:05:50.719 INFO 35932 --- [.72.118.73_8848] o.s.c.e.e.RefreshEventListener : Refresh keys changed: [storage.service.type]Conclusion
By combining the Adapter design pattern with Nacos dynamic configuration, the application achieves seamless, zero‑code switching among multiple OSS implementations, improves maintainability, and supports hot updates without restarting the service.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer XiaoFu
xiaofucode.com – a programmer learning guide driven by the pursuit of profit
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
