I can't think in three cases.
Lagom service consumes another Lagom service in the same cluster
Lagom service consumes another Lagom service in a different cluster
Lagom service consumes an external non-Lagom service
An external non-Lagom service consumes a Lagom service
1. Lagom service consumes another Lagom service in the same cluster
For this case the approach is that ServiceAImpl depends on the ServiceB API, which is binded to a concrete implementation that will be injected to ServiceAImpl.
ServiceB binding:
import com.google.inject.AbstractModule;
import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport;
import docs.services.HelloService;
public class Module extends AbstractModule implements ServiceGuiceSupport {
protected void configure() {
bindClient(HelloService.class);
}
}
ServiceA implementation:
public class MyServiceImpl implements MyService {
private final HelloService helloService;
#Inject
public MyServiceImpl(HelloService helloService) {
this.helloService = helloService;
}
#Override
public ServiceCall<NotUsed, NotUsed, String> sayHelloLagom() {
return (id, msg) -> {
CompletionStage<String> response = helloService.sayHello().invoke("Lagom");
return response.thenApply(answer ->
"Hello service said: " + answer
);
};
}
}
If I understand it correctly, in order to consume the service API in this way, both clients must be in the same cluster.
However Lagom says that
A cluster should only span nodes that are running the same service.
In this case we have two different types of services.
"The same service" means a top level service whose API is exposed to external services?
In Lagom 1 Microservice = 1 service with external API + n internal services?
2. Lagom service consumes another Lagom service in a different cluster
The documentation says:
Note that if the service you want to communicate with is actually a Lagom service, you may want to read the documentation for integrating with an external Lagom projects.
Why is only configured the dependency to the service API and not the IP and port of the external Lagom service also?
3. Lagom service consumes an external non-Lagom service
The first thing you will have to do is to register each external
service in the Service Locator. Assume we want to register an external
service named weather that is running on http://localhost:3333, here
is what we would add to the build:
lagomUnmanagedServices in ThisBuild := Map("weather" -> "http://localhost:3333")
What is the contract with that IP? What should be behind it?
4. An external non-Lagom service consumes a Lagom service
I have to use the Third-Party Registration Pattern until Lagom support the self registration pattern?
When Lagom talks about "cluster", it's referring to Akka clusters. Each service may be deployed as an Akka cluster, that is, a service may be a cluster of nodes. So you don't have multiple services in a cluster, you just have one clustered service.
Lagom service calls map down in a fairly straight forward fashion to idiomatic REST. So, when talking to an external service, the thing at that IP should be a REST service. Likewise, when an external service is talking to Lagom, it should use REST.
Related
I am trying to implement a shopping cart checkout() service that needs to make a call into two other rest services , one is to call products REST service to check final prices and then another is to make payment via Payments REST service. Should I be making these REST calls in my spring controller or spring service class?
It is best to separate the different concerns within the service itself. The controller in its nature, is responsible for mapping http requests and should not contain business logic as such.
It would be best to move the REST calls to the service layer of the checkout service.
However there is still room for improvement. Instead of the service layer making the REST calls, a sort of communicator module can be used to make the REST calls or handle the communication to the other services. Because today we might be using REST, tomorrow we can move to use gRPC etc. So it would be best to move the communications to another module.
Some code to bootstrap the structure;
Checkout controller
class CheckoutController {
#Autowired
private CheckoutService checkoutService;
#PostMapping("/checkout")
public string performCheckout(CheckoutDto checkoutDto){
checkoutService.performCheckout(checkoutDto);
}
}
Checkout service
class CheckoutService {
#Autowired
private PriceCheckerCommunicator priceCheckerCommunicator;
void performCheckout(CheckoutDto checkoutDto){
priceCheckerCommunicator.checkPrices(checkoutDto.getProductIds());
// the same will be done with the payment service as well
}
}
Price checker communicator
interface PriceCheckerCommunicator {
// the implementation of the interface will be calling the price checker service using REST call
void checkPrices(List<Long> productIds);
}
Here is an overview of the overall architecture;
I have an external requirement that I provide an endpoint to tell the load balancer to send traffic to my app. Much like the Kubernetes "readiness" probe, but it has to be a certain format and path, so I can just give them the actuator health endpoint.
In the past I've used the HealthEndpoint and called health(), but that doesn't work for reactive apps. Is there a more flexible way to see if the app is "UP"? At this level I don't care if it's reactive or servlet, I just want to know what Spring Boot says about the app.
I haven't found anything like this, most articles talk about calling /actuator/health, but that isn't what I need.
Edit:
Just a bit more detail, I have to return a certain string "NS_ENABLE" if it's good. There are certain conditions where I return "NS_DISABLE", so I can't just not return anything, which would normally make sense.
Also, I really like how Spring Boot does the checking for me. I'd rather not re-implement all those checks.
Edit 2: My final solution
The answers below got me very far along even though it wasn't my final solution, so I wanted to give a hint to my final understanding.
It turns out that the HealthEndpoint works for reactive apps just as well as servlet apps, you just have to wrap them in Mono.
How do we define health of any web servers?
We look at how our dependent services are, we check the status of Redis, MySQL, MongoDB, ElasticSearch, and other databases, this's what actuator does internally.
Actuator checks the status of different databases and based on that it returns Up/Down.
You can implement your own methods that would check the health of dependent services.
Redis is healthy or not can be checked using ping command
MySQL can be verified using SELECT 1 command or run some query that should always success like SHOW TABLES
Similarly, you can implement a health check for other services. If you find all required services are up then you can declare up otherwise down.
What about shutdown triggers? Whenever your server receives a shutdown signal than no matter what's the state of your dependent services, you should always say down, so that upstream won't send a call to this instance.
Edit
The health of the entire spring app can be checked programmatically by autowiring one or more beans from the Actuator module.
#Controller
public class MyHealthController{
#Autowired private HealthEndpoint healthEndpoint;
#GetMapping("health")
public Health health() {
Health health = healthEndpoint.health();
return healthEndpoint.health();
}
}
There're other beans related to health check, we can auto wire required beans. Some of the beans provide the health of the respective component, we can combine the health of each component using HealthAggregator to get the final Health. All registered health indicator components can be accessed via HealthIndicatorRegistry.
#Controller
public class MyHealthController{
#Autowired private HealthAggregator healthAggregator;
#Autowired private HealthIndicatorRegistry healthIndicatorRegistry;
#GetMapping("health")
public Health health() {
Map<String, Health> health = new HashMap<>();
for (Entry<String, HealthIndicator> entry : healthIndicatorRegistry.getAll().entrySet()) {
health.put(entry.getKey(), entry.getValue().health());
}
return healthAggregator.aggregate(health);
}
}
NOTE: Reactive component has its own health indicator. Useful classes are ReactiveHealthIndicatorRegistry, ReactiveHealthIndicator etc
Simple solution is to write your own health endpoint instead of depending on Spring.
Spring Boot provides you production-ready endpoints but if it doesn't satisfy your purpose, write your end-point. It will just return "UP" in response. If the service is down, it will not return anything.
Here's the spring boot documentation on writing reactive health endpoints. Folow the guide and should be enough for your usecase.
They also document on how to write liveliness and Readiness of your application.
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#reactive-health-indicators
I have Kubernetes running on two nodes and one application deployed on the two nodes (two pods, one per node).
It's a Spring Boot application. It uses OpenFeign for service discoverability. In the app i have a RestController defined and it has a few APIs and an #Autowired #Service which is called from inside the APIs.
Whenever i do a request on one of the APIs Kubernetes uses some sort of load-balancing to route the traffic to one of the pods, and the apps RestController is called. This is fine and i want this to be load-balanced.
The problem happens once that API is called and it calls the #Autowired #Service. Somehow this too gets load-balanced and the call to the #Service might end up on the other node.
Heres and example:
we have two nodes: node1, node2
we make a request to node1's IP address.
this might get load-balanced to node2 (this is fine)
node1 gets the request and calls the #Autowired #Service
the call jumps to node2 (this is where the problem happens)
And in code:
Controller:
#Autowired
private lateinit var userService: UserService
#PostMapping("/getUser")
fun uploadNewPC(#RequestParam("userId") userId: String): User {
println(System.getEnv("hostIP")) //123.45.67.01
return userService.getUser(userId)
}
Service:
#Service
class UserService {
fun getUser(userId: String) : User {
println(System.getEnv("hostIP")) //123.45.67.02
...
}
}
I want the load-balancing to happen only on the REST requests not the internal calls of the app to its #Service components. How would i achieve this? Is there any configuration to the way Spring Boot's #service components operate in Kubernetes clusters? Can i change this?
Thanks in advance.
Edit:
After some debugging i found that It wasn't the Service that was load balanced to another node but the initial http request. Even though the request is specifically send to the url of node1... And since i was debugging both nodes at the same time, i didn't notice this.
Well, I haven't used openfeign, but in my understanding it can loadbalance only REST requests indeed.
If I've got your question right, you say that when the REST controller calls the service component (UserService in this case) the network call is issued and this is undesirable.
In this case, I believe, following points for consideration will be beneficial:
Spring boot has nothing to do with load balancing at this level by default, it should be a configured in the spring boot application somehow.
This also has nothing to do with the fact that this application runs in a Kubernetes environment, again its only a spring boot configuration.
Assuming, you have a UserService interface that obviously doesn't have any load balancing logic, spring boot must wrap it into some kind of proxy that adds these capabilities. So try to debug the application startup, place a breakpoint in the controller method and check out what is the actual type of the user service, again it must be some sort of proxy
If the assumption in 3 is correct, there must be some kind of bean post processor (possibly in spring.factories file of some dependency) class that gets registered within the application context. Probably if you'll create some custom method that will print all beans (Bean Post Processor is also a bean), you'll see the suspicious bean.
I have the following services:
EurekaServer - hosts the eureka discovery server
Client-service - registers to EurekaServer
Finder-service - registers to EurekaServer
Is there a way to get Client-service's ip so I can make requests to it from the Finder-service.
I know there is a way to find InstanceInfo from EurekaServer and I was thinking of making a controller in eureka server where you pass service id and get service's instance ip. This way Finder-service would only need to know service id and eureka ip which it knows because it is registered there.
Is there another solution which is cleaner than this?
To use client discovery you first need to enable by adding either #EnableEurekaClient or #EnableDiscoveryClient to your #SpringBootApplication annotated class (or your specialized #Configuration).
Next to make use of the resolution of the actual service instance to use you need to create a RestTemplate which is load-balanced. This will add an interceptor which translates the service name into the ip-address / DNS name to send the request to (if multiple instances are found it will distribute the load between those instances).
To create a load balanced RestTemplate add the #LoadBalanced annotation to the configured RestTemplate.
#Bean
#LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder rtb) {
return rtb.build();
}
Now when doing a request through the RestTemplate it will resolve the service name client-service to the actual service instance to use. Without you having to do anything.
I have hosted 3 microservices in PCF. One is a eureka server and the other 2 are client and service microservices. The client is supposed to call the service through a rest template call and service will return a string.
I have 1 instance each of the eureka server and the client and 2 instances of the service.
I can see both my client and service registered in the Eureka dashboard. But when i try to access the service from the client[Using a rest template call] I get - 'No instances available for the [service name]'
But if i access my service directly from browser then its works fine and returns the string. But the same URL if called from a rest template returns the exception i mentioned above.
Any suggestions will help
Did you add the #LoadBalance on restTemplate bean.
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
And when calling service you have to use the service name as below.
https://SERVICENAME/restpath