DEV Community

Solon Framework
Solon Framework

Posted on

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: ...    │                  │
└─────────────────────────────────────┘
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 CloudBreakerService interface, 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.

Top comments (0)