Microservices Using Spring Cloud Netflix - Eureka With Demo

1. Introduction

Microservice architecture is a buzzword now-a days. As opposed to traditional monolithic applications, an application is deployed as a suite of smaller and independent applications each one managing a separate business operation. These mini-applications can be independently developed (even in different languages), scaled up/down and deployed on single server or across different servers.


In order to accomplish a functionality, it’s often required for these services to communicate with each other. They typically communicate using REST API.In order for service A to access APIs of service B it has to know its host and port. Hardcoding it is not the right way, since this means they are tightly coupled.

Webp.net-gifmaker

The solution here is to have an equivalent of a Telephone Directory for services, where each service registers itself and Our Java programmers in India can simply fetch the desired service by its name.

There are various popular service registries, like Netflix Eureka, CONSOL and Zookeeper. In this article, we’ll focus on Netflix Eureka.



2. Netflix Eureka

Netflix Eureka is a popular service registry and discovery service.
Netflix built it and later open sourced it.


Following are its salient features:

3. Project

In this project, we will be creating 3 Microservice:

These services will be created using Spring-Boot as well as we’ll be using Spring-Cloud configuration.


3.1 Eureka Server:


To implement the Eureka server, all we need to do is:


The project structure is as follows:

3.1.1 Maven Dependencies


<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hemant</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

3.1.2 Spring boot application class


package com.hemant.eurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }

3.1.3 application.yaml


In addition to above, we have to specify the following configuration in application.yaml file (we can alternatively use application. Properties file).


# Server port server: port: 8761 eureka: client: # Dont register itself with eureka registerWithEureka: false fetchRegistry: false

Explanation:

3.2 User Service

This is a simple spring boot service which acts as a Eureka Client and gets registered with our Eureka Server.


The project structure is as follows:

3.2.1 Maven Dependencies


<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hemant</groupId> <artifactId>user-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>user-service</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

3.2.2 Sping boot Application class


package com.hemant.userservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }

The convenience annotation @EnableEurekaClient makes this application as a Eureka Client which can be registered with our Eureka Server.


3.2.3 REST API


This application also exposes a few REST END points.
The model is a simple User class:


package com.hemant.userservice.model; public class User { private String id; private String name; private String email; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof User)) return false; User other = (User) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", email=" + email + "]"; } public User(String id, String name, String email) { super(); this.id = id; this.name = name; this.email = email; } }

The REST Controller exposing the User based endpoints is as follows:


