Introducing Navi: An Open‑Source Component Routing Framework for Scalable Backend Systems
Navi is an open‑source component‑routing framework that separates routing logic from business code using matcher annotations, allowing developers to define reusable handlers for different client types and versions, thereby reducing code complexity, improving extensibility, and simplifying backend services while supporting future extensions and community contributions.
Navi (short for Navigation) is an open‑source framework that provides component‑level routing capabilities. It was born from the iQIYI membership backend team’s exploration of system extensibility and component‑based design, and later generalized for public use. The project repository is http://github.com/iqiyi/navi .
The framework addresses common problems in complex business systems: high code complexity, poor extensibility, tangled core and branch logic, and low code reuse. By separating routing logic from business logic, Navi enables developers to keep the main functionality clean while handling diverse client scenarios (e.g., Android, H5) through dedicated components.
How to use Navi – a step‑by‑step example
Below is a typical order‑creation service before applying Navi. The code mixes business logic with multiple client‑type branches:
public class OrderService {
public OrderCreateResult createOrder(OrderCreateRequest req) {
Order order = Order.create(req);
if (req.getClientType().equals(ANDROID)) {
// Do for the common android client.
if (Version.create("1.0.0", "2.0.0").within(req.getClientVersion())) {
// Do for android client v1.
} else if (Version.create("2.0.0", "3.0.0").within(req.getClientVersion())) {
// DO for android client v2.
} else {
// Do for other versions android client
}
}
if (req.getClientType().equals(H5)) {
// Do for H5.
}
orderRepository.save(order);
return OrderCreateResponse.create(order, req);
}
}Refactoring the branching logic into a separate method improves readability but does not solve extensibility, because every new client requirement still forces modifications to existing code.
With Navi, the same functionality is expressed by defining handler components annotated with matcher annotations. The routing selector automatically picks the appropriate handler based on request attributes, leaving the core service untouched:
interface OrderCreateHandler {
void handle(Order order, OrderCreateRequest request);
}
@EqualMatcher(property = "clientType", value = "android")
@VersionMatcher(range = "[1.0.0,2.0.0)")
@Component
class AndroidV1Handler implements OrderCreateHandler {
public void handle(Order order, OrderCreateRequest request) { }
}
@EqualMatcher(property = "clientType", value = "android")
@VersionMatcher(range = "[2.0.0,3.0.0)")
@Component
class AndroidV2Handler implements OrderCreateHandler {
public void handle(Order order, OrderCreateRequest request) { }
}
@EqualMatcher(property = "clientType", value = "android")
@VersionMatcher(range = "[3.0.0,*)")
@Component
class AndroidLatestHandler implements OrderCreateHandler {
public void handle(Order order, OrderCreateRequest request) { }
}
@EqualMatcher(property = "clientType", value = "h5")
@Component
class H5Handler implements OrderCreateHandler {
public void handle(Order order, OrderCreateRequest request) { }
}
public class OrderService {
@AutoWired
private Selector selector; // SpringBasedSelector
public OrderCreateResult createOrder(OrderCreateRequest req) {
Order order = Order.create(req);
OrderCreateHandler handler = selector.select(req, OrderCreateHandler.class);
if (handler != null) {
handler.handle(order, req);
}
orderRepository.save(order);
return OrderCreateResponse.create(order, req);
}
}Developers can also create composite matcher annotations to simplify usage. For example, a @ClientRequestMapping annotation combines @EqualMatcher and @VersionMatcher:
@Retention(RUNTIME)
@EqualMatcher(property = "name")
@VersionMatcher(property = "clientVersion")
@interface ClientRequestMapping {
@AliasFor(annotation = EqualMatcher.class, attribute = "value")
String name();
@AliasFor(annotation = VersionMatcher.class, attribute = "range")
String clientVersionRange();
}Applying the composite annotation makes handler definitions more concise:
@ClientRequestMapping(name = "hulk", clientVersionRange = "[1.0.0,2.0.0)")
class AliasAllHandler implements Handler { }Implementation principle
When selector.select(req, OrderCreateHandler.class) is invoked for the first time, the selector loads all candidate components of type OrderCreateHandler from the Spring ApplicationContext. Each candidate’s matcher annotations are parsed into MatcherDefinition objects and cached.
The selector then evaluates the definitions against the request attributes. A REJECT result stops the current candidate; otherwise matching continues. By default the selector returns the first non‑rejected handler, but other strategies (e.g., highest match score, multiple candidates) are possible.
Future directions
Navi plans to add more built‑in matchers, additional component‑selection modes, configuration options (files, DSL), AOP‑based transparent usage, hot‑loading, and performance improvements.
The article concludes with a call for developers to try Navi, submit PRs, and discuss complex system architecture. A recruitment notice for iQIYI’s backend and big‑data teams is also included.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
