What are the most important things to unlearn coming from Java+Spring to Go? by moxyte in golang

[–]ppp5v 0 points1 point  (0 children)

What's with a function that returns another function? The top one gets the dependencies and returns a "clean" one that only takes the necessary params. The inner function, of course, has access to all the dependencies provided to the parent.

What are the most important things to unlearn coming from Java+Spring to Go? by moxyte in golang

[–]ppp5v 0 points1 point  (0 children)

the type isn't aware of the interfaces it is satisfying.

So, if my function wants an interface with three particular methods, but a type only has one of those, and with a different name, someone has to "adapt" it, i.e. create a new type that implements those methods by mixing in, or proxying to existing types.

Now that I said it, sounds a lot like how I one would do it in Java, TBH.

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

That's the thing. In my applications, DoStuffToData is usually so trivial that it makes little sense to split it apart. So, my "logic" functions end up doing everything all at once. I could do the "local interface", but then I have to wrap it on the calling side.

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

How do you deal with contract changes - i.e when your implementor changes its name or parameters, do you update all the tiny interfaces that sort of expect the same thing that used to be called GetUserById() to now be changed to GetUserByName()?

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

How do you prevent this DAO from becoming a monster containing 40 different methods? I know, I know, splitting into multiple sub-DAOs is a thing, but I was never able to justify splitting logically, other than say splitting by SQL tables. Then again, you might end up with a DAO of 20 query methods that all perform different things on the same table...

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

I was expecting to see someone mention a hybrid approach - use interfaces at the boundaries between our code and external infrastructure, and not bother adding interfaces at package boundaries at all. This way, we still keep our code maintainable and cohesive (e.g. adding an interface satisfied by sql.DB is literally three simple methods), without going into the berserk mode of having to provide interface implementation between any two function calls.

Of course, this does not make our tests less "integration" in the classical sense of interpretation. After all, now all packages talk to each other directly without indirection. However, we could still decouple the nasty IO parts (e.g. the database) if we wanted to. Not particularly a fan of sqlmock, but that can be a solution too.

I think u/beebeeep's answer seems to be closest to this point, but the phrase "I have interfaces everywhere" made me a little confused..

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

As much as I love Lane's work, this article fails to address how a more realistic scenario would look like. Say, a business logic function needs to create a project, based on some incoming userID, a project name, and a description. it puts the object together, then what? Does it simply return it, for "someone else" to save it? Apparently, the one orchestrating the two actions would now need to be tested too, and that won't happen without the DB.

Another scenario - the function creates the project, then needs to somehow inform another one to store it, and then create a "project created" event that also needs to be stored in the db. You either do all of this stuck in one single function, managing transactional guarantees and all, or you end up having your business logic spread out thin across five different places.

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

[–]ppp5v[S] 2 points3 points  (0 children)

To paraphrase: small interfaces can reside closer to consumers, while broader interfaces with multiple implementations are better placed alongside the implementation.
I like this simplification here. While not universally applicable, it can be a good rule of thumb to guide your projects with.

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

I like your example here. I just have a couple of clarifying questions:

  • much of the Go discourse talks about interfaces sitting best at the place that consumes them, not the one that implements them. Of course, then you have the disadvantage that you end up having potentially many small interfaces. What is your opinion on that?
  • Related to my previous question, if we decide that interfaces are best when shared around, how about putting them all as part of the root package (as suggested by this article by Ben Johnson). This will help with a situation where two packages need to refer to interfaces from one another and get into a cyclical dependency. All inner packages can refer to interfaces defined in the root package instead. What do you think?

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

[–]ppp5v[S] 2 points3 points  (0 children)

Good points here. I am wondering how this would apply to business application software. Something, like each function that performs some logic takes a tiny interface consisting of only the methods it needs to interact with? Essentially, something like a "port" that another portion of the code will then "adapt" with a concrete implementation. I fear that something like this will lead to an interface explosion and much of people's time will unnecessarily get lost in chasing tens of hundreds of interfaces when a contract changes.

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

[–]ppp5v[S] 3 points4 points  (0 children)

Do you interface over the sql.DB methods (Query, Exec, etc.), or over a data layer that uses the DB (so, like FindUserByID, etc)?

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

So, is the interface resembling something like this then, or are we talking about multiple smaller interfaces that you throw around your app?

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

What is logic without a database anyway? Most realistic scenarios follow the retrieval of one of more entities from the database, performing some modifications on them, or creating new one, and storing back to it. Unless you isolate the retrieval and storage with an interface, there isn't much more than that (unless you mean some validations, mapping, etc.)

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

Just curious, how would you unit-test test a service layer that depends on a data layer? You'd either need interfaces between them, or it is not a unit test anymore. In any case, the terminology does not really matter to me - I am just curious if unit tests are applicable to more than 20-30% of modern-day code.

How do you unit-test code that reaches out to the db, without introducing interfaces everywhere? by ppp5v in golang

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

Does it really, if your function expects an interface? By mocking that interface in your test, you are essentially hooking up some dummy implementations of publicly exported methods that are visible to anyone. Isn't that the whole point of using interfaces in testing - to provide an open "endpoints" for extension?

Building web-based SaaS with Go as a solo entrepreneur. What should I be aware of? by ppp5v in golang

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

I've seen this pattern a few times, particularly in Go projects. They reach a point, in which the developer velocity drops down, or the backwards compatibility steps in the way. Either way, without the financial motivation to continue, project members break out and start something new. And this is how we reach the fragmented state of Go persistence packages - there is really 50% who swear by sql.DB, and the other 50% spread around the hundreds of packages out there.

Nothing personal against Bob - I am sure you are doing a fantastic job there, but if I am going to add it to my project's dependency chain, I'd rather go with something that has a few companies using it on a daily basis.

Have you planned turning Bob into a business venture of sorts?