package com.hemant.userservice.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.annotation.PostConstruct; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.hemant.userservice.model.User; @RestController public class UserController { private Map<String, User> userMap; @PostConstruct public void initMap() { userMap = new HashMap<>(); for (int i = 1; i <= 5; i++) { String uuid = UUID.randomUUID().toString(); userMap.put(uuid, new User(uuid, "User" + i, "user" + i + "@gmail.com")); } } @RequestMapping(value = "", method = RequestMethod.GET) public List<User> getAllUsers() { return new ArrayList<>(userMap.values()); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUserById(@PathVariable("id") String id) { User user = userMap.get(id); if (null == user) { throw new IllegalArgumentException("User not found for : " + id); } return user; } }

Here the mock data is created using @PostConstruct and 2 endpoints are provided


3.2.4 Application properties


server : port : 8080 spring : application : name : user-service eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka

Explanation:

3.3 Info Service


This is another Eureka Client service similar to the User Service.
However it provides additional endpoints which:


The project structure is as follows:

3.3.1 Maven dependencies


<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hemant</groupId> <artifactId>info-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>info-service</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring.cloud.version>1.2.6.RELEASE</spring.cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>${spring.cloud.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

3.3.2 Spring boot application class


package com.hemant.info; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class InfoServiceApp { public static void main(String[] args) { SpringApplication.run(InfoServiceApp.class, args); } }

3.3.3 Controllers

Here 2 controllers are provides


1. EurekaClientController : This provides information about the Eureka


package com.hemant.info.controller; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/eureka") public class EurekaClientController { @Autowired private DiscoveryClient discoveryClient; @RequestMapping(value = "", method = RequestMethod.GET) public List<String> getAllEurekaClients() { return discoveryClient.getServices(); } @RequestMapping(value = "/{serviceName}", method = RequestMethod.GET) public Map<String, Object> getEurekaClientURLByName(@PathVariable String serviceName) { List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName); if (serviceInstances.isEmpty()) { throw new IllegalArgumentException("No instance found with serviceId :" + serviceName); } ServiceInstance instance = serviceInstances.get(0); Map<String, Object> infoMap = new LinkedHashMap<>(); infoMap.put("host", instance.getHost()); infoMap.put("port", instance.getPort()); infoMap.put("uri", instance.getUri()); infoMap.put("serviceId", instance.getServiceId()); infoMap.put("secured", instance.isSecure()); return infoMap; } }

2. UserClientController : This consumes the User service’s endpoints.


package com.hemant.info.controller; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserClientController { @Autowired private DiscoveryClient discoveryClient; private RestTemplate restTemplate = new RestTemplate(); private String userServiceName = "user-service"; @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public Map<String, String> getUserById(@PathVariable String id) { List<ServiceInstance> serviceInstances = discoveryClient.getInstances(userServiceName); if (serviceInstances.isEmpty()) { throw new IllegalArgumentException("No instance found with serviceId :" + userServiceName); } ServiceInstance userServiceInstance = serviceInstances.get(0); String url = userServiceInstance.getUri().toString() + "/" + id; ResponseEntity<String> authResponse = restTemplate.exchange(url, HttpMethod.GET, null, String.class); Map<String, String> map = new LinkedHashMap<>(); map.put("url", url); map.put("user-service-response", authResponse.getBody()); return map; } }

The userService’sServiceId (spring.application.name in itsapplication.properties) i.e. “user-service” is used to query the DiscoverClient instance so as to fetch the instance of UserService. The only hard-coding here is serviceId. The other details like URL, PORT etc. is maintained by Eureka.


3.3.4 Application properties


server.port : 8081 spring.application.name : info-service eureka.client.serviceUrl.defaultZone: http://localhost:8761/eureka

Explanation:

4. Starting the microservices :

4.1 Starting Eureka Server

1. Start the Eureka Server application, go to the project’s root directory and execute

>>>mvn clean install

2. If the build is success, we can launch the application by:


2018-07-26 23:47:47.792 INFO 5715 --- [ main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 8761 2018-07-26 23:47:47.801 INFO 5715 --- [ main] c.h.e.EurekaServerApplication : Started EurekaServerApplication in 13.399 seconds (JVM running for 14.281)

You can access, the Eureka Server console at http://localhost:8761/

5. Presently no client services are started.


4.2 Starting User Service :


The steps for starting this Eureka Client service are:

1. Go to the project’s root directory and execute


>>>mvn clean install

2. If the build is success, we can launch the application by:


>>>java -jar target/user-service-0.0.1-SNAPSHOT.jar

3. The following logs indicate that application is up and running and also registered with Eureka server.


2018-07-26 23:53:19.068 INFO 5752 --- [ main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 8080 2018-07-26 23:53:19.080 INFO 5752 --- [ main] c.h.userservice.UserServiceApplication : Started UserServiceApplication in 10.22 seconds (JVM running for 11.243) 2018-07-26 23:53:19.173 INFO 5752 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER- SERVICE/192.168.1.117:user-service:8080 - registration status: 204

4. At the same point, in Eureka Server logs, you can see that User service has registered itself.


2018-07-26 23:53:19.165 INFO 5715 --- [nio-8761-exec-4] c.n.e.registry.AbstractInstanceRegistry : Registered instance USER- SERVICE/192.168.1.117:user-service:8080 with status UP (replication=false)

5. Upon refreshing the Eureka server console (http://localhost:8761/), you can see the UserService is now available.


4.3 Starting InfoService :


The steps are similar to that of starting of UserService.
After starting Info Service, it should be registered with Eureka and Eureka console on refresh should reflect the same


5. Demonstration

1. Access the User Service endpoints individually


GET http://localhost:8080/ Response: [ { "id": "30f9f24e-538b-458c-aa9b-1e06749ced52", "name": "User1", "email": "user1@gmail.com" }, { "id": "b514af00-2d32-4fe6-b9c1-43aeb83d58d4", "name": "User2", "email": "user2@gmail.com" }, { "id": "2ab01121-0b71-4581-a127-ad46c20f67a3", "name": "User4", "email": "user4@gmail.com" }, { "id": "d994c3ce-153f-4eaf-9aca-f2ae9a3735d3", "name": "User5", "email": "user5@gmail.com" }, { "id": "6dc503a1-2f59-4712-aac2-e2cc5496f303", "name": "User3", "email": "user3@gmail.com" } ] --------------------------------------------------------- GET http://localhost:8080/6dc503a1-2f59-4712-aac2-e2cc5496f303 Response: { "id": "6dc503a1-2f59-4712-aac2-e2cc5496f303", "name": "User3", "email": "user3@gmail.com" }

2. Access the info-service endpoint to give information on the client services currently registered on Eureka Server.


GET http://localhost:8081/eureka Response: [ "user-service", "info-service" ]

This gives the list of serviceIds (spring application names) which are registered with Eureka.


3. Access the API which provides more information on particular service, whose names is passed as path-variable


GET http://localhost:8081/eureka/user-service Response { "host": "192.168.1.117", "port": 8080, "uri": "http://192.168.1.117:8080", "serviceId": "USER-SERVICE", "secured": false } GET http://localhost:8081/eureka/demo-service Response { "timestamp": 1532630674705, "status": 500, "error": "Internal Server Error", "exception": "java.lang.IllegalArgumentException", "message": "No instance found with serviceId :demo-service", "path": "/eureka/demo-service" } Here since no service by serviceId "demo-service" is registered on Eureka, it's instance couldn't be found.

4. Access the UserClientController’s API. This will inturn fetch us the instance of UserService (running on 8080) from Eureka and call its API.


GET http://localhost:8081/user/6dc503a1-2f59-4712-aac2-e2cc5496f303 Response { "url": "http://192.168.1.117:8080/6dc503a1-2f59-4712-aac2-e2cc5496f303", "user-service-response": "{\"id\":\"6dc503a1-2f59-4712-aac2-e2cc5496f303\",\"name\":\"User3\",\"email\":\"user3@gmail.com\"}" }

6. Conclusion:

Thus we have implemented the Service registry and Discovery using Spring Cloud Netflix.

Tutorial : Plugin System in Magento 2

Tutorial : Plugin System in Magento 2

Magneto development services provider will explain plugin system in Magento 2. You will learn the way to use magento 2 plugins from the basics. Read and discover how professionals do it.

You will learn through this article how to use Magento 2 plugins that modify the behavior of all public functions without overloading the PHP class (block, model etc.) the container.

What are plugins?
To avoid having to override a class to modify a simple, Magento 2 plugins provides a mechanism to effectively manage this type of "rewriting" as well as to minimize conflicts between extensions.

Limitations plugins
To date, plugins can only be used for rewriting public methods and are therefore subject to the following limitations:
1) final methods
2) non-public methods (protected or private)
3) static methods
4) Constructor ( _construct)
5) virtual Types

