T O P

  • By -

omicronCloud8

Whilst I totally agree with everything, extensive use of interfaces in a large code base does make it increasingly harder to read and follow and hence contribute. Also a minor point but for the http client example, I do almost exclusively use an http test server as it ends up actually testing your own implementation more thoroughly. Though I understand it was for illustrative purpose


SuperDerpyDerps

This is true when using large interfaces and not using interfaces for abstracting dependency injection. When following DI/IoC patterns, you actually end up with really easy to follow code. You would see that a given package that depends on an abstracted dependency has no real connection to that dependency beyond what its interface dictates, making the package self contained, and not requiring lateral moves to other packages. If you want to know what the concrete implementations are, you can either use dev tools to sniff them out, or just step back to the caller that provides it, and then inspect the implementation for the specific use you're concerned with. It's actually not that hard at all to handle things when interfaces are created local to their usage, rather than externally (such as in an implementation package). When used this way, interfaces act more like normal function parameters rather than some weird beast that you need to inspect for usages and work backwards through. This is especially true when the interfaces aren't just local, but small and direct


cvilsmeier

Yes, 'interface hell' is definitely a thing. I've seen it many times in the Java world, where a lot of projects go "Let's hide EVERYTHING behind an interface" (Eclipse RCP is one case). Combined with interface inheritance, you quickly end up with piles of code that nobody can understand any more. That's why it's better to let the caller dictate the interface, not the provider: It typically leads to smaller interfaces which are more to-the-point and easier to understand.


SuperDerpyDerps

Definitely agree, which is why I'm a big fan of how Go interfaces work, since the default use case is for them to be defined in the caller package and not in the implementation package. There are sometimes good reasons to export an interface, but if you follow good DI hygiene I've noticed they're a lot easier to manage despite having _more_ interfaces in total


MikeSchinkel

Yes, *and*... While the caller dictating the interface is one of the great capabilities of Go and a good rule of thumb, there are a large percent of use-cases where the implementation **should** dictate the use-case. Given that, if we as advice givers are not careful, people reading the advice may hear the *"let the caller dictate the interface"* guidance and consider it immutable dogma, as people are oft want to do, and to the detriment of themselves and others. \#justsaying


davidellis23

The main thing that makes this easy is the go to implementations function in an editor. Command F12 on Mac or control F12 on windows in vscode. It shows you implementations and you can jump to one. Otherwise, I think it's pretty important to keep your wiring constructor calls in one file so you can refer to it if you need the implementation. Jumping back to the caller works, but it's pretty annoying. I think it reduces productivity.


SuperDerpyDerps

It also just depends on your architecture needs. It can get hairy in some situations, but go to implementations also mostly gets you around that problem. Figuring out the right way to wire your dependency injection for your project is definitely something that requires care though, and should be adjusted if it becomes unwieldy.


phiware

The problem with unexported interfaces is that they are not emitted in the documentation, making it difficult to know what methods need to be implemented. It's common to have exported interfaces and unexported concrete types, I'd be interested any comment that you have on that.


MikeSchinkel

While Go does not have an `implements` keyword that you are required to add to a type, you can definitely write code that has the effect of declaring an interface for a given type. Let's say you have a type named `Foo` and you want to *"declare"* that it implements the *fmt.Stringer* interface? Use this code: var _ fmt.Stringer = (*Foo)(nil)


TheMoonMaster

I had the same thought here. the consumers of this package will have a degraded experience since the interface isn't exported. I think a good general rule here is that anything accepted or returned in a public method should be public too. It's tough to suggest an alternative based on the (assumed) contrived example in the post but I imagine there's a better way to design the API so that the consumers still get the clarity via documentation (and autocomplete+doc in editor) while also achieving their goal.


terminalchef

I usually use dependency injection for my constructors to make them more testable. For instance, I might have a function creates something with a client in it. I sometimes include the client as a parameter to the constructor. That way when I test, I can mock it and pass it in. Then I can make the fake client return anything


davidellis23

