Backend Development 10 min read

Applying the Adapter Pattern for Multi‑Cloud OSS Storage in a Spring Microservice

This article demonstrates how to use the Adapter pattern to abstract multiple OSS providers such as MinIO and Aliyun in a Spring microservice, configure the concrete adapters via Nacos dynamic configuration, and expose a unified upload API through a service and controller layer, complete with deployment and testing steps.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Applying the Adapter Pattern for Multi‑Cloud OSS Storage in a Spring Microservice

In a microservice project, the OSS storage service often needs to support multiple cloud vendors (Aliyun, Tencent Cloud, MinIO) and may need to add new providers later, which would otherwise cause changes in both controller and service layers, violating low‑coupling principles.

To solve this, the Adapter pattern is introduced: a StorageAdapter interface defines the common operations (createBucket, uploadFile, getUrl), and each vendor implements its own adapter class.

Example MinIO adapter implementation:

@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;
    }
}

Example Aliyun adapter implementation:

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";
    }
}

The StorageConfig class reads the current storage.service.type from Nacos using @Value and creates the appropriate adapter bean; @RefreshScope enables hot‑reloading when the configuration changes.

@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");
        }
    }
}

A FileService acts as an anti‑corruption layer, delegating calls to the injected StorageAdapter and constructing the final file URL.

@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 file, String bucket, String objectName) {
        storageAdapter.uploadFile(file, bucket, objectName);
        String finalName = (StringUtils.isEmpty(objectName) ? "" : objectName + "/") + file.getOriginalFilename();
        return storageAdapter.getUrl(bucket, finalName);
    }
}

The FileController exposes a /upload endpoint that validates input, calls FileService.uploadFile , and returns the file URL.

@RestController
public class FileController {
    @Resource
    private FileService fileService;

    @PostMapping("/upload")
    public Result
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);
    }
}

The article also provides Nacos deployment commands (Docker run with privileged mode, cgroup host, JVM settings) and the necessary Spring Boot configuration (bootstrap.yml, @RefreshScope usage) to enable dynamic switching of storage providers without code changes.

Testing shows that switching storage.service.type from aliyun to minio updates the behavior at runtime, with successful file uploads and correct URL generation, confirming the effectiveness of the adapter‑based design.

JavaMicroservicesNacosSpring CloudAdapter PatternOSS storage
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

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