Types plugin
Magento provides three "types" of different plugins:
1) before: Change the arguments provided to a method
2) around: Change the behavior of a method
3) after: To "rework" the output of a method

Declaration of a plugin
The declaration of any plugin must be done in the file di.xml of your module. This file can be placed, depending on its use, either within the directory etcyour module (plugin to use the backend and frontend) or in etc/frontend

(limited use in frontend) or etc/adminhtml(limited use in backend):

<config> <typename="{observer_type}"> <pluginname="{plugin_name}"type="{plugin_type}"sortOrder="{plugin_order}"disabled="{plugin_disabled}"/> </type> </config>

The following can be specified:
1) observer_type: The class name, or virtual interface type you want to observe
2) plugin_name: The name of the plugin (eg. catalog_product_custom_plugin)
3) plugin_type: The name of the class or type used by the virtual module (ex.Vendor\Module\Plugin\ModelNamePlugin)
4) plugin_order: The order in which the plugin will run. Very useful to effectively manage the execution order of several plugins overloading the same method (see "Execution Order" below for details)
5) plugin_disabled: This parameter can be set trueif you want to disable the plugin

Execution order of the plugins

When multiple plugins rewriting the same method exist, the following order of performance will be followed by Magento:
1. The type of plugin beforethat has the highest priority (= the one with the least sortOrder)
2. The type of plugin aroundthat has the highest priority (= the one with the least sortOrder)
3. The other type of plugins beforebased on their priority (from smallest to largest sortOrder)
4. The other type of plugins aroundbased on their priority (from smallest to largest sortOrder)
5. The type of plugin afterthat has the lowest priority (= the one with the largest sortOrder)
6. The other type of plugins afterbased on their priority (largest to smallest sortOrder)

Example plugins
before
To change the settings passed to a function, it is necessary to create a methodbefore[methodName]in your plugin (which [methodName]corresponds to not the method you want to change, for example SetNamefor a method named setName).

The method created within your plugin parameter must take the class which includes the function you want to change and its various parameters. Example of method overloading setNameclass \Magento\Catalog\Model\Product:

<?php namespaceMy\Module\Plugin; classProductPlugin { publicfunctionbeforeSetName(\Magento\Catalog\Model\Product $subject, $name) { return['('. $name. ')']; } }

In this example, the product name passed in the argument $namewill be automatically enclosed in parentheses.
As you have probably noticed, the type of plugins beforehave returned an array containing all the parameters of the function.

around
To change the behavior of a function, a method around[methodName]must be created within your plugin (which [methodName]corresponds to not the method you want to change, for exampleSavefor a method named save).

The method created within your plugin parameter must take the class which includes the function you want to change and a second type parameter \Closurecorresponding to the original method.

Example of method overloading saveclass \Magento\Catalog\Model\Product:

<?php namespaceMy\Module\Plugin; classProductPlugin { publicfunctionaroundSave(\Magento\Catalog\Model\Product $subject, \Closure $proceed) { $returnValue= $proceed(); if($returnValue) { $this->doSomething(); } return$returnValue; } }

In this example, we execute line 9 will recover the original method and the return value. If it is defined, a third function, called here doSomething, will be executed.

Finally, we return line 13 returning the original method.

after-
To change the return of a function, it is necessary to create a method afte