Solon Cloud: The Distributed Toolkit That Doesn't Lock You In
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: ... โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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
}
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
});
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);
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"
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;
Or use @CloudConfig to pull directly from the config center (bypassing the local properties):
@CloudConfig("demo-user-name")
String userName;
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"
Your RPC client:
@NamiClient(name = "hellorpc", path = "/rpc/")
HelloService helloService;
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>
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());
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.
Comments
No comments yet. Start the discussion.