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.

Easy Python 3.9+ Aug 9, 2026 System design patterns 13 views 0 copies

Python code

36 lines
Python 3.9+
class 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

stdout
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

  1. Use a `@dataclass` for the service entry instead of a plain dict
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.