- So I want to reiterate that
I don't want to be using interfaces
anytime we have to think about mocking.
But I really want to show you some mechanics
in Go about that convention over-configuration too,
because it's really helped us have to write less code,
and has taken the burden of testing,
at least worrying about users' tests
from that API developer.
So let's start with this piece of code here.
So imagine one day that, or I have a client.
My client loves the PubSub type of strategy, right?
We have a message bus in the middle,
and what we do is all of our services
connect to the message bus,
and services talk to each other,
we get service discovery through the message bus.
It's a really nice starting pattern
if you're trying to do micro-services.
And what I've done here is my client has come to me
and they said Bill,
we have our own internal message bus,
we've had it for years.
We use it, we love it.
We want to start writing code in Go,
but we don't have a package
or an API that lets Go developers
connect to PubSub, all that good stuff,
to the message bus.
Will you write the package?
I said absolutely.
I'll implement the binary protocols,
I'll do everything you need.
But my question to the client is,
how many message systems do I have to worry about?
They said no Bill, just one, just this one.
This is the only one that matters.
So there's not a second one?
No, no, no, Bill, just this.
Look, I don't need interfaces.
There's nothing to decouple.
There's not multiple implementations,
and I don't need anything for my user.
So I design an API entirely in the concrete,
and I come up with this type PubSub,
it's got other fields like host connectivity information.
I've got my factory function returns,
a pointer of the concrete type,
and then we implement our API.
Now we can pretend this is a 10 method set API.
It's a method based API,
because PubSub has state connectivity issues,
things like that.
So we've got here publish and subscribe.
We pretend that we've implemented that, brilliant.
Now, I implement this,
and now it's time to write tests.
I'm not gonna add an interface to write tests,
because I need to make sure that my binary protocol works.
So what I'm gonna do is use Docker
and I'm gonna put that message bus system
that this client uses,
I'm gonna put it into a Docker container,
and every time I call Go Test,
I'm gonna bring up the message bus,
and I'm gonna make sure my entire API
and all the binary protocols
work as expected.
Nothing's mocked.
When this code is now ready for a user,
I know everything absolutely works.
So I finish the code,
and I go over to somebody on the other team
and I say, here is the PubSub package, start using it.
And a couple days later they come over to me,
maybe Jack, Jack comes over to me,
and he says, Bill, Bill, come here, I got a problem.
And I say, dude, you found a bug with the PubSub?
He says, oh no man, I haven't even gotten that far yet,
you know, I started realizing that I need to write tests
against the PubSub,
and I'm not gonna have a PubSub system to hit
when I write tests.
I need to mock the PubSub system.
And I think about it, and I go,
you know what, you're right.
For your application,
and for your application's tests, you need to mock.
You should assume
that the PubSub API works.
So you should be able to mock
if I subscribe to this, and publish that,
I get everything back.
I go you're right, you need to mock your tests.
He goes great Bill, I appreciate that you understand
what I need to do.
You didn't give me an interface, so I can't mock it.
Can you go back and add an interface to your package?
And I say, dude, no no no no no no,
I'm not adding an interface to my package,
because I don't need the interface.
I don't have a need to mock.
My tests hit against the real system,
thanks to Docker.
My API is one and only one.
I go, you need to mock, not me,
so I'm not gonna provide you an interface.
And he says, Bill, why are you being a jerk?
You know I need the interface, just give it to me.
And I say to him very quickly,
I'm not being a jerk,
what you're not understanding is this is Go.
And Go says that I only have to worry about my tests,
and I don't have to worry about your tests.
Your code, your tests need this interface, not mine.
So sit down, Jack, and I want to show you something.
And I what I tell Jack really clearly is to do this.
Jack, there is nothing,
nothing stopping you
from going ahead and defining your own interface.
Remember Go's about convention over configuration.
Isn't it true that if Jack implements this interface
for the methods that he's using,
because he might not be using all of them,
then doesn't any concrete type that I've defined
also satisfy this interface?
Yes, think about how convention over configuration
is helping us to reduce code and cognitive load.
Jack can go ahead and define the interface he needs,
write the entire application using his interface.
I don't have to provide it.
And yet any concrete piece of data,
any value or pointer that comes out of my package
can satisfy this interface for Jack.
So Jack now in his tests
defines a concrete piece of data called mock.
He implements the API that he needs,
and now he's able to mock,
and then when his application comes up,
he either calls pubsub.New,
he gets the concrete data from my package,
or when his tests start to run,
he just creates his own concrete data.
But his entire application is built,
not on the concrete,
but on this value-less interface.
And Jack doesn't need me.
My code is clean, streamlined,
and Jack's code that needs the decoupling,
defines the interfaces.
This is what's beautiful about Go
and this idea of convention over configuration,
that we've now shifted this idea of responsibility,
that if you need to worry about tests,
you worry about your own tests.
I don't have to do anything special in my code,
because you have to test.
This is brilliant, brilliant stuff coming out of Go.