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.

Programmer XiaoFu
Programmer XiaoFu
Programmer XiaoFu
Seamless Multi‑OSS Switching with Adapter Pattern and Nacos Dynamic Configuration

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-server

Key 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: yaml

Create 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.

Nacos deployment diagram
Nacos deployment diagram
bootstrap.yml example
bootstrap.yml example
Aliyun adapter test result
Aliyun adapter test result
MinIO upload success
MinIO upload success
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaDynamic ConfigurationNacosSpring CloudAdapter PatternOSS
Programmer XiaoFu
Written by

Programmer XiaoFu

xiaofucode.com – a programmer learning guide driven by the pursuit of profit

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.