{"id":14975837,"url":"https://github.com/sinkedship/cerberus","last_synced_at":"2025-10-27T14:31:19.791Z","repository":{"id":39955739,"uuid":"200194025","full_name":"sinkedship/cerberus","owner":"sinkedship","description":"An easy-to-use service(s) registration, discovery framework for common RPC solution, Apache Thrift.","archived":false,"fork":false,"pushed_at":"2023-06-14T22:33:23.000Z","size":146,"stargazers_count":7,"open_issues_count":5,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-01T06:31:46.114Z","etag":null,"topics":["consul","etcd","k8s","rpc","rpc-framework","service-discovery","service-registration","thrift","zookeeper"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sinkedship.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-02T08:10:28.000Z","updated_at":"2024-10-12T07:35:42.000Z","dependencies_parsed_at":"2022-09-12T07:40:47.709Z","dependency_job_id":null,"html_url":"https://github.com/sinkedship/cerberus","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinkedship%2Fcerberus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinkedship%2Fcerberus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinkedship%2Fcerberus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sinkedship%2Fcerberus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sinkedship","download_url":"https://codeload.github.com/sinkedship/cerberus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238508863,"owners_count":19484215,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["consul","etcd","k8s","rpc","rpc-framework","service-discovery","service-registration","thrift","zookeeper"],"created_at":"2024-09-24T13:52:44.783Z","updated_at":"2025-10-27T14:31:19.383Z","avatar_url":"https://github.com/sinkedship.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cerberus\n\n## What is Cerberus\n\n### A brief introduction\n\nAn easy-to-use service(s) registration, discovery framework for common RPC solution, Apache Thrift.\n\n### A slightly detailed information\n\nGenerally but sadly speaking, Cerberus are not doing anything creatively:\n\n* Simplifying every Thrift RPC calls.\n\n* Registering and/or discovering any available service instances\n\nThese are the **two** and the **only two** main purposes of Cerberus is going to provide for you.\n\n#### Simplifying the usage of Apache Thrift\n\nCerberus integrates [Drift](https://github.com/airlift/drift), an annotation-based Java library for creating Thrift serializable types and services, internally.\n\nAll Thrift related functionality is basically relied on it while Cerberus makes a little twist of it as well as encapsulates service boots-up and invocation proxy for both server side and client side, respectively.\n\n#### Auto service registration and discovery\n\nPersonally, I am sure that I don't need to explain the necessary of service auto registration and discovery in a distributed SOA environments, as I assure you have already borne in mind.\n\nCerberus provides the same functionality, with integration of a few well-known data center(s), **Zookeeper**, **Consul**, **Etcd**, a special one called **Local** which is good for local RPC testing currently.\n\n## How to use Cerberus\n\n\u003e Cerberus integrates Drift internally which provides all the awesome necessary functionality about Thrift.\n\nI am gonna to present you with a pretty simple example, a calculator service which provides only **ADDITION**.\n\nShowing you how to:\n\n* Define a calculator service.\n\n* Boot up a server:\n  * With calculator service, serving as a Thrift server.\n  * Registers calculator service to a specific data center.\n\n* Create service client:\n  * Finds a calculator service instance from a specific data center.\n  * Makes a RPC invocation in both **synchronous** and **asynchronous** ways.\n\nHere we go.\n\n### Define calculator service\n\nFirst of all, you do not need to share the contracts (like IDL in Apache Thrift) between a service provider(aka: server) and a service consumer(aka: client) as before. However, of course, you can still share them as a old-fashion way.\n\nAnd what's more important is that you can just use Java Annotation to define Thrift types and/or services instead of writing IDL files.\n\n#### Define an interface that ***WILL BE SHARED*** between server and client.\n\n```java\n@ThriftService\npublic interface CalculatorService {\n\n    @ThriftMethod\n    int add(int a, int b);\n\n    @ThriftService\n    interface Async {\n        @ThriftMethod\n        ListenableFuture\u003cInteger\u003e add(int a, int b);\n    }\n}\n```\n\nDone!\n\n### Boot a server and register it to a data center\n\n#### Add dependency to use cerberus service bootstrap\n\n```maven\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.sinkedship.cerberus\u003c/groupId\u003e\n    \u003cartifactId\u003ecerberus-service-bootstrap\u003c/artifactId\u003e\n    \u003cversion\u003e0.2.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n#### Implement calculator service\n\n```java\npublic class CalculatorServiceImpl implements CalculatorService {\n\n    @Override\n    public int add(int a, int b) {\n        return a + b;\n    }\n}\n```\n\n#### Serve calculator service\n\nCurrently, Cerberus support three kinds of data center: **Zookeeper**, **Consul**, **Etcd** and a special one called **Local** which is good for local RPC services testing without depending on any other middleware.\n\nHere we use **LOCAL** for demonstration.\n\n```java\npublic class CalculatorServer {\n    public static void main(String[] args) {\n\n        // Use local data center which doesn't depend on any others middleware and friend for testing.\n        DataCenter dataCenter = DataCenter.LOCAL;\n        // Boot it!\n        new CerberusServerBootstrap.Builder(dataCenter)\n            .withService(new CalculatorServiceImpl())\n            .builder()\n            .boot();\n    }\n}\n```\n\nThis example above will serve calculator service to an available inet IPv4 address and a arbitrary valid port number.\n\nIf you like to specify to host and port explicitly, configure it like this way:\n\n```java\npublic class CalculatorServer {\n    public static void main(String[] args) {\n\n        // Use local data center which doesn't depend on any others middleware and friend for testing.\n        DataCenter dataCenter = DataCenter.LOCAL;\n\n        // Configure it\n        CerberusServerConfig serverConfig = new CerberusServerConfig(dataCenter);\n        serverConfig.getBootConfig().setHost(\"192.168.1.31\").setPort(11111);\n\n        // Boot it!\n        new CerberusServerBootstrap.Builder(serverConfig)\n            .withService(new CalculatorServiceImpl())\n            .builder()\n            .boot();\n    }\n}\n```\n\nThese are all you need to create a Thrift server and register it to a data center, easy and simple!\n\n### Create client service and make an actual RPC invocation\n\nFrom a client's aspect, Cerberus needs to know which data center your service provider is using and the fundamental information, like data center's connection host and port.\n\nIn our example, we use **LOCAL** as a data center and the connection address will be **192.168.1.31:11111**.\n\nOne more word, as we share the service interface between server and client, we do not need to create another annotated Thrift service for our client.\n\n#### Add dependency to use cerberus service client\n\n```maven\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.sinkedship.cerberus\u003c/groupId\u003e\n    \u003cartifactId\u003ecerberus-service-client\u003c/artifactId\u003e\n    \u003cversion\u003e0.2.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n#### Create a client and make a call\n\n* Synchronous\n\n```java\npublic class CalculatorClient {\n    public static void main(String[] args) {\n\n        // Configure the client\n        // Use local data center which doesn't depend on any others middleware and it's friend for testing.\n        CerberusClientConfig config = new CerberusClientConfig(DataCenter.LOCAL);\n        config.getConcreteDataCenterConfig(LocalConfig.class)\n            .setConnectHost(\"192.168.1.31\")\n            .setConnectPort(11111);\n        // Create proxy thrift client\n        CerberusServiceFactory serviceFactory = new CerberusServiceFactory(config);\n        // Create a synchronous calculator service\n        Calculator calculatorService = serviceFactory.newService(Calculator.class);\n        // Call it\n        int result = calculatorService.add(3, 4);\n        assert result == 7;\n    }\n}\n```\n\n* Asynchronous\n\n```java\npublic class CalculatorClient {\n    public static void main(String[] args) {\n        // Same like above example\n        // ...\n        // ...\n        // ...\n        // Create a asynchronous calculator service\n        Calculator.Async asyncService = serviceFactory.newService(Calculator.Async.class);\n        // Call it and fetch result\n        ListenableFuture\u003cInteger\u003e result = asyncService.add(3, 4);\n        result.addListener(() -\u003e {\n            try {\n                int r = result.get();\n                assert r == 7;\n            } catch (Throwable t) {\n                // Log exception\n                LOGGER.error(t);\n            }\n        }, Executors.newFixedThreadPool(10));\n    }\n}\n```\n\nThis is how you can easily create your service from client with Cerberus who can finds your service automatically and proxies all the RPC calls for you.\n\n## Why use Cerberus\n\nEverything starts with a reason, the same as the initialization of Cerberus.\n\n### How do we use Apache Thrift before\n\nIt was a difficult time that I could not bear to remind myself, however I am gonna show you in case of you have forgotten:\n\n----------\n\n#### Defines an IDL\n\n```idl\nnamespace java aa.bb.cc\n\n// some structures\n\nstruct One {\n    1: ...\n    2: ...\n    3: ...\n    X: ...\n}\n\nstruct Two {\n    // same as above\n    // bla bla bla\n}\n\nservice SomeService {\n    // you define some methods here\n    void methodA(1:One one) throws (1:TExeption e)\n\n    // more methods\n    ...\n}\n```\n\n\u003e Frankly I do not think it's a bad idea to define an IDL file, actually I admire it because of the universal contract for both server and client side,\n\u003e especially when you use different programming languages in individual environments.\n\u003e\n\u003e However, it seems like a little cumbersome if you use only one language, like Java, all the development time.\n\nWhat makes me feel struggling is what happens next.\n\n#### Generates codes using Thrift\n\n```bash\nthrift --gen java /path/to/idl/some.thrift\n```\n\nThis creates all the source code defined by IDL file above, however they contains **THOUSANDS OF** unreadable codes that you will depend on later, for both two sides.\n\n#### Implement service with generated codes\n\nFrom a server's aspect, you will fill in your own business logic by implementing a specific **Iface**:\n\n```java\npublic class SomeServiceImpl implements SomeService.Iface {\n\n    // Bunch of Override methods\n    @Override\n    public void methodA(One one) throws Exception {\n        // bla bla bla\n    }\n\n    // And more\n}\n```\n\n#### Serve your service\n\nAfter you have implemented all of your stuff, you are about to reaching the final step, make everything run:\n\n```java\npublic class MyServer {\n    public static void main(String[] args) throws Exception {\n        // Define  processor\n        TProcessor processor = new SomeService.Processor\u003c\u003e(new SomeServiceImpl());\n        // Define transport\n        TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(11111);\n        TNonblockingServer.Args tArgs = new TNonblockingServer.Args(serverTransport);\n        tArgs.processor(tprocessor);\n        tArgs.transportFactory(new TFramedTransport.Factory());\n        // Define protocol\n        tArgs.protocolFactory(new TCompactProtocol.Factory());\n        TServer server = new TNonblockingServer(tArgs);\n        server.serve();\n    }\n}\n```\n\n#### Consume your service from client\n\n```java\npublic class MyClient {\n    private static final String HOST = \"some where\";\n    private static final int PORT = 11111;\n    private static final int TIME_OUT = 3 * 1000;\n\n    public static void main(String[] args) throws Exception {\n        // Define transport\n        TTransport transport = new TFramedTransport(new TSocket(HOST, PORT, TIME_OUT));\n        // Define protocol\n        TProtocol protocol = new TCompactProtocol(transport);\n        // Create client\n        SomeService.Client client = new SomeService.Client(protocol);\n        // Make connection\n        transport.open();\n        // make an actually meaningful RPC call\n        client.methodA(new One());\n        // Close your connection after you are done\n        transport.close();\n    }\n}\n```\n\n----------\n\n#### So, what is annoying\n\nImage we are working at a SOA environment and breaking services into small but strong enough pieces is our first concern. Thrift gives us a reliable foudation as a development framework, but somehow introduces some pitfalls with it:\n\n* Cumbersome generated codes that must be implemented and/or used by server and client side.\n\n* Verbose of steps that need to serve a services or create a client.\n  * When serving a service, you got to choose:\n    * A processor with your service implementation\n    * A specific transport\n    * A specific communication protocol\n    * A server argument that contains all of above\n    * A server that takes the argument\n  * When creating a client, you got to:\n    * Do the pretty much same thing as serving a service\n    * Explicitly open a connection before any RPC invocations\n    * Explicitly close a connection after all RPC invocations\n\n\u003e I appreciate Thrift provides different kinds of transport, protocol and server for us to combine with, depends on our own needs and situations.\n\u003e\n\u003e But I think a configurable way is better than verbose steps.\n\u003e\n\u003e Always forgetting how to boot a service or create a client does make me feel frustrating.\n\n* Bunches of boilerplate codes as services increase with your business scale.\n\n* Extracts a part of your attention from developing business logic to the verbose usages of Thrift, which should not and do not have to happen, at least in my opinion.\n\nAnd of course, there are other factors may or may not be a pitfall, but still possibly be a **BURDEN**:\n\n* Pre-installation of Thrift, or cannot generate any template codes that you rely on.\n\n* IDL files\n\n\u003e **Again**: I do think IDL benefits the cross-platform environments, but I did see some colleagues bothered by it.\n\u003e\n\u003e Well, old words: every coin has two sides.\n\n### How do we deal with service registration and discovery before\n\nThere are plenty of trusted open source solutions (such as: Zookeeper, Consul, Etcd, Eureka, etc) for service registration and discovery, however none of them can be plugged into your system naturally and without any efforts.\n\nEach of these middleware has its own features and APIs that manipulate it. That means you got to read through the documentations and be familiar with the APIs when start using it.\n\nI feel sorry for a business developer has to deal with these details who should not have to. If I am, as a user of services registration of discovery center, going to use one this solutions, all I want is:\n\n* Get my services registered to the registration center correctly.\n\n* Find my services from the registration center correctly.\n\n* And neither two of these will bother me with any manipulation details.\n\nSadly, you can not achieve these goals with directly depending on the middleware. Because somehow, you got to programme with it, more or less:\n\nFor instance, if Zookeeper is your best bet as a registration center. Then messing with the original Zookeeper client library or Curator Framework will be unavoidable.\n\nOne more step, what if one day you find out Consul is more suitable for the environment you are facing. Then refactoring and replacing all the codes with the corresponding APIs for Consul will be your great fun.\n\n### How does Cerberus solve these problems\n\n#### About Thrift RPC\n\n##### Contracts between server and client\n\n* Original Apache Thrift: uses **IDL** to define contracts between server and client.\n\n* Cerberus: integrates **Drift**, which provides an annotation way to define all essential concepts in Apache Thrift, that means you does not need to define IDL files and generates bunches of unreadable codes any more. Further more, the annotated services doesn't need to be shared between server and client, the annotated service can either be an interface or a concrete class.\n\n##### Serve services\n\n* Original Apache Thrift: verbose steps to combine components or concepts to boot a server.\n\n* Cerberus: can almost boot a server with fall-through in **ONE LINE** of code without any configurations. And of course, cerberus also provides a lots of understandable configurations as you needed.\n\n##### Consume services\n\n* Original Apache Thrift: verbose steps to create a thrift client and has to explicitly manage all the resources that should not be your jobs.\n\n* Cerberus: uses service factory, which can be created easily with fall-through in **ONE LINE** configurations, to create any services you need. Besides, nothing to be managed anymore and you can grasp all your attentions on business.\n\n----------\n\n#### About service registration and discovery\n\n* Common open source solutions: must be integrated in your application with considerable development. Registers services when serving your server and discovers services when consuming from client manually. If you got to change another registration center later, you need to re-integrate again.\n\n* Cerberus: just only need to declares a data center(as known as: registration center) you would like to use when booting server or create service factory. Cerberus Keeps all the dirty work from you and frees your hands. What's more, you can just change another data center as you declare before, no developments anymore.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsinkedship%2Fcerberus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsinkedship%2Fcerberus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsinkedship%2Fcerberus/lists"}