How to Design Clean RPC Interfaces: Best Practices and Pitfalls

This article examines common pain points of traditional RPC interfaces—excessive query methods, poor extensibility, difficult upgrades, testing challenges, and flawed exception handling—and presents practical solutions such as single‑parameter designs, Specification pattern, proper exception contracts, version‑by‑module evolution, and automated testing integration.

Programmer DD
Programmer DD
Programmer DD
How to Design Clean RPC Interfaces: Best Practices and Pitfalls

RPC frameworks like Dubbo, Motan, gRPC and Sofa are widely used, yet most discussions focus on their implementation rather than on how to design clean RPC interfaces. This article shares practical best‑practice guidelines for RPC interface design.

Initial Understanding of RPC Interface Design

In RPC terminology, an API is the contract exposed by a service, while the app implements that contract in an independent process. A typical Maven multi‑module project for a service looks like:

<modules>
    <module>serviceA-api</module>
    <module>serviceA-app</module>
</modules>
<packaging>pom</packaging>
<groupId>moe.cnkirito</groupId>
<artifactId>serviceA</artifactId>
<version>1.0.0‑SNAPSHOT</version>

The serviceA-api module defines the external API and is packaged as a JAR for other services to depend on:

<parent>
    <artifactId>serviceA</artifactId>
    <groupId>moe.cnkirito</groupId>
    <version>1.0.0‑SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
<artifactId>serviceA-api</artifactId>

The serviceA-app module implements the service, typically as a Spring Boot application:

<parent>
    <artifactId>serviceA</artifactId>
    <groupId>moe.cnkirito</groupId>
    <version>1.0.0‑SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
<artifactId>serviceA-app</artifactId>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Problems of Traditional RPC Interfaces

Too many query methods : Numerous findBy overloads occupy most of the interface, leading to duplicated logic and difficulty locating the right method.

Hard to extend : Adding a new parameter forces all callers to upgrade, amplifying coupling.

Upgrade difficulty : When the project version changes, even unchanged APIs require a new version, causing unnecessary client upgrades.

Difficult to test : A bloated interface makes unit‑test maintenance painful.

Unreasonable exception design : Inconsistent handling—some teams wrap exceptions in a common response, others rely on try‑catch—creates confusion.

Single‑Parameter Interface

Spring Cloud’s HTTP limitation forces a single @RequestBody parameter, which aligns well with a single‑parameter RPC design. By consolidating all query criteria into a specification object, the number of methods can be drastically reduced.

Using the Specification Pattern to Reduce Query Methods

public interface StudentApi {
    Student findByName(String name);
    List<Student> findAllByName(String name);
    Student findByNameAndNo(String name, String no);
    Student findByIdcard(String idcard);
}

All these methods can be replaced by a single‑parameter approach:

public interface StudentApi {
    Student findBySpec(StudentSpec spec);
    List<Student> findListBySpec(StudentListSpec spec);
    Page<Student> findPageBySpec(StudentPageSpec spec);
}

The specification objects encapsulate any combination of query criteria, covering virtually all use cases while keeping the interface concise.

Single‑Parameter Simplifies Unified Management

All request objects can extend a common AbstractBaseRequest that defines fields such as traceId, clientIp, clientType, and operationType. This enables uniform AOP handling:

Unified request validation (e.g., request.checkParam()).

Centralized locking for entity changes.

Unified request classification (e.g., if (request instanceof XxxRequest)).

Consistent request logging.

Automatic success‑message publishing.

Single‑Parameter Improves Compatibility

Because Spring Cloud Feign requires a single request body, a single‑parameter RPC interface can be automatically generated as an HTTP endpoint, allowing the same service to be accessed via Dubbo or HTTP. This enables:

Automatic generation of HTTP implementations (Dubbo + JSON‑RPC / REST).

Swagger UI visual testing of Dubbo services.

TestNG integration for auto‑generated test cases, JPA auto‑schema creation, and PowerMock‑based mocking.

TestNG automated testing
TestNG automated testing

Interface Exception Design

RPC frameworks can encapsulate exceptions, but Java developers often prefer try‑catch. Business exceptions should be modeled as checked exceptions to make handling explicit.

Initial Approach

public interface ModuleAProvider {
    void opA(ARequest request);
    void opB(BRequest request);
    CommonResponse<C> opC(CRequest request);
}

Here, callers are unaware of possible ModuleAException failures.

Correct Exception Contract

public interface ModuleAProvider {
    void opA(ARequest request) throws ModuleAException;
    void opB(BRequest request) throws ModuleAException;
    CommonResponse<C> opC(CRequest request) throws ModuleAException;
}

Checked exceptions become part of the contract, forcing callers to handle them.

Caller Handling Example

public class ModuleBService implements ModuleBProvider {
    @Reference
    ModuleAProvider moduleAProvider;

    @Override
    public void someOp() throws ModuleBexception {
        try {
            moduleAProvider.opA(...);
        } catch (ModuleAException e) {
            throw new ModuleBException(e.getMessage());
        }
    }

    @Override
    public void anotherOp() {
        try {
            moduleAProvider.opB(...);
        } catch (ModuleAException e) {
            // business logic handling
        }
    }
}

Business exceptions are translated or handled locally, preventing unnecessary propagation.

Exception and Circuit Breaker

RPC failures are normal; Hystrix (or similar) should ignore business exceptions and only trigger circuit breaking for system‑level errors. Using the checked‑exception contract makes this distinction clear.

public class ModuleAProviderProxy {
    @Reference
    private ModuleAProvider moduleAProvider;

    @HystrixCommand(ignoreExceptions = {ModuleAException.class})
    public void opA(ARequest request) throws ModuleAException {
        moduleAProvider.opA(request);
    }

    @HystrixCommand(ignoreExceptions = {ModuleAException.class})
    public void opB(BRequest request) throws ModuleAException {
        moduleAProvider.opB(request);
    }

    @HystrixCommand(ignoreExceptions = {ModuleAException.class})
    public CommonResponse<C> opC(CRequest request) throws ModuleAException {
        return moduleAProvider.opC(request);
    }
}

API Version Independent Evolution

In monolithic projects, the whole project version changes together, forcing API upgrades even when the implementation changes. By versioning at the module level—separating serviceA-api and serviceA-app —the API can evolve slowly while the app evolves frequently, reducing unnecessary client upgrades.

Problem Recap and Solutions

Too many query methods : Use a single‑parameter Specification pattern.

Hard to extend : Single‑parameter design hides combinatorial query logic, allowing implementation changes without API changes.

Upgrade difficulty : Adopt module‑level versioning; keep API and app versions independent.

Difficult to test : Single‑parameter plus automated test generation improves testability.

Unreasonable exception design : Use checked exceptions and proper handling to keep code clean.

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.

JavaRPCInterface DesignSpecification pattern
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.