Discovering Services using Apache Curator in Scala

In any distributed system, services need to discover each other to be able to communicate. This requires a broker of sorts in place to which services could register themselves and be discovered by other services. This mechanism is what is known as Service Discovery and the most famous choice of broker is non other than our beloved Zookeeper. However, zookeeper exposes APIs that may be difficult to work with at times. To get around this an Apache project called Apache Curator helps facilitate communication with Zookeeper in a more convenient way, with a lot of high-level constructs called "Recipes" out-of-the-box. This includes service discovery which comes in its own pacakge.

In this blog post we will see how to register and discover services using Apache Curator in Scala including some of the best practices.

Register Service

  1. To start with service registration we are going to need a client object that facilitates connection to Zookeeper. We could summon CuratorFrameworkFactory for the same as shown below:

    Note: Ensure that we block until connection to the Zookeeper is established before starting registration.

  2. Next, we need to define a class that encapsulates our service details. This could be any metadata that you would like your service to be discovered with alongside address and port.

    Notice the @BeanProperty annotation that each Scala case class property needs to be annotated with. This is required because Curator uses jackson under the hood that requires class definitions with an empty constructor and getters and setters for each field.

    In Scala this could be implemented as shown below but this could be simplified using scala.bean package as shown above.

    Note: If you don't need any metadata to provide with your service registrations, you could use Object class instead as a placeholder.

  3. Next we need to create ServiceDiscovery[ServiceDetail] instance that facilitates both service registration as well as discovery.

    Here, we need to provide the path to zkNode to which we are going to add entries for our services for discovery and the serializer that knows how to serialize ServiceDetail.

    Note: ServiceDiscovery instance needs to be started before services can be discovered. Keep in mind that the service registration entries in Zookeeper are ephemeral in nature i.e., if the service discovery instance is stopped (as part of application shutdown or otherwise), the entries will be removed from the zkNode and the services won't be available for discovery. This is an expected mechanism to convey service unreachability.

  4. Now we can create our service instance which we will pass to the service discovery instance to register.

  5. Finally register the service.

    Note: You could register multiple such service instances.

    We can verify the same in Zookeeper like so:

Discover Service

  1. To start with service discovery we are going to need a client object that facilitates connection to Zookeeper first, same as we did in step 1 of service registration above. We summon CuratorFrameworkFactory for the same as shown below:

  2. Create and start service discovery instance

  3. Discover service instances

    You can verify the output on the console:

Best Practices

  1. Ask OS for available ports

    In a real-world application, instead of manually supplying port number you could ask OS to provide you one! Refer this post to understand the significance of port 0 and how you could use it to ask OS to find you an available port.

  2. Register Shutdown Hook

    As a matter of best practice, you could always register a shutdown hook to ensure service discovery and zookeeper client resources are always closed like so:

That's all for this post. Hope it was helpful! You could always find all code here on github.

Show Comments