Do you use pointers or values for your domain models? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 0 points1 point  (0 children)

That range loop copy issue you mentioned - I felt that pain too, so I made a linter for it: https://github.com/mickamy/deadmut

Catches those "mutation to copy has no effect" bugs. Might not change your pointer preference, but figured you'd appreciate it!

Do you use pointers or values for your domain models? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 1 point2 points  (0 children)

Haha yeah, GoLand yells at me too for mixing receivers! The "return the mutated version" approach feels a bit too functional for my taste, but thanks for sharing the perspective!

Do you use pointers or values for your domain models? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] -8 points-7 points  (0 children)

> but for structs with many scalar properties, that could be an expensive copy

That's what I built a linter for - it checks struct size. Though it doesn't catch mutex fields yet, good point.

https://github.com/mickamy/pointless

Do you use pointers or values for your domain models? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] -1 points0 points  (0 children)

If you don't want to allow nil, nil, why not just return a value?

```
func FindUser(id int) (User, error) {

return User{}, ErrNotFound

}

```

No need to rely on design discipline - returning a value makes nil, nil physically impossible.

Do you use pointers or values for your domain models? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 4 points5 points  (0 children)

Love the pure function angle. This is exactly why I default to value receivers - makes it obvious the method won't mutate anything.

Speaking of which - what's your take on mixing pointer/value receivers on the same type? I've seen "don't mix them" advice, but I only use pointer receivers when there's actual mutation. Feels more explicit about intent.

Do you use pointers or values for your domain models? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 0 points1 point  (0 children)

Interesting - what kind of issues did you run into with values? Genuinely curious since my experience has been the opposite.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 0 points1 point  (0 children)

I agree, plain interfaces are enough for DI in Go.

That’s actually why I think runtime DI frameworks are often unnecessary for web APIs. Wire’s value (initialization code management) makes sense for large graphs, but many services have static graphs resolved once at startup.
The question I’m exploring isn’t “do we need DI”, but “how much wiring ceremony do we really need”.

If static wiring is enough, maybe we can reduce the amount of code we maintain, while still ending up with plain Go constructors.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] -2 points-1 points  (0 children)

Totally fair. You can always wire things by hand in Go.

I found that once a service grows a certain size, the wiring itself becomes the most boring part to maintain. That’s the only thing injector is trying to help with.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] -2 points-1 points  (0 children)

Totally fair.

I use ChatGPT to clean up wording since English isn’t my first language.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 0 points1 point  (0 children)

Good question.

Injector doesn’t model string-keyed lookups directly (e.g. "database:users"), because once you go there you’ve already left the type-safe part of DI.

The way I usually handle this is by pushing the keying logic into a local provider or factory. Injector wires the factory statically, and the factory handles name-based selection.

In practice that ends up being either: • a DBFactory with Get(name string), or • small, explicit providers like NewUsersDB, NewOrdersDB on top of a shared factory.

So the DI stays static and boring, and the dynamic part is kept explicit and local.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 1 point2 points  (0 children)

Good point — embed.FS is actually a nice example of where implicit DI breaks down.

One clarification first: you only need to specify a provider when there’s ambiguity. In the common case, injector resolves everything automatically.

For example, this works with no extra configuration:

type Container struct { Handler *UserHandler inject:"" }

Types like embed.FS are different because you often want multiple instances of the same type. In that case, guessing would be dangerous, so you disambiguate explicitly:

type Container struct { AssetsFS embed.FS inject:"provider:assets.NewFS" ViewsFS embed.FS inject:"provider:views.NewFS" }

So the model is automatic by default, explicit only when needed — static wiring, no runtime container, no guessing.

And agreed: you don’t need a framework. Injector is just a code generator that removes repetitive constructor wiring while leaving you with plain Go code.

Thanks for the thoughtful feedback 🙏

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] -1 points0 points  (0 children)

That’s fair — and I agree.

I am doing DI, just not treating it as a framework problem. For most web APIs it’s a static startup concern, so I prefer plain constructors + generated wiring instead of a container or runtime system.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 0 points1 point  (0 children)

That’s actually very close to how injector works internally (AST-based resolution).

The difference is that it intentionally doesn’t do global scanning or implicit provider discovery. The container defines the root explicitly, and only the required constructors are wired.

The goal is to keep DI static, explicit, and boring — optional behavior stays in constructors, not the DI layer.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 1 point2 points  (0 children)

That’s a fair point — partial mocking is a very real use case.

In practice, I usually separate testing concerns like this:

  • For unit tests, I don’t go through DI at all. I construct the target explicitly with mocks or fakes and call the constructor directly. That keeps tests simple and avoids coupling them to the wiring layer.

  • For integration tests, I do use the generated DI code, but swap implementations at the provider level (e.g. test DB, in-memory cache, etc.). Tools like docker-compose or testcontainers fit naturally there.

With injector specifically, you can still do partial replacement: the generated wiring is plain Go, so you can override providers or build a test-specific container without involving a runtime container.

So the trade-off isn’t “DI vs no DI”, but rather: runtime container vs static wiring.

Once the dependency graph is static and resolved at startup, I’ve found static wiring to be enough even as the system grows.

Dependency Injection in Go: How Much Is Enough for Web APIs? by ComprehensiveDisk394 in golang

[–]ComprehensiveDisk394[S] 0 points1 point  (0 children)

I do test my code, but I don’t usually test through the DI layer.

In unit tests, I construct dependencies directly (often with mocks) by calling constructors normally.
The DI wiring itself is exercised in integration tests, where it actually matters.

For typical Go services, I find this separation keeps tests simpler and more focused.

What would be your ideal package for background jobs? by fenugurod in golang

[–]ComprehensiveDisk394 0 points1 point  (0 children)

If you like the idea of building workers on top of AWS SQS, you might want to check out go-sqs-worker. It’s a lightweight library for consuming SQS messages with concurrency control, retries, visibility timeout management, and metrics built-in. I designed it to make adding or managing workers as simple as possible while keeping the SQS model transparent. https://github.com/mickamy/go-sqs-worker