Hey there, Rustaceans! 🦀
Remember when we embarked on that thrilling journey of crafting an API Gateway in Rust? If you missed it, don’t worry! You can catch up with the first part of our adventure right here. It laid the groundwork, diving into the core concepts and constructing the initial functionalities of our gateway.
Ready for round two? This time, we’re leveling up by introducing a dynamic service registry. No more hardcoded addresses; we’re talking about flexibility, scalability, and all the jazz that comes with dynamically discovering and routing to services. 🚀
So, tighten up those coding gloves, grab a cup of your favorite brew, and let’s jump right into part two!
Why Do We Need Dynamic Service Registration?
In microservices-heavy ecosystems, services come and go. Some are scaled up in response to high loads, while others might be momentarily down for maintenance. Manually keeping track of which service runs where becomes a Herculean task. Enter dynamic service registration: a system where services autonomously register their location, making the act of discovery and routing fluid and automated.
Breathing Life into the Dynamic Service Registry
Structuring the Service Registry
Let’s start by encapsulating services and their locations:
struct ServiceRegistry {
services: Arc<RwLock<HashMap<String, String>>>, // Service Name -> Service Address (URL/URI)
}
impl ServiceRegistry {
fn new() -> Self {
ServiceRegistry {
services: Arc::new(RwLock::new(HashMap::new())),
}
}
fn register(&self, name: String, address: String) {
let mut services = self.services.write().unwrap();
services.insert(name, address);
}
fn deregister(&self, name: &str) {
let mut services = self.services.write().unwrap();
services.remove(name);
}
fn get_address(&self, name: &str) -> Option<String> {
let services = self.services.read().unwrap();
services.get(name).cloned()
}
}
Crafting Registration Endpoints
Our API Gateway needs to expose endpoints allowing services to announce or rescind their presence:
async fn register_service(req: Request<Body>, registry: Arc<ServiceRegistry>) -> Result<Response<Body>, hyper::Error> {
// ... parsing and registration logic ...
Ok(Response::new(Body::from("Service registered successfully")))
}
async fn deregister_service(req: Request<Body>, registry: Arc<ServiceRegistry>) -> Result<Response<Body>, hyper::Error> {
// ... parsing and deregistration logic ...
Ok(Response::new(Body::from("Service deregistered successfully")))
}
Main Function Tweaks
The main function needs to be aware of these new endpoints:
let make_svc = make_service_fn(move |_conn| {
let registry = Arc::clone(®istry);
async move {
Ok::<_, hyper::Error>(service_fn(move |req| {
let path = req.uri().path();
if path == "/register_service" {
return register_service(req, Arc::clone(®istry));
}
if path == "/deregister_service" {
return deregister_service(req, Arc::clone(®istry));
}
// ... other routes ...
}))
}
});
Testing Our New Service Registry with a Hello Service
Introducing the HelloService
Our HelloService is a basic Rust microservice. Its primary tasks are:
- Register itself with the API Gateway upon startup.
- Respond with a friendly “Hello from the Service!” when accessed.
Crafting the HelloService
1. The Basics
For starters, let’s get our dependencies in order. In our Cargo.toml, we'll have. We’re relying on `hyper` for our HTTP server and client functionalities, and `tokio` for asynchronous operations:
[dependencies]
hyper = "0.14"
tokio = { version = "1", features = ["full"] }
2. Service Logic
Our service will have a simple endpoint that replies with a welcoming JSON message:
async fn handle_hello(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
Ok(Response::new(Body::from(r#"{"message": "Hello from the Service!"}"#)))
}
3. Registration Magic
Upon startup, the HelloService reaches out to our API Gateway, announcing its presence:
let client = Client::new();
let req_body = format!("{},{}", SERVICE_NAME, SERVICE_ADDRESS);
let request = Request::builder()
.method("POST")
.uri(API_GATEWAY_ADDRESS)
.body(Body::from(req_body))
.unwrap();
if let Err(e) = client.request(request).await {
eprintln!("Failed to register the service: {}", e);
}
4. Bringing It All Together
Combine the service logic and registration magic under a Tokio runtime:
#[tokio::main]
async fn main() {
// Register with the API Gateway
// ... [registration code from above] ...
// Start the Hello Service
let make_svc = make_service_fn(|_conn| {
async { Ok::<_, hyper::Error>(service_fn(handle_hello)) }
});
let addr = ([127, 0, 0, 1], 9090).into(); // Service runs on port 9090
let server = Server::bind(&addr).serve(make_svc);
println!("Hello Service running on http://{}", addr);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
}