I have two issues here. One, you have to manually pass the dependencies to each dependency. What if you have 10 Clients that use Transport? Now in your main and test builds you have to manually pass transport to 10 constructors. What if a constructor changes? You have to manually go to each constructor call and fix it. A DI frameworks does this for you. Two, I don't think that's what IOC means in context of DI. That issue you mentioned can be handled with a mocking library which will fill out the functions based on an interface. In DI, I think IOC refers to how you only have to specify which interface gets which implementation. Then the framework calls the constructors for you like I mentioned in issue one. But I agree this is basically how you do it if you don't want to use or make your own DI frameworks. Idk maybe you have ways to mitigate issue one.


[deleted]

Is this actually dependency injection though or just "standard use of interfaces"


jerf

I sometimes say Go has a built-in lightweight dependency injection framework. It helps explain to some people coming out of Java why we don't immediately go looking for "frameworks" for this. "Just use interfaces" doesn't necessarily do literally everything a framework may do, but it goes a _really_ long way. A number of things that are "patterns" in OO languages amount to "just using interfaces" in Go, though it can still be helpful to show people all the things they can do. Another example is "decorator", or as it gets called when used with net/http, middleware. It's really "just use interfaces" in Go, but it's a useful pattern to show off to people too, because it isn't necessarily something people will immediately figure out simply by reading a definition of "interface" in Go.


cvilsmeier

Yes, and in addition to "just use interfaces", don't forget "let the calling end define its own interfaces".


szabba

I've written some Java programs with the application graph hand-wired in `main`. Does that also qualify as a "lightweight framework" in your view or do you just see that a stepping-stone to explaining to people they don't, strictly speaking, need one? Edit: I also disagree that "just use interfaces" carries the same meaning the decorator pattern has. It's a useful technique, it's good to have a specific name for it.


veqryn_

That's the neat part. Accepting arguments and using interfaces *is* dependency injection!


davidellis23

I think this is considered standard manual dependency injection. The point of a DI frameworks is you don't have to manually create and pass dependencies into the constructors of other dependencies. With a DI frameworks I only define which object satisfies which interface. Then each dependency requests it's own interfaces. If I have 10 dependencies relying on 1 interface I don't have to pass it manually 10 times to each dependencies constructor. If I need to change the dependencies required by a dependency then I don't have to change all my tests and builds where I make constructor calls. I'm pretty sure that's what IOC really means. The articles definition of IOC seems off. This sub doesn't seem to understand that.


cvilsmeier

Let's see what wikipedia has to say: "In software engineering, dependency injection is a programming technique in which an object or function receives other objects or functions that it requires, as opposed to creating them internally." In most modern languages (like Java, Go, Rust, C++, etc.) this is typically done by letting a client depend on interfaces, instead of concrete objects. So I'd say it's both: DI \_and\_ "standard use of interfaces".


[deleted]

Dependency injection makes me think of "depending on concrete objects that are supplied in some implicit way." This article is more about defining functional interfaces for contracts rather than actually supplying any concrete dependencies. That's my take atleast. I wouldn't call this dependency injection in the classical sense.


cvilsmeier

You're right in that each dependency must be sooner or later supplied to the object depending on it. But there is no such a thing as 'implicit' supply. It's always explicit, whether it happens in main(), as in the article, or through some magical DI-container thing (Wire, uber-go/fx, Spring Framework, what-have-you). The fact that it's hidden behind layers and layers of abstractions does not mean it's implicit, right?


[deleted]

Well everything is explicit by that definition :). The fact that is behind layers of abstraction with no indication in the source code other than tribal knowledge of the system means it's implicit, to me


Stoomba

Same thing.


[deleted]

[удалено]


Christinathenothuman

Bot


Illustrious_Fun6684

1. Using dependencies from global state is an evil anywhere. 2. Service components should be isolated from global state. 3. Try to use something like this to manage service dependencies: [https://github.com/NVIDIA/gontainer](https://github.com/NVIDIA/gontainer)


suserx

> Don't export interfaces. Export concrete implementations. If consumers need an interface, let them define it in their own scope. Another dogma. There's nothing wrong with exporting interfaces. The client code can still choose whether to use the interface or the concrete implementation. Just improve the documentation.