Remix.run Logo
the_gipsy 10 hours ago

I don't quite see when a package is "understood to be the consumer" of... itself? We're talking about other packages importing an interface.

I can give you a concrete example.

I have a "storage" package, that exports multiple storage implementations/backends. Files, in-memory, S3, zip...

Some other packages pick one of the implementations to instantiate, depending on the use case (this is NOT a case of mocking for testing or anything like that).

Most other packages work with a "storage" interface, as they're only concerned with reading/writing to "storage".

So the storage package, or in any case some package, has to export the interface. Otherwise, every consuming package would have to copypaste that interface, which is NOT warranted.

9rx 9 hours ago | parent [-]

Actual code is always better, but based on the description it seems a bit strange that there would be a single package that exports multiple implementations. Presumably each distinct implementation should be its own package. None of these packages would export the interface.

An additional package would export the interface and provide the common functionality around that interface. Those common functions would be considered the consumer. In fact, the standard library offers an example of exactly this: io/fs.

the_gipsy 6 hours ago | parent [-]

No, I cannot agree that this would be called the consumer.

Yes, you have technically moved the interface type away from the implementation, but just for the sake of it, without any other upsides. The consumer is still the package that is using and importing this interface type, just from another package now.

9rx 2 hours ago | parent [-]

That is the beauty of engineering: There is no universal truth, just different tradeoffs. Meaning that you don't need to agree, nor should you even seek agreement. You can and should forge your own path if different tradeoffs are warranted for your unique needs.

But, this is the "idiomatic" approach. The upside is consistency for future readers. Code is for humans to read, after all. Most codebases follow this pattern, so it will be familiar when the next person encounters it. If you have a reason to do things differently, go for it. Nobody knows your problem better than you, so you cannot listen to others anyway.

I am quite curious about what you see in the different tradeoffs you are accepting, though! What has you weighing them in favour?

the_gipsy 38 minutes ago | parent [-]

Sorry but I haven't really seen this pattern anywhere, care to give some examples?

All libraries that I recall ever using always export a single package, including interfaces. I just took a look, and even io exports a bunch of structs along the interfaces they implement. And `error` is like a basic type of the runtime.

9rx 18 minutes ago | parent [-]

> Sorry but I haven't really seen this pattern anywhere, care to give some examples?

The standard library provides examples, including a "storage" example.

> All libraries that I recall ever using always export a single package

And now there are questions around the language being spoken, so to speak. That's the power of idioms – it avoids the reader needing to ask questions when encountering language that is new to them. But idioms are not the be all, end all. Sometimes they just don't fit. And if that's the case in your situation, no problem. Nobody knows your problems better than you. To listen to someone else tell you how to solve your own problems is foolish.

That is why I earlier wished we had seen some real code. Perhaps then we would understand the nuance that has lead to you choosing these particular tradeoffs. We have no sense of what problems you are actually trying to solve. As a rule, though, an overarching interface package with consumptor functions along with multiple implementation packages is preferable because then it avoids a lot of the questions developers are going to start asking.

For example, with the alternative suggested, what if I want to add a new storage adapter that conforms to your interface? Do I need commit rights to your package or should I create my own package that satisfies your exported interface? If I create my own package, why is it the lone implementation in its own package while the others are all rolled up in one package? Is it that you don't want third-party implementations for other services not covered by your package? Why is that? On and on...

If you stick to the idioms, those questions are already answered by convention.