How to Build a Simple Service Discovery Registry in Python
A lightweight in-memory service registry class using a dict — register, deregister, and discover services with host, port, and version.
Python code
36 linesclass ServiceRegistry:
def __init__(self):
self._services = {}
def register(self, name, host, port, version="1.0"):
self._services[name] = {
"host": host,
"port": port,
"version": version
}
def deregister(self, name):
return self._services.pop(name, None)
def discover(self, name):
service = self._services.get(name)
if service:
return f"{service['host']}:{service['port']} (v{service['version']})"
return None
def list_all(self):
return self._services
if __name__ == "__main__":
registry = ServiceRegistry()
registry.register("auth", "10.0.0.1", 3000)
registry.register("users", "10.0.0.2", 3001, version="2.3")
registry.register("orders", "10.0.0.3", 3002)
print(registry.discover("auth"))
print(registry.discover("users"))
print(registry.discover("missing"))
print(registry.list_all())
registry.deregister("orders")
print(registry.list_all())
Output
10.0.0.1:3000 (v1.0)
10.0.0.2:3001 (v2.3)
None
{'auth': {'host': '10.0.0.1', 'port': 3000, 'version': '1.0'}, 'users': {'host': '10.0.0.2', 'port': 3001, 'version': '2.3'}, 'orders': {'host': '10.0.0.3', 'port': 3002, 'version': '1.0'}}
{'auth': {'host': '10.0.0.1', 'port': 3000, 'version': '1.0'}, 'users': {'host': '10.0.0.2', 'port': 3001, 'version': '2.3'}}
How it works
The registry stores each service as a dict keyed by its name, keeping host, port, and version together. register either adds a new service or overwrites an existing one, so a caller can update endpoints in place. discover uses .get() (not []), which avoids a KeyError when the service is missing and returns None instead. deregister uses pop(name, None) so it is idempotent — removing a non-existent service does not raise. The dictionary is returned directly from list_all, which is fine for a mock but should return a copy in real code to prevent callers mutating the registry.
Common mistakes
- Using `self._services[name]` instead of `.pop(name, None)` in deregister, which raises KeyError for missing names
- Returning the internal dict directly from `list_all`, letting callers mutate the registry by accident
- Forgetting `.get()` in `discover` and raising KeyError when a service is not registered
- Hard-coding version defaults without validating that the service data is complete
Variations
- Use a `@dataclass` for the service entry instead of a plain dict
- Add a `health_check` callback that probes each service endpoint before returning it
Real-world use cases
- Stubbing an in-memory registry in unit tests so services can be registered and discovered without network calls.
- Teaching the core pattern of service discovery before introducing ZooKeeper or Consul in a microservices codebase.
- Starting a lightweight sidecar-style process that tracks internal microservice endpoints during local development.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.