Lucee is an application server for the CFML (ColdFusion Markup Language) programming language, which I used for many years, initially with Railo and then with Lucee. Even though I now work exclusively with Java, I enjoy writing CFML code from time to time. Recently, I noticed that the concept of a service registry is not widely used in the CFML world, even though it offers numerous advantages. In this article, we will explore what a service registry is, focusing on Eureka Server, and how we can use it to register Lucee Instances with Eureka.
What is a Service Registry?
A service registry is an architectural pattern used to maintain a list of available services within a distributed system. It acts as a centralised directory where services can register and discover other services. This pattern is crucial in microservices architectures, where the number of services can be large, and the instances of each service can vary dynamically.
Benefits of a Service Registry
Scalability: Allows dynamic scaling of services without the need to manually configure each instance.
Resilience: Improves system resilience as services can automatically detect the failure of other instances and reroute requests accordingly.
Ease of Configuration: Reduces the complexity of configuring connections between services by centralising information about available services.
Monitoring and Management: Provides a central point for monitoring the status of service instances.
Eureka Server
Eureka Server is a discovery service developed by Netflix. It is one of the core components of the Netflix OSS project and is used to implement the service registry pattern. Eureka is designed to be highly available and scalable, offering advanced features such as replication between multiple Eureka instances to ensure data availability even in case of failures.
How Does Work Eureka
Service Registration: When a service starts, it sends a registration request to the Eureka Server. The request includes metadata about the service, such as the access URL, service name, and other relevant information.
Heartbeat: Service instances periodically send a “heartbeat” signal to Eureka to confirm they are still active. If Eureka does not receive a heartbeat within a certain time interval, it considers the instance unavailable and removes it from the registry.
Service Discovery: Client services can query Eureka to get the list of available instances for a particular service. This allows clients to dynamically find services without needing static configurations.
Problems Solved by Eureka
Managing Dynamic IP Addresses: In microservices architectures, service instances can be created and destroyed dynamically, and their IP addresses can change frequently. Eureka solves this problem by centralising information about service instances, allowing clients to discover active instances without needing to know their IP addresses in advance.
Load Balancing: Eureka facilitates load balancing between service instances. Clients can use the information provided by Eureka to distribute requests among available instances, improving load distribution and system scalability.
Automatic Failover: In case of a service instance failure, Eureka enables clients to quickly detect the unavailable instance and reroute requests to other active instances. This increases system resilience and reduces downtime.
Centralised Configuration: Eureka centralise the configuration of service information, reducing the complexity of managing connections between services. This is particularly useful in environments with many services that change frequently.
How to register a Lucee Instances with Eureka
application.yml
spring:
application:
name: eureka-server
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
Maven.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
SpringBoot Application
@EnableEurekaServer
@SpringBootApplication
public class DemoEurekaApplication {
public static void main ( String[] args ) {
SpringApplication.run( DemoEurekaApplication.class, args );
}
}
2024-07-11T16:31:09.826+02:00 INFO 3580 --- [eureka-server] [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2024-07-11T16:32:09.320+02:00 INFO 3580 --- [eureka-server] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 0ms
Integrate the Eureka REST Operations API
https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
component displayname="EurekaClient" hint="Client for Eureka Service Registry" {
// Configure the default Eureka server details
this.eurekaServerUrl = "http://localhost:8761/eureka";
this.appName = "MyCFMLApp";
this.instanceId = createUUID();
this.instancePort = cgi.server_port; // Use the server port from CGI
// Constructor to initialize the Eureka client with custom settings
public EurekaClient function init(struct config={}) {
if (structKeyExists(config, "eurekaServerUrl")) {
this.eurekaServerUrl = config.eurekaServerUrl;
}
if (structKeyExists(config, "appName")) {
this.appName = config.appName;
}
if (structKeyExists(config, "instanceId")) {
this.instanceId = config.instanceId;
}
if (structKeyExists(config, "instancePort")) {
this.instancePort = config.instancePort;
}
return this;
}
// Function to register the application with Eureka
public void function register() {
var registrationData = {
"instance": {
"instanceId": this.instanceId,
"hostName": cgi.server_name,
"app": this.appName,
"ipAddr": cgi.remote_addr,
"status": "UP",
"port": {
"$": this.instancePort,
"@enabled": "true"
},
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
}
}
};
var httpResponse = httpRequest(
method="POST",
endpoint="#this.eurekaServerUrl#/apps/#this.appName#",
headers={
"Content-Type" = "application/json"
},
body=serializeJSON(registrationData)
);
if (httpResponse.statusCode != 204) {
throw(type="Application", message="Failed to register with Eureka. Status code: #httpResponse.statusCode#");
}
}
// Function to send a heartbeat to Eureka
public void function sendHeartbeat() {
var httpResponse = httpRequest(
method="PUT",
endpoint="#this.eurekaServerUrl#/apps/#this.appName#/#this.instanceId#"
);
writeLog( text=httpResponse.statusCode, file="Heartbeat" );
if (httpResponse.statusCode != 200) {
throw(type="Application", message="Failed to send heartbeat to Eureka. Status code: #httpResponse.statusCode#");
}
}
// Function to get all registered instances
public struct function getRegisteredInstances() {
var httpResponse = httpRequest(
method="GET",
endpoint="#this.eurekaServerUrl#/apps",
headers={
"Accept": "application/json"
}
);
if (httpResponse.statusCode != 200) {
throw(type="Application", message="Failed to retrieve instances from Eureka. Status code: #httpResponse.statusCode#");
}
return deserializeJSON(httpResponse.fileContent);
}
// Function to get instances of a specific application
public struct function getInstancesByAppName(string appName) {
var httpResponse = httpRequest(
method="GET",
endpoint="#this.eurekaServerUrl#/apps/#appName#",
headers={
"Accept": "application/json"
}
);
if (httpResponse.statusCode != 200) {
throw(type="Application", message="Failed to retrieve instances from Eureka for app: #appName#. Status code: #httpResponse.statusCode#");
}
return deserializeJSON(httpResponse.fileContent);
}
// Helper function to send HTTP requests
private struct function httpRequest(string method, string endpoint, struct headers={}, string body="") {
var httpService = new Http();
httpService.setMethod(method);
httpService.setUrl(endpoint);
httpService.setCharset( "utf-8" );
for (var headerName in headers) {
httpService.addParam( type="header", name=headerName, value=headers[headerName] );
}
if (body != "") {
httpService.addParam( type="body", value=body );
}
return httpService.send().getPrefix();
}
}
config = {instanceid = "18C8F3FF-B0C3-4DBC-836915E8DBA55431"} ;
// registerLucee.cfm
// Register Lucee Server
eurekaClient = new EurekaClient( );
eurekaClient.register();
// heartbeat.cfm
// Heartbeat
eurekaClient = new EurekaClient( config );
eurekaClient.sendHeartbeat();
// eureka.cfm
eurekaClient = new EurekaClient( config );
instances = eurekaClient.getRegisteredInstances();
writeDump( instances );
http://localhost:8888/eureka.cfm
Comments are closed