Skip to content

First draft of dependency injection package#6

Merged
spoenemann merged 3 commits intomainfrom
dependency-injection
Oct 21, 2025
Merged

First draft of dependency injection package#6
spoenemann merged 3 commits intomainfrom
dependency-injection

Conversation

@spoenemann
Copy link
Copy Markdown
Member

@spoenemann spoenemann commented Oct 1, 2025

First Attempt (commits 1 & 2)

Concepts:

  • Distributed definition of service keys (in contrast to centralized definition in Langium)
  • Type-safe registration (binding) and retrieval of services
  • Decoupled creation and injection of services:
    1. Create and register all services (in arbitrary order)
    2. Call InjectAll to inject dependencies
    3. Retrieve services via Get
  • Since every service is created before it's registered, there's no need for lazy resolution
    • Bonus: circular dependencies are supported 🎉
  • If we want, we can add a concept of service modules later so it's easier to set up all the default services
    • Like in InversifyJS, a module could simply be a function that adds bindings to the container

Second attempt (commit 3)

Concepts:

  • Bundled definition of service keys similar to Langium
  • Services are not injected; instead, each service fetches its dependencies from the service container when it needs them
  • In some cases, a service could be added later; for example, Connection is set only when the language server is started
  • Default services are initialized with a respective function per package

@cursor
Copy link
Copy Markdown

cursor Bot commented Oct 1, 2025

You have run out of free Bugbot PR reviews for this billing cycle. This will reset on October 26.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link
Copy Markdown
Member

@danieldietrich danieldietrich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Miro,

I learned a bit Go while reading the code ;)

In Go, pointers that are not initialized reference to nil, however, this is not checked at the type level (read: compile time) and the runtime will just panic when dereferencing nil.

This perfectly allows to split service creation and injection! Especially it supports cyclic dependencies in a natural/direct way (because struct instances can be built step-by-step).

It is an interesting feature of Go that a struct can be augmented with an Interface - using a function. However, it is a bit confusing that Injectable is not explicitly mentioned (e.g. TestInjectableService has Inject but does not mention that it implements Injectable.

I asked Claude:

Go is statically typed, not duck-typed. However, it has a feature called interfaces that provides duck-typing-like flexibility at compile time.

I see now why there is no constructor injection. Because the Inject impl pulls all needed service dependencies out of the DI container. That is perfectly possible.


Thinking...

To the time of Google Guice I liked modules because they served as a central catalogue of services (easy to grasp for me).

I'm now wondering how this new DI concept works in practice - can one still get a clear picture of which services are available? The key to understanding and maintaining a good overview is having a central place for both the service keys and the service registrations at the container.

Update: dependency pointers must not change after injection. I guess that it is not a problem to have pointers to functions instead of structs? Or how are providers/factories modeled?


Overall this is a good approach that is very well tailored to the characteristics of the Go language! 👍

Comment thread inject/inject.go Outdated
Comment thread inject/inject.go Outdated
@spoenemann
Copy link
Copy Markdown
Member Author

@danieldietrich I started a second round before I saw your PR. I think they are similar.

Copy link
Copy Markdown
Member

@msujew msujew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like both approaches to DI in this PR. It's rather difficult to pick one, but if I had to, I would go with the second one:

  1. The typing is more explicit, which also prevents to register "invalid" services. This is a real concern in the first approach.
  2. IMO the Inject interface function approach is very object-oriented and not very idiomatic to go.
  3. Both approaches require some kind of casting in case an adopter overrides a service and wants to retrieve the type of that overriden service. As far as I can tell, there should be no runtime/compilation issues with that approach though.

@spoenemann spoenemann merged commit e557b12 into main Oct 21, 2025
3 checks passed
@spoenemann spoenemann deleted the dependency-injection branch October 21, 2025 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants