When I first looked at Solon Cloud, I expected another opinionated microservice framework—the kind that tells you exactly which registry, which config center, and which message queue to use.
What I found instead was a different philosophy: a set of interface standards with swappable plugin implementations. You write your code against the interfaces, and switching from local development to production Cloud is a YAML change, not a code rewrite.
Let me walk through how it works.
The Core Idea: An Anti-Corruption Layer
Solon Cloud isn't a single product. It's a collection of 13 service interfaces backed by a plugin ecosystem. The official docs call it a "通用防腐层" (general anti-corruption layer), and the name fits.
Here's the architecture:
Your Business Code
↓ (uses CloudClient or annotations)
┌─────────────────────────────────────┐
│ Solon Cloud Interfaces │
│ (CloudConfigService, CloudEvent, │
│ CloudDiscoveryService, ...) │
├─────────────────────────────────────┤
│ Plugin: local │ Plugin: water │
│ Plugin: nacos │ Plugin: consul │
│ Plugin: ... │ │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode
Your code depends on the interfaces. The plugins implement them. You swap the dependency and the YAML config—the code stays untouched.
The 13 Service Interfaces
From the official family page, Solon Cloud defines these capability interfaces:
| Interface | Purpose |
|---|---|
CloudConfigService |
Distributed configuration |
CloudDiscoveryService |
Service registration & discovery |
CloudEventService |
Distributed event bus |
CloudFileService |
Distributed file storage |
CloudI18nService |
Distributed i18n |
CloudIdService |
Distributed ID generation |
CloudJobService |
Distributed scheduled jobs |
CloudListService |
Distributed whitelist/blacklist |
CloudLockService |
Distributed locking |
CloudLogService |
Distributed logging |
CloudMetricService |
Distributed metrics |
CloudTraceService |
Distributed tracing |
CloudBreakerService |
Circuit breaker |
Each interface has a corresponding configuration namespace (solon.cloud.@@.xxx) and a set of plugin implementations.
Using the Annotations
Solon Cloud provides four annotations that map to the most common use cases:
// Inject a config value from the cloud config center
@CloudConfig("demo-user-name")
String userName;
// Subscribe to a cloud event
@CloudEvent("user.login")
public class UserLoginHandler implements CloudEventHandler {
@Override
public boolean handle(Event event) throws Throwable {
// handle login event
return true;
}
}
// Define a distributed scheduled job
@CloudJob("daily-report")
public class DailyReportJob implements CloudJob {
@Override
public void run(CloudJobRuntime runtime) throws Throwable {
// generate report
}
}
// Protect a method with a circuit breaker
@CloudBreaker
public String callExternalApi() {
// call remote service
}
Enter fullscreen mode Exit fullscreen mode
You can also disable individual annotations at startup if needed:
Solon.start(App.class, args, app -> {
CloudClient.enableEvent(false); // disable @CloudEvent
CloudClient.enableBreaker(false); // disable @CloudBreaker
CloudClient.enableConfig(false); // disable @CloudConfig
CloudClient.enableJob(false); // disable @CloudJob
});
Enter fullscreen mode Exit fullscreen mode
Using the CloudClient API
For programmatic access, CloudClient gives you a unified API across all plugins:
// Pull config (no matter which config framework is behind)
Config cfg = CloudClient.config().pull("demo.ds");
// Generate a distributed ID
long id = CloudClient.id().generate();
// Publish an event (no matter which message queue is behind)
CloudClient.event().publish(new Event("demo.user.login", "1"));
// Distributed lock
if (CloudClient.lock().tryLock("demo.lock.key")) {
try {
// critical section
} finally {
CloudClient.lock().unlock("demo.lock.key");
}
}
// IP whitelist check
if (CloudClient.list().inListOfIp("safelist", ip)) {
// allow access
}
// Read a file from distributed file storage
String json = CloudClient.file().get("demo.file.key").bodyAsString();
// Record a metric
CloudClient.metric().addCount("demo", "demo.api.user.add", 1);
Enter fullscreen mode Exit fullscreen mode
The beauty is that every line above works identically whether you're running locally or in production with Water, Nacos, or Consul. You just change the plugin and config.
Config: The First Service You'll Use
Config is usually the first cloud service you interact with. Here's a typical setup:
solon.app:
group: "demo"
name: "demoapp"
solon.cloud.water:
server: "waterapi:9371"
config:
load: "demoapp.yml,demo2:test-ds"
Enter fullscreen mode Exit fullscreen mode
The config.load entries are fetched from the remote config center and merged into Solon.cfg(). They auto-refresh when the remote values change.
You can inject them with @Inject (since they're now in the application properties):
@Inject("${demo.user}")
UserModel userModel;
Enter fullscreen mode Exit fullscreen mode
Or use @CloudConfig to pull directly from the config center (bypassing the local properties):
@CloudConfig("demo-user-name")
String userName;
Enter fullscreen mode Exit fullscreen mode
The difference: @CloudConfig("dataId") maps to the config center's dataId, while @Inject("{prop-name}") reads from Solon.cfg() (which may have been populated by config.load).
Discovery: Making RPC Location-Transparent
Service discovery is where Solon Cloud shines alongside Nami RPC. Instead of hardcoding URLs, you use service names:
solon.cloud.local:
discovery:
service:
demoapp:
- "http://localhost:8081"
Enter fullscreen mode Exit fullscreen mode
Your RPC client:
@NamiClient(name = "hellorpc", path = "/rpc/")
HelloService helloService;
Enter fullscreen mode Exit fullscreen mode
The @NamiClient annotation picks up the service instance list from CloudDiscoveryService automatically. Under the hood, it uses LoadBalance.get("hellorpc") to resolve targets.
Supported discovery plugins:
| Plugin | Refresh | Protocol | Namespace | Group |
|---|---|---|---|---|
| local | No | / | No | No |
| water | Yes (push) | http | No | No |
| consul | Yes (poll 5s) | http | No | No |
| nacos | Yes | tcp | Yes | Yes |
| zookeeper | Yes | tcp | No | No |
| polaris | Yes | grpc | Yes | Yes |
| etcd | Yes (push) | http | No | Yes |
The Killer Feature: Monolithic or Distributed, Same Code
This is where Solon Cloud's design pays off. Because your code only depends on the interfaces, you can package the same application as a monolith or a distributed service by swapping the plugin in your pom.xml:
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-cloud</artifactId>
</dependency>
</dependencies>
<profiles>
<profile>
<id>single</id>
<dependencies>
<!-- Local-only implementation of all Cloud interfaces -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>local-solon-cloud-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>logback-solon-plugin</artifactId>
</dependency>
</dependencies>
<properties>
<project.env>single</project.env>
</properties>
</profile>
<profile>
<id>cloud</id>
<dependencies>
<!-- Distributed implementation -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>water-solon-cloud-plugin</artifactId>
</dependency>
</dependencies>
<properties>
<project.env>cloud</project.env>
</properties>
</profile>
</profiles>
Enter fullscreen mode Exit fullscreen mode
Same codebase. Same business logic. One Maven profile switch. No if (env == "cloud") branches.
Customizing Your Own Plugin
If none of the existing plugins fit your infrastructure, you can implement the interfaces yourself:
public class MyCloudDiscoveryService implements CloudDiscoveryService {
@Override
public void register(String group, String service, String uri) {
// register with your system
}
@Override
public List<String> findService(String group, String service) {
// query your system
return List.of("http://my-service:8080");
}
}
CloudManager.register(new MyCloudDiscoveryService());
Enter fullscreen mode Exit fullscreen mode
What Solon Cloud Does Not Do
Honest boundaries matter. Solon Cloud is not:
- A service mesh (it works alongside one, though)
- An API gateway (that's a separate module:
solon-cloud-gateway) - A replacement for circuit breaker libraries like Resilience4j (it has its own
CloudBreakerServiceinterface, but it's a lightweight abstraction) - Tied to any specific infrastructure vendor
It's a thin abstraction layer that lets you stay portable. If you outgrow it, you can always drop down to the native client—the interfaces don't get in your way.
Summary
| Aspect | Solon Cloud Approach |
|---|---|
| Design philosophy | Interface standards + plugin implementations |
| Number of service interfaces | 13 (config, discovery, event, file, i18n, id, job, list, lock, log, metric, trace, breaker) |
| Annotations | @CloudConfig, @CloudEvent, @CloudJob, @CloudBreaker |
| Unified client | CloudClient (config(), event(), id(), lock(), etc.) |
| Plugin ecosystem | local, water, nacos, consul, zookeeper, polaris, etcd, jmdns |
| Maven coordinate | org.noear:solon-cloud |
| Monolith vs distributed | Same codebase, different Maven profile |
The real value of Solon Cloud isn't in any single feature—it's in the portability contract. Your team learns one API, and the infrastructure team can swap the underlying middleware without touching a line of business code.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.