A service locator that knows what it registered. (Work in progress)
Part of the Kiit framework · kiit.dev/registry · Blog post · Video walkthrough
- ℹ️ About
- 🧩 The problem
- 💡 The idea
- 🚀 Quick start
- 🧠 Core concepts
- 🤖 Why this matters for AI
- 🛠️ Use cases
- ✅ When to use this
- 📦 Requirements
- 🗺️ Roadmap
- 🤝 Contributing
- 📄 License
kiit.registry is a lightweight dependency/service registry for Kotlin. It does the job a typical DI container or service locator does — register a builder for a type, resolve an instance later — but it's built around one different idea: every registration carries explicit metadata about its role in your architecture, not just its type.
Concretely, that means a registration records things like: what kind of resource this is (infra, entity, repo, API, service), which module registered it, and how it's identified when there's more than one of a given type. That metadata isn't a side note — it's queryable, which is what makes the registry itself a live, accurate map of your app's architecture, usable by tooling, documentation generators, and AI assistants, not just your own application code at runtime.
It's a small, dependency-free library — you can adopt it on its own, independent of the rest of Kiit.
[diagram placeholder — before/after: opaque service locator vs. semantically-typed registry]
Figure 1. A typical registry resolves types but discards meaning. kiit.registry keeps the meaning.
Most DI containers and service locators are good at one thing: giving you back an instance when you ask for a type. What they don't do is remember what that thing is for.
Ask a typical container "what infrastructure does this app touch?" or "what's registered under the accounts module?" or "list every AWS resource this service depends on" — and the honest answer is: it can't tell you. That information lived in someone's head, or in a wiki page that's a year out of date, or nowhere at all.
That's an onboarding problem for humans. It's a much bigger problem for AI tooling trying to reason about your codebase — an assistant can read your registration code, but it can't distinguish "this is a repository" from "this is an HTTP client" from "this is a queue consumer" unless that distinction is actually encoded somewhere.
kiit.registry is a service locator where every registration carries an explicit type, category, and identity — so the registry itself becomes a queryable map of your architecture, not just a lookup table.
Nothing about how you use it should feel unfamiliar if you've used a DI container before. What's different is that registration is semantic by default, not just typed.
Gradle (Kotlin DSL):
dependencies {
implementation("dev.kiit:kiit-registry:0.1.0")
}Register something:
class UserModule(override val registry: Registry) : Module {
override val name: String = "users"
override fun register() {
register {
entity<User>("app", schema = "public", table = "user") { User.create() }
repo<SimpleRepo<User>>(User::class) { SimpleRepo<User>() }
service<UserService>("", "general", "accounts", "user", "1") {
UserService(get<SimpleRepo<User>>(User::class))
}
}
}
}Resolve it:
val userService = registry.resolve<UserService>()Inspect it:
val allInfra = registry.inspect(ResourceKind.Infra)
val fromUsersModule = registry.inspect(module = "users")That's the whole loop: register with meaning attached, resolve like normal, inspect when you need to know what's actually there.
| Term | What it is |
|---|---|
| ResourceId | A resource's identity — company, kind/category (e.g. aws / queue), type, name, and an optional qualifier to disambiguate resources that share a type. |
| Resource | A ResourceId plus a lazy builder function, plus metadata (singleton or not, which module registered it, how long it took to build). |
| Resources | Storage and lookup for all resources, plus bulk "this folder is a set of X" declarations. |
| Registry | Owns storage and the low-level write/override primitives. |
| RegistryScope | The write surface a module actually uses to register things — infra, entities, APIs, repos, config, and more. |
| Resolver | Read-only lookup by type, with an optional qualifier for disambiguation. |
| Inspector | Read-only bulk lookup — everything of a kind, everything from a module — plus simple diagnostics. |
| Module | A logical group of resources that registers through its own scope. |
Because every registration is explicitly typed and categorized, an AI assistant working in a codebase that uses kiit.registry can answer questions a typical container leaves opaque:
- "What infrastructure does this service depend on?"
- "Which module owns the
UserService?" - "What's registered as an API vs. an internal service?"
This is the same information a human would otherwise have to reconstruct by reading constructor chains across files. With kiit.registry, it's queryable directly — which is also what powers Kiit's architecture-aware tooling (generating README/architecture docs from what's actually registered, not what's documented separately and likely stale).
- Application wiring — resolve dependencies by type instead of wiring constructors by hand.
- Tooling — read everything registered, by kind, category, or module, for external tools (or AI assistants) to inspect.
- Diagnostics — track how long each resource took to build.
- Testing — override one or two registrations with mocks without touching the rest of your setup.
- Bulk registration — declare "this folder is a set of X" instead of registering many similar things one at a time.
Good fit if:
- You want dependency wiring and an accurate, queryable picture of your architecture as a side effect.
- You're building tooling, documentation generation, or AI-assisted workflows that need to reason about what your app actually contains.
- You'd rather register explicitly than rely on classpath scanning or annotation magic.
Probably not necessary if:
- Your app is small enough that manual wiring is genuinely simpler.
- You need Android-specific lifecycle-aware DI (Koin/Hilt are more purpose-built for that today).
- You want zero ceremony at the registration site — explicit typing means slightly more to write per registration, in exchange for the registry knowing what it holds.
We'd rather be upfront about that tradeoff than pretend there isn't one.
- Kotlin, JVM
- No external runtime dependencies
(Multiplatform support is on the roadmap — not yet available.)
- Kotlin Multiplatform support
- Additional artifact-generation skills (README/architecture.md generation from registry contents)
- Expanded inspector/diagnostics API
Track progress or open a discussion in Issues.
Contributions are welcome — see CONTRIBUTING.md for setup, build, and PR guidelines.
kiit.registry is one module of Kiit — a lightweight, modular, 100% Kotlin framework for building server apps, APIs, CLIs, and jobs. Adopt one module at a time.