Introduction
Been looking at Netflix Eureka for Service Registry/Discovery and thought I'd share some thoughts via an example. My interest was more around Eureka 2.0 and I was eagerly awaiting their release in early 2015 per swags(?) but that did not materialize with the team stating its better to adopt Eureka 1.0. Not very thrilled I must say :-(.
Looking at Eureka 1.0, there appears two routes that one could go:
The Heart of the Service code is the declaration of the Spring Boot Application as shown below:
Looking at Eureka 1.0, there appears two routes that one could go:
- Use Stock Netflix Eureka
- Use Spring Cloud Netflix which is a Netflix Eureka and garnished with other Netflix OSS components, heated to a Spring temperature and presented as a Spring Boot Dish.
- RestAssured - Rest Assured is used a fluent testing of the service API
- Spring Fox - Used to expose Swagger UI for service documentation and testing
- Netlflix Ocelli - Client Side Load Balancing
Eureka Components
The following are some components one deals with when working with Eureka
EurekaClientConfig
EurekaClientConfig provides the configuration required by eureka clients to register an instance with Eureka. Netflix Eureka provides an implementation DefaultEurekaClientConfig that is configured via a property file eureka.client.props. Spring Cloud Netflix extends EurekaClientConfig and provides a EurekaClientConfigBean which implements and EurekaClientConfig and works with Spring properties. Properties prefixed by eureka.client will map into the EurekaClientConfigBean via Project Lombok magic.
EurekaServerConfig
Follows a similar approach like the ClientConfig but this is used for Eureka Server properties
EurekaInstanceConfig
Provides the configuration required by an instance to register with Eureka server. If building a service, you might want to tweak some of knobs here. You can control properties like:
- Name of Service
- Secure/Non-secure ports of operation
- Lease renewal and expiration
- Home page URL and Status page URL
Discovery Client
Discovery Client uses a 'jersey 1' client to communicate with Eureka Server to:
- Register a service instance
- Renew the lease of a Service instance
- Cancel of a lease of service instance
- Query Eureka Server for registered instances
The Notes Example
The Notes example defines a Server side component that will register with Netflix Eureka, a Client side library, the Notes Client which discovers a Notes Service Instance from Eureka and an integration test that demonstrates this lifecycle.
Notes Service Code
The Heart of the Service code is the declaration of the Spring Boot Application as shown below:
@SpringBootApplication // Spring Boot @EnableEurekaClient // Enable Eureka Client @RestController @EnableSwagger2 // Swagger public class Application { @RequestMapping("/") public String home() { return "This is the Notes App"; } @Bean public Docket swaggerSpringMvcPlugin() { // Minimalistic setting for Swagger Support return new Docket(DocumentationType.SWAGGER_2) .select() .paths(PathSelectors.any()) .build().pathMapping("/"); } public static void main(String args[]) { SpringApplication.run(Application.class, args); } } @RestController @Api(basePath = "/notes", value = "Notes", description = "Note Creation and Retrieval", produces = "application/xml") public class NotesController { @Inject private NotesService notesService; @RequestMapping(value = "/notes", method = RequestMethod.POST) public Long create(@RequestBody Note note) { return notesService.createNote(note); } @RequestMapping(value = "/notes/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE) public Note get(@PathVariable Long id) { return notesService.getNote(id); } }In the above Boot application, we enabled Eureka Client via the @EnableEurekaClient annotation and also set up swagger support by creating a Docket and adding the @EnableSwagger2 annotation. The JSON Swagger resource is available at http://localhost:9090/v2/api-docs and the Swagger HTML is available at http://localhost:9090/swagger-ui.html. The configuration for the application is driven by a simple YAML file as shown below where the port, location of Eureka server instance and a custom instance Id are defined:
spring: application: name: Notes server.port: 9090 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/v2/ instance: metadataMap: instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}If one wished to integration test (exercise DB and other calls) in the Service application without having to worry about the Eureka registration and discovery, you could simply turn of the eureka client and use, say RestAssured to validate your service as shown below.
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration @IntegrationTest({"server.port=0", // Pick an ephemeral port "eureka.client.enabled=false" // Prevent registering/discovering with Eureka }) public class NotesControllerTest { @Value("${local.server.port}") private int port; @Before public void setUp() { RestAssured.port = port; } @Test public void createAndGetNote() { given().contentType(MediaType.APPLICATION_XML) .body(new Note("Test")) .when() .post("http://localhost:" + port + "/notes") .then() .statusCode(200).body(equalTo("1")); when().get("/notes/{id}", 1).then() .assertThat() .statusCode(200) .body("note.content", equalTo("Test")); } }
Notes Client Code
For the NoteClient, as mentioned, Ocelli is used as a Load Balancer with RestTemplate used to make the REST call.
NotesIntegrationTest is where things get interesting. The integration test will -
For the curious, this Notes Spring Cloud Netflix Eureka example can be found at sleeplessinslc github.
I would also like to share the below as my personal 'opinion' only
public class NotesClientImpl implements NotesClient, Closeable { private final RestTemplate restTemplate; private final Subscription subscription; private final AtomicReference<List<InstanceInfo>> result; private final RoundRobinLoadBalancer<InstanceInfo> instanceInfoLoadBalancer; public NotesClientImpl(RestTemplate restTemplate, DiscoveryClient discoveryClient, long refreshInterval, TimeUnit refreshIntervalTimeUnit) { this.restTemplate = restTemplate; this.result = new AtomicReference<List<InstanceInfo>>(new ArrayList<>()); this.subscription = new EurekaInterestManager(discoveryClient).newInterest() .forApplication("Notes") // Notes Service .withRefreshInterval(refreshInterval, refreshIntervalTimeUnit) // Ocelli Refresh Interval .asObservable() .compose(InstanceCollector.<InstanceInfo>create()) .subscribe(RxUtil.set(result)); instanceInfoLoadBalancer = RoundRobinLoadBalancer.<InstanceInfo>create(); // Round Robin } private String getServiceInstanceUrl() { InstanceInfo instanceInfo = instanceInfoLoadBalancer.choose(result.get()); // Choose an instance if (instanceInfo != null) { return "http://" + instanceInfo.getIPAddr() + ":" + instanceInfo.getPort(); } throw new RuntimeException("Service Not available"); } @Override public Note getNote(Long noteId) { return restTemplate.getForEntity(getServiceInstanceUrl() + "/notes/{id}", Note.class, noteId).getBody(); } ... }Ocelli uses an 'interest' manager to register a subscription for changes to Notes Service. A round robin load balancer is used in the example. The NotesClient is set up in the NotesClientConfig.
Notes Integration Test
NotesIntegrationTest is where things get interesting. The integration test will -
- Start a Eureka Server in the pre-integration-test phase of maven using Cargo. Note that we are using the stock Netflix eureka WAR for this. One could also use the Spring Cloud version via an exec maven plugin.
- Have Notes Service start on a random port and register with the Eureka Server
- Have the Notes Client create and obtain a 'Note' by discovering the Notes Service from Eureka
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class, NotesClientConfig.class}) // Note the Import of Server and Client @WebIntegrationTest(value = {"eureka.instance.leaseRenewalIntervalInSeconds=10", // Lease Interval "eureka.client.instanceInfoReplicationIntervalSeconds=1", "eureka.client.initialInstanceInfoReplicationIntervalSeconds=1", "eureka.client.registryFetchIntervalSeconds=1", "eureka.client.serviceUrl.defaultZone=http://localhost:9000/eureka/v2/"}, // Overriding eureka location randomPort = true) public class NotesIntegrationTest { @Inject private NotesClient notesClient; // Notes Client injected @Value("${ocelliRefreshIntervalSeconds}") private Long ocelliRefreshIntervalSeconds; // How often Ocelli Client has been set up to refresh @Test public void createAndGetNote() throws InterruptedException { // Wait for Notes App to register waitForNotesRegistration(); // Waits for Notes Service to register before proceeding Thread.sleep((ocelliRefreshIntervalSeconds * 1000) + 1000L); // Ocelli might not be in sync Note note = new Note("Test"); Long noteId = notesClient.createNote(note); // Create a note assertEquals(note, notesClient.getNote(noteId)); // Read the node } @Inject private DiscoveryClient discoveryClient; private void waitForNotesRegistration() throws InterruptedException { /* Discovery client is used to make sure service is registered before the Notes Client gets a chance to discover and exercise API */ } }The important thing here to note is that I am using the 'war' version of the Netflix Eureka server to start up before the integration test runs. It is equally possible to launch the Spring Boot 'jar' version of the eureka server via the exec maven plugin prior to the start of the integration test.
Parting Thoughts
For the curious, this Notes Spring Cloud Netflix Eureka example can be found at sleeplessinslc github.
I would also like to share the below as my personal 'opinion' only
Netflix Eureka
The good -
Awesome project, a Service Registry for the Web. Apps in any language can participate. REST + Jersey, preaching to the choir. More importantly, developed with a profound understanding of failure scenarios around a Microservice 'service registry'. Nitesh Kants presentation is a must see.
Good starter documentation and responsive folks on the mailing list.
Good starter documentation and responsive folks on the mailing list.
The bad -
I understand Eureka was designed for AWS usage primarily by Netflix. Maybe a more modular (plugin) approach would have been nicer ensuring a clean separation between AWS and non-AWS code, especially considering it is a fine candidate for private cloud.
Discovery client fetches entire registry rather than information about specific services. Eureka 2 architecture looks better in that regard where one only gets data about services they are interested in.
Jersey 1 - Lets update to Jersey 2.X folks!
Discovery client fetches entire registry rather than information about specific services. Eureka 2 architecture looks better in that regard where one only gets data about services they are interested in.
Jersey 1 - Lets update to Jersey 2.X folks!
Eureka UI looks pretty horrid and that is coming from a 'server side' only programmer :-).
There is an issue where shutting down a service instance leads to shutting down all other services on that host which is concerning if hosting multiple services on a node.
Ocelli vs. Ribbon. Can we have clear direction? Ocelli has not had a release since July 10th, Ribbon though has a more recent release. So is Ocelli really the new Ribbon?
Ocelli vs. Ribbon. Can we have clear direction? Ocelli has not had a release since July 10th, Ribbon though has a more recent release. So is Ocelli really the new Ribbon?
Spring Cloud Netflix
The good -
If using a Spring ecosystem, a no brainer to adopt. Meshes really well with Spring properties and enables Hystrix and other Netflix OSS.
Spring Boot is pretty sweet but you need to work in a 'controlled' environment in the 'Spring Boot Way' to enjoy full benefit.
Spring Boot is pretty sweet but you need to work in a 'controlled' environment in the 'Spring Boot Way' to enjoy full benefit.
Spring Cloud Netflix re-bundled Eureka server is much more visually pleasing than the core Eureka UI. I believe they also have a workaround with reference to the Shutdown bug I mentioned.
The bad -
You sold yourself to the Bootiful devil :-)
Leaning back on my reactive example, lets say I have an aggregator Product Service that I want to test that utilizes a Base Product Service, a Price Service and an Inventory Service, all developed with Spring Cloud Eureka, how would I write a maven integration test for this? Off to Docker land my friends, onward and upward!
- Custom extension classes of Netflix interfaces (EurekaClientConfigBean..etc), rather than bridging
- Version of Eureka is older
- Documentation does not match up with implementation. Case in point the property "spring.cloud.client.hostname" defined in their documentation is not even part of current release. I spent some time chasing this
- Do we really need Lombok?
Leaning back on my reactive example, lets say I have an aggregator Product Service that I want to test that utilizes a Base Product Service, a Price Service and an Inventory Service, all developed with Spring Cloud Eureka, how would I write a maven integration test for this? Off to Docker land my friends, onward and upward!