Episode Transcript
Transcripts are displayed as originally observed. Some content, including advertisements may have changed.
Use Ctrl + F to search
0:05
Welcome to Fall Through Your Source
0:07
for Deep Conversations about technology, software,
0:09
and computing. I'm your host, Chris
0:11
Brando, also known as scriptable, and
0:13
on today's episode, we are talking
0:15
about APIs, versioning, and HDP, along
0:17
with a few other things, and
0:20
of course, we've got some un-pops
0:22
at the end. In this version
0:24
of the show, we're going to
0:26
be sticking to the content about
0:28
versioning and HDDP. If you want
0:30
to hear us talk about APIs
0:32
and identifiers, then you should head
0:34
on over to fallthrough.fm slash subscribe, where
0:36
you can sign up and get access
0:38
to that content. If you're not already, please
0:41
follow us on social media, and don't forget
0:43
to like and comment. And if you want
0:45
to see our lovely faces instead of just
0:47
hearing our lovely voices, head on over
0:50
to YouTube and smash that subscribe button.
0:52
And with that let's. It's
1:14
a fall-through. I probably don't
1:16
need an intro or something like
1:18
that. So we're here with our
1:20
panel, regular panel, you know, introduction.
1:22
Today we're talking about APIs, API
1:25
design, API implementation, maybe some API
1:27
testing. We shall see. And joining
1:29
us to talk about APIs is
1:31
the wonderful Jamie Tana. How are
1:33
you doing today, Jamie? Hey, I'm doing
1:35
great. And for our listeners that
1:37
don't know who you are,
1:39
would you mind introducing yourself?
1:41
Yeah, so I'm Jamie, I'm
1:43
a senior software engineer at
1:45
elastic and I'm working on
1:47
things like engineering productivity, but
1:50
over like the last decade
1:52
I've spent a load of time in
1:54
the API space. I am a blogger,
1:56
you may know, moved from some of
1:58
my lukewarm takes. I
2:00
most recently the co-maintainer of
2:03
the open APO to go
2:05
co-generator or APO Co-Gen, which
2:07
we spoke about on go
2:09
time a while back as
2:11
well. Yes, I remember that episode.
2:14
This is a fun episode.
2:16
So what's... Oh, gosh, Jamie, like,
2:18
what's your stance on versioning
2:21
then, right? Like, when is
2:23
a change, whether you're using,
2:26
like, URL pathing, or whether
2:28
you're using content negotiation, when
2:31
is a change become a
2:33
breaking change, and how does
2:35
that affect how you see
2:37
versioning for your API? Like,
2:39
what are your thoughts that used
2:41
to be required that are
2:44
now optional or not even
2:46
present? but also more importantly
2:49
if the behavior changes and
2:51
that is less easy to
2:53
work out because behavior being
2:56
like back-end how it's stored or
2:58
process or what do you mean
3:00
when you say behavior there? Yeah
3:02
so yeah and so for instance
3:05
like if you're saying to say
3:07
I want to create a new
3:09
user if that's in like the
3:11
V1 API creates the user and
3:14
actually does some like early on-boarding
3:16
steps or something like that or
3:18
like sends an on-boarding email maybe.
3:20
If in your V2 API you
3:22
just create the user and then
3:25
there's like a location head of
3:27
its return to something which then you
3:29
as the client need to go in
3:31
and then trigger the on-boarding step or
3:34
there's something slightly different that happens without
3:36
on-boarding email. I would say that is
3:38
a breaking change because even though
3:41
it may not be explicitly relied
3:43
on. by one of the consumers.
3:45
It is definitely a change in
3:47
underlying behavior. That's one of the
3:49
harder things that isn't necessarily documented
3:51
in your like API description, but
3:53
is something that consumers may be
3:56
relying on, may need to be
3:58
aware of. I remember all. lot
4:00
of debates around the
4:02
subjects when Go modules
4:04
came out with semantic
4:06
keyboard versioning. There are
4:08
lots of breaking changes
4:10
that aren't breaking from
4:12
the standpoint of API
4:14
signatures. And it's very
4:17
difficult to convey that
4:19
in the simplified model
4:21
of a single simver
4:23
string and semantic keyboard
4:25
version. You know, we're going
4:27
to have to like come
4:29
up with like a a
4:31
tone or something that plays
4:33
whenever any of us does
4:35
one of the classic things
4:37
we do like when I
4:39
go in a monologue or
4:41
when Dylan talks about semantic
4:44
import versioning like there's gonna
4:46
be some like noise encounter
4:48
because this is it want once
4:51
it can be match ding
4:53
that was me that wasn't
4:55
the soundboard that was that
4:57
was mad That's a very
4:59
nuanced area of discussion, like versioning
5:02
is important, but versioning is not
5:04
just your API signatures. You can
5:06
break an API by preserving the
5:09
signature, but changing the semantic behavior
5:11
of what you, what that API
5:13
does. So how are we telling
5:16
clients this then, right? Like how
5:18
are we, how are we communicating
5:20
the version changing? Because we've talked
5:23
about this earlier in the conversation
5:25
where we said that, you know,
5:27
your original storage model can leak and
5:30
then your business next week or next month
5:32
is going to change because your CTO said
5:34
we're going in the AI route or doing,
5:36
you know, they pivot. How do you handle
5:38
that then, right? Because it's going to happen.
5:40
The change is coming and your API, the
5:43
way you designed it yesterday, is no longer
5:45
relevant and needs to be changed for today's
5:47
users. How do you communicate that version to
5:49
your clients and how do you like bring
5:51
the old clients forward with you into the
5:53
new and greatest way of doing things? I
5:56
mean, I think the naive version of that is
5:58
you just stack towers and towers of. points
6:00
and eventually you say hey that
6:02
old one's gonna stop working right
6:05
but in there but is it though like
6:07
no you would ever stop working like I
6:09
can go look at our our like main
6:11
monolith API in point list and
6:13
it's like 800 of them right
6:15
yeah and 400 of those are probably
6:17
old and no one uses them
6:20
but they stick around because maybe
6:22
someone uses them and hot
6:24
take here I don't have the biggest
6:26
issue with that like in a
6:28
lot like having a many routes
6:30
stay alive? Yeah. Okay. In a
6:32
lot of ways, it's easier, like the
6:35
change manager of that is actually
6:37
easier than trying to version APIs.
6:39
Just like building, like, I mean,
6:41
it's the same strategy as slash
6:43
v2 slash v3, right? That's not
6:45
versioning your API. That's building new
6:47
in points. Those aren't the same
6:49
in points, right? And like, it's
6:51
not the most technically correct. But
6:53
I do think for your customers,
6:55
it's the easiest. to migrate, it's
6:58
the easiest to understand. I don't
7:00
know, it's hot take. It's understandable
7:02
and it's comprehensible. Like, it makes
7:04
sense to a human. Yeah, if I
7:07
have to tell my customer, our reporting
7:09
API, you now have to send this
7:11
weird version header, and then suddenly you've
7:13
got to negotiate the, like, the version
7:15
and everything, they're going to be like,
7:17
we're not going to use this. I
7:19
need to be able to put this
7:21
in a ETL job. I actually can't
7:23
even change. One of my, and not
7:26
so much anymore, but back in
7:28
my Windows days, one of my
7:30
favorite blogs was, it's called
7:32
The Old New Thing. It's
7:34
from a guy on the Windows
7:36
team, his name is Raymond Chin,
7:39
and he's on the compatibility
7:41
team. And he wrote for years
7:43
these explanations about the Herculean
7:45
efforts that the Windows team have
7:47
done over the years to make
7:49
sure that Lotus 123 from 1994
7:52
still works. on Windows 11. So
7:54
at that level, kind of, it
7:56
does cross over into that the
7:58
concept of API version. and kind
8:00
of like you said stacking in
8:03
points and like you have these
8:05
millions of consumers if you're wildly
8:07
successful who realistically you can't get
8:09
all of them a switch so
8:12
you have to account for it
8:14
on your they don't care about
8:16
your tech debt yes they're not
8:18
going to switch what they have
8:21
works for them so you're new
8:23
you're like you said your internal
8:25
refactor if it breaks them you're
8:27
for lack of a nicer way
8:30
to put it, you're failing
8:32
at your job. I don't know
8:34
if I agree with that, but
8:36
I want to hear, yeah, I
8:38
want to hear, I want to
8:40
hear other people's thoughts about this.
8:42
So I wrote a thing, I
8:45
wrote a little, a little,
8:47
I call it a thought
8:49
about this because I think
8:51
I've thought about this problem
8:53
a lot because like it bothers
8:55
me that. Like you almost wind up
8:58
feeling held hostage by your customers because
9:00
you don't want to let them down
9:02
or your consumers really, not necessarily customers.
9:04
You don't want to let them down
9:06
and they obviously don't want to change
9:08
things. Maybe can't change things without an
9:11
impetus, right? Like they won't change something
9:13
unless there's a reason for them to
9:15
change it. So if you're just like,
9:17
hi, please move over to my new
9:19
API. They're like, I have a giant
9:22
backlog. So unless I have to, no. And there's
9:24
a way we deal with this in the
9:26
broader world, and they're called contracts, right? You
9:28
write down a contract, and both sides have
9:30
to agree, this is what we've agreed on.
9:33
And what we essentially do now is we
9:35
have a contract, but the contract says, you
9:37
get to use this thing in perpetuity, right?
9:39
I mean, I know we sneak into the
9:41
terms of service and things like that, that
9:43
say, like, oh, we can turn this off
9:46
whenever we want, but like, the kind of
9:48
social contract or like, in effect contract we
9:50
have is you get to use this for
9:52
as long as you want and I think
9:54
one of the things we should do is
9:57
start just having explicit contracts that say hello
9:59
you can use this API, like here's all
10:01
the endpoints you get to use for this
10:03
amount of time. And you can encode that
10:05
as like, you know, a token that they
10:07
send or can be encoded into API tokens
10:09
like it generated. You can implement it however
10:11
you want. And, you know, at the end
10:14
of that time, maybe it's a year, they
10:16
can renew the contract. You can say, okay,
10:18
yes, we're gonna support these API endpoints for
10:20
the next year. here's your new contract.
10:22
But if at the end of
10:24
that contract, you're like, well, actually,
10:27
we don't want to maintain these
10:29
APIs anymore, well, then you go into
10:31
contract negotiations. So then you sit
10:33
down with the other side and
10:35
you say, okay, well, we don't
10:37
want to support this API anymore
10:39
because, you know, it's costing us
10:41
all that money, so, we'll allow you
10:43
to keep using it, but here's it,
10:45
but here's how much it's going
10:47
to cost you. Like, we don't want
10:50
to support this. Okay I'll pay that or
10:52
no I don't want to pay that and
10:54
if they don't want to pay that then
10:56
okay well here's the new contract that doesn't
10:58
have these API endpoints if you call them
11:00
you will get rejected and so and here
11:02
you go but if they do want it's
11:05
like okay well here you go and give
11:07
you the money and then you're done right
11:09
and you do that for each individual consumer
11:11
you have obviously you can automate this however
11:13
you want and you can you know make
11:15
the contracts however you know in depth that
11:17
you want them to be but fundamentally
11:19
the problem we have here is that
11:21
not everybody is a I don't think
11:24
we're going into this with a mindset
11:26
of Oh, this is a contract and
11:28
I don't think we're all purposefully
11:30
making in perpetuity contracts and we
11:32
need to get away from ourselves
11:35
making in perpetuity contracts because obviously
11:37
we would all like to change things
11:39
in the future. This also gets you
11:41
away from necessarily having just a version
11:43
of versions or even content type anything
11:46
into headers or anything like that because
11:48
the API token or the contract
11:50
can say what version of the API you're
11:52
using. Right. You can get a new contract
11:55
that says, okay. will be processed using the
11:57
V2 API and your old contract is the
11:59
V2. one API and then you choose in
12:01
your application which token you get and that.
12:04
will tell you that will inform the application
12:06
which version to send back to you. So
12:08
it solves, it definitely can solve the versioning
12:10
problem, but it also solves this larger problem
12:12
of being able to clean out some of
12:15
those older things that you have, because there
12:17
is a big problem of, is this API
12:19
still used? I don't have a way of
12:21
definitively knowing that. But you can say, if
12:23
you look in the contract database, no one
12:25
has a valid contract for this API endpoint,
12:27
then you know you can get rid of
12:30
it for whatever you want. Yeah, I guess
12:32
one of my concerns with that is friction.
12:34
So there's, first of all, you need to
12:36
have all that data available, which like
12:38
scrappy young startup maybe isn't going to
12:40
have that. But even like large
12:43
enterprises aren't generally building that
12:45
sort of information into new things
12:47
they're building. It's also like, say, I
12:49
want to come and play around with
12:51
your API. Do I now have to
12:53
go through a contract negotiation to just get
12:56
an API key to play around with
12:58
it the first time? No, not necessarily.
13:00
You could have just like, you could
13:02
have a shared contract or an automatic
13:04
contract that's just like, oh, it's like
13:06
the same way you get API tokens,
13:09
right? There's not to be something
13:11
necessarily different in that flow. You
13:13
could be like, here's your API
13:15
credential. This is how long it
13:17
lasts. And oh, here's the documentation
13:20
about what you're allowed to call
13:22
with it. And when you regenerate it,
13:24
that documentation might be different.
13:27
Basically. Here's the current published version
13:29
of this API. It's Q1 2025.
13:32
As a vendor, as a service
13:34
provider, we will support that version
13:36
of the API for one year
13:39
and we'll guarantee that. But
13:41
in Q1 of 2026, those APIs
13:43
are now subject to breakage. And
13:46
it kind of solves the implicit
13:48
in perpetuity thing that leads to
13:50
all the nasty compatibility shims and
13:53
windows to make DOS programs
13:55
from 1980 still work.
13:57
but without all the
13:59
extra. infrastructure requirement
14:02
that Jamie was calling out kind
14:04
of now you have to go
14:06
build that in and have the
14:09
system in place to go provision
14:11
contracts for every user and store
14:13
their API tokens and kind
14:15
of there's a lot of
14:17
investment potentially a lot of
14:19
investment to get that working
14:21
that a lot of early
14:23
stage systems just don't have
14:25
the time or the resources to
14:27
build. So what you described still
14:30
fits within what I was talking
14:32
about, right? That's just a, I
14:34
mean, you can conceptually understand it
14:36
as... There's only one contract that is negotiated
14:38
with everybody. And when you, it's like, it's
14:40
like terms of service, right? You don't write
14:42
terms of service for each individual user of
14:44
your website, but terms of service is still
14:46
a contract. So you can have a contract
14:49
that is just with anybody that uses your
14:51
service that is like, okay, well, there's no
14:53
negotiation for it. Like, here's what it is,
14:55
here's the API and points, after a year,
14:57
we get to break all of them if
14:59
we so choose. That's what it is, right?
15:01
But I think the thing about it is
15:03
that it puts explicitness on the whole situation,
15:06
which I think will shift people's minds
15:08
over time about how they use APIs
15:10
and how they consume APS and how
15:12
they build their own applications as well,
15:14
and what they are telling their customers,
15:16
because they think a lot of the
15:18
time it's, oh, I need this API
15:20
because my customers use this specific thing
15:22
in my application, and I haven't told
15:24
my customers that this app might stop
15:27
working, right? It's less now that we're
15:29
all subscription based, but definitely was a
15:31
thing in the past where people'd buy
15:33
an app and they'd be like, I
15:35
get to use this app forever. But it's
15:37
like, well, that's not really reasonable if,
15:39
you know, if people want to deprecate
15:41
things that, you know, are underneath you
15:43
or not directly in you as an
15:45
app developers control. This also opens up
15:48
the opportunity for if you do wind
15:50
up selling an app and... Like you're you you
15:52
as a as a person like I don't maintain
15:54
this version of the app anymore But someone or
15:56
maybe some big enterprise is like you know we
15:58
really need this, but we're it's an app for
16:00
a third party thing. Maybe they
16:03
can go to that third party
16:05
and be like, okay, we want
16:07
our own contract. And as long
16:09
as you've negotiated, as long as
16:12
you've put configurability in your app,
16:14
they can put their own contract
16:16
token in there. So you as
16:18
a developer don't have to get
16:21
involved at all. You don't have
16:23
to go back and care about
16:25
this old version of the app.
16:27
They can keep using it because
16:30
they've negotiated. what a contract could
16:32
be, right? Like in contract law,
16:34
if I just say, oh, let's
16:36
do this thing, we get a
16:39
hand, and we handshake, that's a
16:41
contract. So it doesn't have to
16:43
be some like in-depth, you have
16:45
lawyers and negotiation, it takes months
16:48
to do it. It's enough to
16:50
be that. It's just the explicitness
16:52
of this is what we are
16:54
providing for this amount of time,
16:56
and this is your ability to use
16:59
it. Right. It's it's something you need
17:01
to think about up front, not when
17:03
it actually you're ready to deprecate
17:05
something. You're talking about like having an
17:07
app, something we ran into on my
17:10
previous job was I mean, we had
17:12
people on very old versions of iOS
17:14
apps, right? And we had no way
17:16
to cleanly deprecate that, right? Like if
17:18
we if we change the APIs or
17:20
get rid of them, that app just
17:22
breaks in a way we might not
17:24
understand. So something we implemented was
17:26
a minimum version. for APIs that
17:29
returned like a explicit error that
17:31
said this app version is no longer supported
17:33
so we could pop a message saying like hey
17:35
if you want to keep using our app you
17:37
have to update right but we couldn't do
17:39
that on the oldest versions because we
17:41
hadn't implemented that code but that's like
17:43
a lesson you learn that like you
17:45
need to think about this stuff up
17:47
front like from the beginning. Otherwise you're
17:49
stuck. You just go get your time machine
17:52
and you go back and add that code.
17:54
before you thought about it. This is a
17:56
very nuanced interesting topic and I'm glad we're
17:58
talking about it honestly because I personally
18:01
haven't seen someone's API
18:03
that I'm like, oh, they're doing
18:05
versioning perfectly, right? This is amazing.
18:07
It sucks to have to say,
18:09
no, that thing is no longer
18:11
around, please migrate to the new
18:13
thing. And it's hard to convince
18:16
people that are on the old
18:18
thing that's working for them to
18:20
migrate. It's weird too, because we're in
18:22
the software field where software is malleable,
18:24
like we should be able to change
18:27
it almost at will. It's not like
18:29
we constructed a building, and it's like,
18:31
oh man, that V1 designer at calm,
18:33
I really wish we would have did
18:35
it differently, like let's go tear the
18:38
whole billion down, like we don't have
18:40
to do that in software. It's
18:42
much easier for us to change software,
18:44
yet we're much like more likely to
18:46
not change it, because we're like, it
18:48
works, it works, it works, it's fine.
18:50
contracts and burgeoning and stuff, it kind
18:53
of almost reminds me of like a
18:55
restaurant almost where you go in there and the
18:57
contract of you eating is whatever's on the
18:59
menu. And then you go there a month
19:01
later, the menu has changed and your favorite
19:03
item that you were used to using is
19:06
no longer there. And it's either you get
19:08
the new replacement item that's that's similar to
19:10
what you like to eat or you don't
19:12
use that API and you don't use that
19:14
restaurant and you go somewhere else. And it's
19:16
kind of what I'm feeling there, but
19:18
it's communicated. The idea is
19:21
that the service that's being
19:23
offered, the food that's being
19:25
offered, is communicated to all
19:27
the users. Yeah, I would say,
19:30
like you said, it's always nuanced
19:32
and there's gray area around what's
19:35
the right way to version
19:37
your API, but I think the
19:39
most common and the biggest mistake
19:41
that I see is not
19:43
versioning at all and discovering a
19:46
year in. that this needs
19:48
to change and I don't have
19:50
a way to version my API.
19:53
It's almost impossible to bolt
19:55
versioning on after the fact. Well
19:57
that's why you get slash
19:59
retoos. the most important thing
20:01
is you have to think
20:04
about versioning up front. Whatever
20:06
your specific implementation of versioning
20:08
ends up being, there's lots
20:11
of flexibility there, but you
20:13
have to account for versioning
20:15
in your system before there's
20:18
a breaking change. Because if
20:20
you need to go ad
20:22
versioning after it's already out
20:25
there, it's orders of magnitude
20:27
more difficult. Not impossible, but...
20:29
extremely difficult. Prime example,
20:32
go introducing major versions
20:34
and semantic keyboard versioning.
20:36
One of the bigger
20:39
problem areas is the V0 versus
20:41
V1 because there was no explicit
20:43
difference there. There was no line
20:45
in the sand to say now
20:48
this is V1. That's kind of
20:50
an example of when you do
20:52
versioning after the fact. It's much
20:54
harder, it's much more difficult, it's
20:56
harder for people to understand, it's
20:59
harder for you to implement. So
21:01
if I had to have any
21:03
bit of advice for anyone, it's
21:05
think about how you want to
21:07
handle versioning up front. Like that's
21:09
one of your first things that you need
21:12
to account for. And don't do whatever Go
21:14
do with V2 modules, don't do that. Sorry.
21:16
I think as well, like, on the topic
21:18
of like, thinking up from for versioning, one
21:21
of the things that I don't see a
21:23
lot of people doing, thinking generally
21:25
up from and doing a
21:27
lot of actual designing of
21:29
the API and considering
21:31
what sort of things should actually
21:34
be in that. You work on Open
21:36
API CodeGen, right? The project. So my
21:38
question to you is like, maybe does
21:40
versioning even matter, right? Could we just
21:43
keep our Open API... spec up to
21:45
date for our APIs and like AutoGen
21:47
our clients and servers and everything's happy
21:49
and everyone's always on the latest. Yeah,
21:51
we're all happy. Like is that a
21:54
world where we can be in where
21:56
versioning kind of goes away because we're
21:58
all just on latest? How do you
22:00
feel about that? So shot on to
22:02
know, because people don't keep their open API
22:05
specs up to date. And I mean
22:07
more on the consumer side. So my
22:09
previous company, we had quite a lot of
22:11
open API usage. And some of it was,
22:13
because I was the maintainer of our OOPI
22:15
Code Gen, so we were trying to do
22:17
a bit more of it. It was the
22:20
sort of thing that when a team would
22:22
first go to integrate with someone else's API,
22:24
they would generate some code. off the spec
22:26
that they had at that point. And then
22:29
they wouldn't update that spec until the
22:31
next time they needed to do a
22:33
load of work with it. And potentially
22:35
in that time there are new API
22:37
endpoints that are coming in. There are
22:39
documentation tweaks. There's different things that could
22:41
be of note. And so I think in
22:43
that case, because people aren't keeping up to
22:46
date, it is making that a little bit
22:48
more difficult. There are ways you can get
22:50
around that by like, I've written many a
22:52
post about it, like you can do things
22:54
like... You can sync the open API spec
22:57
and then regenerate the SDK. You can generate
22:59
the SDK in a more central place. There
23:01
are also lots of vendors that provide. Very
23:03
nice SDKs that always update and just like
23:06
send you PRs and say, hey, this is
23:08
updated now. But one of the issues is
23:10
that at some point there is going to
23:12
be a broken change. You are going to
23:14
have to modify the call sites of the
23:17
underlying code base. And I think
23:19
that's really the hard thing is even if
23:21
we like abstract it out with... a go
23:23
module that is a nice SDK. At some
23:25
point, someone still needs to go
23:27
in and make some tweaks to put
23:30
in some new required parameters or move
23:32
to a new function call. So then
23:34
would you say open API is usefulness
23:36
while it can generate SDKs and whatnot
23:39
and serves as that like, you know,
23:41
that documentation of your API's functionality, would
23:43
you say the open API spec
23:45
is more useful as documentation itself
23:47
to show the user like what
23:49
even the API is and how
23:51
it works? Is open API an
23:53
open API spec enough for documentation?
23:55
Do I not have to write
23:57
anything else except for that? So...
24:00
You can write a good enough
24:02
open APA spec with like good
24:04
levels of description and everything in
24:06
there. I still don't think it's
24:08
quite enough. I like there's a
24:10
number of very excellent folks in
24:13
the API community who are also technical
24:15
writers and rightfully so. I like you
24:17
can't just write a yammer file and
24:19
call it done but there needs to
24:21
be a little bit more than that.
24:24
But you can get a good chunk
24:26
of the way if you have a
24:28
very reasonable. Open API spec with descriptions
24:30
and summary as an examples, which can
24:32
then be used by tools to either
24:34
generate service-side or client-side boilerplate. You can
24:36
generate like mocks, so you can actually
24:39
test out that API without actually having
24:41
to hit it, and things like that
24:43
are quite useful, because they also give
24:45
you a more realistic view of what
24:47
the API will look like. So is
24:49
that where you're looking for when you're
24:51
going to a new API when you're
24:53
trying to consume a new API that's
24:55
not yours, right? Are you looking for
24:58
that open API spec? Are you looking
25:00
for good documentation? Are you looking for
25:02
MOCs? Like what are you looking for? That
25:04
makes you say, wow, this API is like,
25:06
I understand it, you know. So I will
25:08
first try and find the API docs
25:11
for whatever the API is. If
25:13
they look nice, generally there's an
25:15
open API spec underneath. and they've
25:17
got a tool that is generating
25:19
out most stocks. So then I
25:21
will also take a look at
25:23
the spec. I will, for instance,
25:25
throw it into a couple of
25:27
different open API to code generators,
25:29
because one of the difficulties with
25:31
open APIs, well, we see this
25:33
with OPA CodeGen, is no two
25:35
tools are equal. So for instance,
25:37
like open API 3.1, came out
25:40
several years ago, kind of
25:42
remember how many years. We
25:44
don't have support yet. because
25:46
the primary go library for
25:48
open API doesn't yet have
25:50
support because it's hard. So
25:52
you're saying there's a versioning
25:54
problem, which is all we've been
25:56
talking about this whole time. Yeah, okay.
25:58
I understand. And then. Like even then
26:00
it's yeah so some parts of the
26:03
ecosystem don't have support for it there
26:05
are like lots of nuances to open
26:07
API that makes it really awkward to
26:09
work with and again it goes
26:12
back to like that conversation
26:14
about technically correct versus what
26:16
is practical so you can write a
26:18
really powerful open API spec that
26:20
perfectly describes a load of conditional
26:22
things that may or may not
26:24
happen in place but if you
26:26
can't use any tools with that
26:28
definition you now have to go and
26:30
write the code anyway, you now have
26:33
to implement it all yourself anyway. So
26:35
it's a little bit like, some of
26:37
it is plain to like the lowest
26:39
common denominator, while also
26:42
trying to like nudge the playing
26:44
field up. I think maybe a
26:46
valid signal for open API is
26:48
maybe it's different for others, but
26:50
in my work experience, very few
26:52
people are actually handwriting open
26:54
API specs. I see lots
26:56
of effort around... I do
26:58
that. Oh, here's my... here's
27:00
my go code and uh...
27:02
Go Restville is an example
27:05
where they have an open
27:07
API generator from your rest
27:09
mappings in code, where you
27:11
write the code and then
27:13
dump the open API spec
27:15
from it, rather than author
27:17
the spec explicitly. I mean,
27:19
I've touched it a few
27:21
times. Handwriting open API is tedious.
27:23
at best. I mean all documentation
27:25
is tedious. It's it's very verbose.
27:28
It's very labor intensive to hand
27:30
write an open AP aspect. I
27:32
mean writing gamble is just especially
27:34
like I've been at places where
27:37
like the open AP aspect was
27:39
like 3,000 lines in the animal.
27:41
I was like editing that is a
27:43
nightmare. Terrible. However, yeah I so
27:45
I agree writing it is not the
27:47
best but I still prefer doing that.
27:50
and at least for me it's
27:52
so some of it's maybe ADHD
27:54
and trying to get the thoughts
27:57
across and like I think
27:59
through writing. find in particular like
28:01
doing that up front what should this
28:03
contract actually look like and then not
28:05
just like okay great I've done some
28:08
yam-old let's go I then will actually
28:10
like use an open API mocking tool
28:12
and that will then give me an
28:14
actual representation of what that API looks
28:17
like and I've actually caught a number
28:19
of things there where it doesn't actually
28:21
match what I originally intended but there
28:23
is then that balance of but it
28:26
takes time to do that. I could
28:28
just go and write the code, but
28:30
also like the code that I
28:32
write may not actually be the
28:34
design that actually we wanted. And
28:36
so it's that balance of trying
28:39
to give yourself enough time to
28:41
actually work out what you want
28:43
to be doing and what you
28:45
want to be providing to your
28:48
users. And yeah, I'd say generating
28:50
code, generating spec from code is
28:52
better than no documentation at all.
28:54
And that's the other thing is
28:57
a lot of people will. just
28:59
handwrite all the documentation and that
29:01
is painful to work with, it's
29:03
painful to update and everything. So
29:05
yeah, if you can generate it
29:08
be some of it, do
29:10
that. And it's probably an
29:12
outcropping from my experience but
29:14
I'd much prefer writing
29:16
API specs in protobov
29:19
syntax than to go write an
29:21
open API spec. It's the
29:23
same thing. with essentially a
29:25
different syntax, but I find
29:27
protobuff, ideal, easier to read
29:30
and follow than open ABI.
29:32
Even though I haven't done
29:34
much protobuff stuff, I think
29:36
I agree. Like of the
29:39
bits I've seen, that does
29:41
seem quite reasonable. And I
29:43
mean, like I mentioned, even
29:45
the beginning of my career
29:47
doing calm on Windows, it
29:49
was very similar ideal based.
29:51
structured, this is an interface,
29:54
and here are the methods, and
29:56
here are the messages, kind of
29:58
structurally similar to prototype. So that
30:00
was a natural thing for me.
30:02
Coming from calm ideal way back
30:05
when and then spend a long
30:07
time doing kind of in the
30:09
world of magic. Microsoft Silver Light,
30:12
a lot of.net framework stuff where
30:14
the mechanics of APIs were, you
30:16
were insulated from that. Kind of
30:19
you didn't have to think
30:21
about the mechanics of APIs.
30:23
And then coming back to go
30:25
after a long time and getting
30:27
back into. And getting back into.
30:29
this idea of how do I define my
30:32
APIs? Doing protobuff and GRPC
30:34
felt much more natural than doing
30:36
open API and HDDP rest. How
30:38
do we avoid? Like I'm all
30:40
in favor of generating things, whether
30:42
it's writing an open API spec
30:44
by hand and generating code, whether
30:46
it's writing codes that generate the
30:48
open API spec or some... combination
30:50
in the middle of those. I
30:52
am in favor of that because
30:55
when you have a very large
30:57
API, it's going to be very
30:59
tedious to keep that up today
31:01
manually, right? Like humans going in
31:03
and editing things, it's going to be
31:05
very tough. But when you, when a
31:07
lot of developers say I generate things,
31:09
it's often a way of them saying I'm
31:12
too lazy to care about this and I
31:14
don't want to put any thought
31:16
into it. And I've seen that,
31:18
where like the documentation for the
31:20
Foo API is like the Foo
31:22
endpoint manages Foo Resources. Yeah, like
31:24
no duh, of course it does,
31:26
that's what I'm calling. But I
31:28
want more than that as a
31:30
description. So how do you like
31:33
balance that in your APIs that
31:35
you're creating? Like how do you
31:38
balance, you know, no documentation or
31:40
bad documentation or like good
31:42
documentation? How do you like find
31:44
that sweet spot? that was like, who
31:46
manages food? I'd just be like, just
31:48
delete it. Like, I prefer to
31:51
have no documentation than documentation like
31:53
that. Yeah, like, you have to
31:55
bring people along on, like, the
31:57
culture of documentation. That is the
31:59
answer, like, not. everyone enjoys writing
32:01
documentation but it is very
32:03
important to be honest people are
32:05
probably not going to read it
32:07
but for the few that do it will be good
32:10
as I say I find that writing the
32:12
documentation helps me have that empathy
32:14
for the user okay what are the
32:17
people actually trying to get out to this
32:19
what of what am I trying to solve
32:21
for them more generally and trying to
32:23
get people in that mindset can help
32:25
not everyone's there not everyone
32:28
has that experience and that skill
32:30
set, but it's something that
32:32
everyone can develop. I definitely
32:35
have found that I do
32:37
a better job of it
32:39
since I've kind of shifted
32:41
to that mentality. Like documentation,
32:43
for documentation's sake, kind of
32:46
the go-linter warning, hey, you
32:48
have this exported symbol with
32:50
no comment, so we're going to yell
32:52
at you. And people go right, fool,
32:55
fool's the bar. That's not a
32:57
useful comment, but it makes
32:59
the complaint go away. Since
33:01
I've kind of made myself
33:04
mentally shift to documentation is
33:06
to explain to your consumers
33:09
how they're supposed to interact
33:11
with this. And then kind of
33:13
taking it, like you said, Jamie,
33:15
from the standpoint of if I
33:18
go and your example of generating
33:20
a client from the spec, is
33:22
like a good, I've done that
33:24
myself, kind of, If I take
33:26
this spec that I've defined and
33:28
then I generate the code and then I
33:31
go try to use it, does it work
33:33
the way that I think it's going
33:35
to work? Do the semantics actually
33:37
behave the way that I'm thinking
33:40
they do or is it
33:42
actually clunky and difficult to
33:44
use to kind of make
33:46
that mental shift to you're
33:48
not writing documentation for documentation
33:50
sake? You're writing documentation as...
33:52
an instruction manual for how
33:54
to use your thing. Kind
33:56
of along those lines. I
33:58
do think when I start with
34:01
documentation versus end with documentation, I
34:03
end up with a better design,
34:05
right? Writing the documentation first to
34:07
me is like part of that
34:09
design exploration, and it's the
34:11
easiest way to to think about a
34:14
design, just writing the documentation. I don't
34:16
know where I'm going with that, but
34:18
I guess you should try to design first,
34:20
but not. Yeah, I think that like, I think
34:22
you all, like, so what I wanted to say
34:24
is that, like, like, I think the problem
34:26
is like an upstream problem here, and
34:28
I think. kind of what all of
34:30
you said are indicative of that, of like,
34:32
I think that you need to do like
34:35
the abstract API design. Like I think part
34:37
of the problem we have is that we
34:39
don't do that abstract API design.
34:41
We try to just implement an
34:43
API, whether that's writing in some
34:45
IDL or writing some open API.
34:47
And then I think more importantly,
34:49
we only implement one version of
34:51
that API, whether it's in HDP
34:54
or in GRPC or in GraphQL.
34:56
And I think that's part of
34:58
the problem here is that we're
35:00
not like, GRPC, GraphQL, HDP, even
35:02
rest as a concept as a
35:04
whole. Those are not, those are
35:06
your transports. Those are your way
35:08
of communicating with the thing you're trying
35:10
to communicate with. They are not the
35:13
thing themselves, but I think we treat
35:15
them. as the thing itself a lot
35:17
of the time. And they're insufficient for
35:19
that. They're not built for that. Like,
35:21
there's no good way, I think, to
35:23
write any IDL and have it contain
35:25
enough information to be sufficient, because there's
35:28
so much other stuff you need to
35:30
document that really, I think, a lot
35:32
of the time belongs in prose or
35:34
needs to be added as some sort
35:36
of extension or we need to evolve
35:38
the format. An example of this is,
35:40
I was trying to use GitHub Graphual
35:42
API. It's very well documented and there's
35:45
lots of information about you know
35:47
this these fields mean this and
35:49
these types mean this and all
35:51
of that but there's absolutely no
35:53
information about permissioning so I don't know
35:55
if say a get-up app can actually call
35:57
this graph can you can call this graph
36:00
GraphQL mutation or if it can query
36:02
for this field like that information is
36:04
completely missing and there's not an easy
36:06
way to actually go ascertain that besides
36:08
actually building the thing and trying it
36:10
out which can be difficult or if
36:12
you're talking about something like that actually
36:15
charges you money it could be expensive
36:17
and like that information doesn't necessarily have to
36:19
live in GraphQL but it should live somewhere
36:21
like I had to file a get up
36:23
support ticket to be like can I call
36:25
this API? from a, you know, from a,
36:27
what, GitHub app. And they came back and
36:29
looked, no, you're not allowed to call this
36:31
class of APIs. Great. That would have been
36:33
nice if you had put it somewhere else
36:35
where I could have found it. Or since
36:38
it's a GraphQL API, you won't actually get
36:40
a four or three, you'll get a 200,
36:42
and you have to go read the response
36:44
body to get the permission error. Well,
36:46
yes. Which is not technically wrong. But
36:48
that's a that's a that's a tangent.
36:50
So okay. No, that's a tangent. We're
36:52
going to come back to though. Okay.
36:55
Okay. We are going to revisit that
36:57
one. Don't you worry. I am writing
36:59
that down. That is the most common
37:01
source of table flips for me every
37:03
time I touch anything graph QO. It's
37:06
like it's a 200. Your request
37:08
was processed successfully. Okay. Fine.
37:10
We can get internet because it is
37:12
related to what I just said. Okay.
37:14
So I was saying how. You need
37:16
to have multiple, we really should have
37:18
like at least two, right? Like I think
37:20
it's, I don't like that, get hub doesn't
37:22
have equivalence between their two APIs, but
37:25
they do have like an HDP slash
37:27
rest API and a graphical API. And
37:29
the reason I think that's good is
37:31
that you, it helps you not encode. the
37:34
semantics of your transport into your
37:36
API. And what you said, Dylan,
37:38
is a good example of when
37:40
people encode the semantics of their
37:42
API into their transport, right? Because
37:45
what 400 errors are telling you
37:47
is that the HDDP request was,
37:49
in the case of 400 errors,
37:51
like malformed in some way by the
37:54
client, right? 400 is for client
37:56
malformations, 500 or something wrong with
37:58
the server, 200 is okay. And the
38:00
thing about GraphQL is that if it
38:02
returns a response to you, and
38:04
that response having an error in it
38:07
isn't incorrect because the HDP request was
38:09
processed successfully, it just resulted in an
38:11
error. But that error wasn't because of
38:13
something at the HDP layer, the error
38:15
is because of something behind the HDP
38:18
layer in your business logic. Right so
38:20
you said so this comes up a
38:22
lot if you have like list of
38:24
things or you want to process things
38:27
in batches if half of it succeeds
38:29
and half of it fails That's not
38:31
an HDPDP request failure the HDPD request
38:33
got through it got processed It was
38:35
fine. There was something within the HDPD
38:38
request at a lower level or higher
38:40
level depending how you want to look
38:42
at it that was the error and
38:44
the place where that error message should
38:47
be is not at the HDPD level
38:49
But at the kind of application that
38:51
you get back So it's, you know,
38:53
the request failed successfully or whatever it
38:56
is, right? It's that sort of thing,
38:58
but it makes sense because you need
39:00
to understand like the layering at the
39:02
end of the day, the layering
39:04
is important. And when you try
39:07
and put errors at the wrong
39:09
errors at the wrong layer, and
39:11
when you try and put errors
39:13
at the wrong layer, you tend
39:15
to start to confuse yourself about
39:17
what you should actually be status
39:19
code that says. partial success.
39:21
But in the context of
39:24
HDP, that doesn't make any
39:26
sense. Either the request succeeded
39:28
or it didn't. It got there
39:30
was process or it didn't. That
39:32
reminds me of, going back to
39:35
my Windows Days, the database API
39:37
in the calm days, everything was
39:40
integer error codes. And there
39:42
was a standard error code that
39:44
was DB underscore S for
39:46
success, underscore errors occurred. So
39:49
it's a success code to tell you
39:51
that errors occurred. Back to like the
39:53
HDFP version of it. It feels a
39:55
lot like the content negotiation where content
39:57
negotiation is the correct way to do
39:59
it. but you have to like cater
40:01
to the lowest common thing and people
40:03
expect the air in HDPD. So like
40:06
is it wrong to do it because
40:08
it's what people expect or like how
40:10
far outside the normal boundaries can you
40:12
go with an API before it's like
40:14
too much new information for our consumer
40:16
to parse right like if you created a
40:19
completely correct HDPI like half of
40:21
developers out there would get that
40:23
in their hands and be like
40:25
this doesn't make sense this is
40:27
horrible. I mean, the piss forks
40:29
would come out instantly.
40:31
I mean, you can always, that's the
40:33
thing about the HDP as well. Like,
40:35
HDP and rest as a
40:37
whole, but definitely HDP is
40:39
incredibly flexible. So you could
40:41
say, okay, well, like. We're going
40:43
to return errors unless you include this
40:46
header that says, okay, we actually are
40:48
going to do error processing the right
40:50
way, right? Or you could just say,
40:53
I'll just accept application Jason and we'll
40:55
just run the regular way, but if
40:57
you can send us this other way,
41:00
but if you can send us this
41:02
other thing and we'll just run the
41:04
regular way, but if you can send
41:07
us this other thing and we'll, you
41:09
can send us this other thing and
41:11
we'll be, send us. default way of
41:13
thinking about things, and unless you document and
41:16
tell them explicitly that that's not how it
41:18
works, they're going to go with that assumption.
41:20
So I think the problem is less that
41:22
people are assuming it works one way because
41:24
they're used to it, and more of that,
41:26
we haven't explicitly told them that this isn't
41:29
how this thing works, and if you want
41:31
to use it, this is how you're going
41:33
to have to use it. And I think
41:35
we need to not just go with
41:37
the lowest common denominator all of the
41:39
time, because that ratchets us down to
41:41
a place where we're just stuck, where
41:43
we can't evolve forward, where we're just
41:45
kind of trapped for a long time,
41:47
right? I mean, that's the issue with
41:49
API versioning, right? Yeah, you can make
41:51
a V2 and a V3 and a
41:53
V4, but people will just sit on
41:55
V1 forever, and you're kind of stuck
41:57
maintaining that implementation detail for the longest
41:59
time. And I think we as an industry
42:02
need to stop being stuck because it's
42:04
causing us problems, right? Yeah. People
42:06
are using HDP very wrong. We should
42:08
not stick ourselves in the mud because of
42:10
that. We should start making the changes because
42:13
we won't in 10 years have people use
42:15
HDDB right if we don't start now, even
42:17
if it's hard. Yeah, before we go too
42:19
far off of it, I do want to
42:22
say I very much agree with the point,
42:24
your point, Chris, about separating transport
42:26
from semantics from semantics. can't
42:29
even count the number of
42:31
times I've had that just
42:34
very similar discussions about kind
42:36
of you built this rest and
42:38
it doesn't work GRPC or
42:40
kind of what I was
42:42
talking about earlier where people
42:44
want to put database tags
42:46
and Jason marshalling tags in
42:49
their generated protobuffs trucks
42:51
because they want to also
42:53
use it for the rest API
42:55
kind of the rest and
42:57
GRPC and various things, hand-coded
42:59
UDP packets with your own
43:02
hand-rolled serialization format, those are
43:04
all transports. There are how
43:06
to get data from one point
43:08
to another. That's not the
43:11
semantics of the API. And what
43:13
you're designing is the semantics
43:15
of the behavior, not the
43:17
mechanics of the transport. Yeah.
43:19
I think Jamie had something I wanted
43:21
to say, but before that I want
43:24
to tag on to that of... Also,
43:26
if there's something your transport doesn't support,
43:28
you don't necessarily have to implement
43:30
it. Like, it's not incorrect. Like, the reason
43:32
I said GitHub's API annoys me in that
43:34
the HDP API and the Graphual API aren't...
43:37
equivalent isn't because like there's things that GraphiO
43:39
can do that HDB can't do it's because
43:41
there's they can both do equivalent things but
43:43
there was something that you know maybe in
43:46
a GRPC API is only doable over GRPC
43:48
and you can't do over a Graphi or
43:50
HDP it's fine to have it that way
43:52
and be like sorry this functionality can only
43:54
be done over this one interface because this
43:57
interface allows it so just wanted to make
43:59
that point. Oh yeah mine was again
44:01
an early unpop which was off the
44:03
back of you saying about like having
44:05
issues with being on v1 so you
44:08
should just say on v0 and always
44:10
break things and always keep
44:12
pushing up breaking changes and
44:14
I'm with you man I'm with you
44:16
yeah I'm with you yeah like honestly
44:19
if you can't be bothered to update
44:21
your client code then is it really
44:23
load bearing in your system yeah v0.
44:25
infinity I was going to ask a
44:28
quick question around HSP status code since
44:30
we opened up that that door. Delets.
44:32
I want to delete. I want
44:34
to do HSP delete. Am I... What's
44:36
the item put in way of doing
44:38
that? Do I always return to a
44:40
4? Do I return to a 4
44:42
when I actually do the deletion and
44:44
then do I return 404? When, you
44:47
know, there's no actual deletion being occurred
44:49
anymore? Or do I always return 404?
44:51
What's everyone's thoughts around around
44:53
this? I don't think there's a
44:56
correct answer to that. I
44:58
think the default to blind
45:00
deletes, like if you're trying
45:02
to delete something that
45:05
doesn't exist, we just
45:07
ignore it. And what do you
45:09
send back to the client? 200.
45:12
Or 200. Or 200. Okay, I've
45:14
seen people do two or fours.
45:16
I'm kind of against
45:18
most non-200-200 errors, like
45:20
other than just 200. I
45:23
think the other 200 errors are
45:25
very, especially 201. I think 201
45:27
accepted is like a very important
45:30
one. Yeah. That feels very hand-wavy.
45:32
Like, I have received your request,
45:34
maybe it worked, maybe it didn't,
45:36
maybe it'll fail at some point
45:38
in the future, we'll see what
45:41
happens. Which I always thought it's
45:43
very hard. Too accepted, not 201.
45:45
No, it's not really. I mean,
45:47
it's... Okay. This is another thing.
45:50
It's only handlavy if you see
45:52
your application transaction as a single
45:54
HDP request, which is not how HDB
45:56
works either, right? You have 202 accepted
45:58
and then something with. in the response
46:01
that you get should tell you how
46:03
to find the status. Yeah, what
46:05
the eventual status could be,
46:07
right? Because accepted means like,
46:09
hey, I got it, but I
46:11
haven't necessarily started processing yet. I'll
46:14
process it at some point. And
46:16
you can give them maybe a
46:18
location header that tells them where
46:20
to find it. Or maybe there's
46:22
a link header that gives it.
46:24
But there's options. So you have
46:27
to add more to it. on
46:29
polling or something like that, or
46:31
we'll send you an email, or
46:33
whatever, right? But it's semantically telling
46:36
you something that I don't
46:38
think 200 encodes well. Yeah, that's
46:40
fair. Yeah, kind of to Ian's point,
46:42
I'm kind of in the same
46:44
camp. I've never liked the idea
46:47
of returning an error code when
46:49
you attempt to delete something that
46:51
doesn't exist. Like, that's not a
46:53
failure in my mental model. So
46:56
if you send a delete. So
46:58
delete is not going to be a 404.
47:00
Oh, no. What? Yeah, I've seen people delete.
47:02
Not found for a delete of a
47:04
thing that doesn't exist. I've seen people
47:06
do it where the first requested
47:08
delete was the 204, right? Like
47:11
no content, because there was actually
47:13
something to delete and the delete
47:15
that happened. And then the follow
47:17
request was a 404, because there
47:19
was nothing to delete, and it
47:21
didn't do anything. And I'm like,
47:23
that's probably the most correct. Is
47:25
it, though? Yeah, I think it is
47:27
the most correct. So I guess it
47:30
depends on really how you want to
47:32
see what are resources like if a
47:34
resource is like if it's a thing
47:36
that actually like exists within your in
47:39
your database and you have now deleted
47:41
it then yes the proper HDB status
47:43
code for anything you do on it
47:46
should be 404 like that thing doesn't
47:48
exist anymore. Many resources conceptually
47:50
aren't really that Like if
47:53
you delete like a user you haven't deleted
47:55
the person so like I don't know it
47:57
really just depends on how you want to
47:59
conceptualize what a resource is, whether you
48:01
want to send back a 404 or
48:03
a 204 or, you know, whatever. And
48:06
I guess that's where it always
48:08
kind of irked me is you
48:10
send a delete for a thing and
48:12
that thing is valid at the
48:14
time that you send the request
48:17
and the system deletes it.
48:19
Then a 404 response becomes
48:21
nebulous because does that mean
48:23
you try to delete something
48:25
that wasn't there and it
48:28
was a bad request? As in
48:30
that thing you wanted to delete
48:32
isn't a thing or is the 404, hey
48:34
your thing is gone now so it
48:37
does not exist. Well I don't think
48:39
sending a 404 for a
48:41
delete request that deletes something
48:43
is correct. That seems incorrect
48:46
because the thing did exist
48:48
when you made the HDP request.
48:50
So you took the request
48:52
you did something and now
48:54
you're responding to the request.
48:56
And whereas like a 404 is like
48:59
when you made the request that thing
49:01
didn't exist, what it was process that
49:03
thing didn't exist, if in the process
49:05
of processing it that they no longer
49:07
exist, then that's a that's a different
49:09
status code. That's a different thing. You
49:11
know what? This is like, I like
49:13
this, this question because it's, it really
49:15
makes you think about where the transport
49:18
ends and where your application logic begins.
49:20
And like, the more we talk about
49:22
this specific example, the more I'm like crap.
49:24
At this point, I'll just send back
49:26
the 200 okay with a response body
49:28
and let the user figure it out
49:31
there. Like, because I don't know the
49:33
answer. Like the response body is
49:35
an important thing, right? I mean,
49:37
sending back 204 don't content means
49:40
there's like literally nothing, you can't,
49:42
nothing, you can't include a body.
49:44
There's, there's nothing there. So in
49:47
the case of a delete, sending a
49:49
200 and then more information is a
49:51
good thing. that Jamie sent us
49:53
a link in the chat that
49:55
I I immediately clicked with no
49:57
I didn't even I just click
50:00
I was like, you know
50:02
what? Yeah, I trust Jamie.
50:04
I'll click whatever you said.
50:06
And it was a link
50:08
to HDTP. cat slash like
50:10
200 or slash 404 or
50:12
whatever HDP code and it
50:15
gives you a nice little
50:17
picture of cats doing various
50:19
things with those HDP codes.
50:21
So thank you for not
50:23
fishing me. Yep, I
50:26
clicked it
50:29
so quickly.
50:31
HDP.cat looks
50:34
trustworthy to me,
50:37
I don't know.
50:39
I was gonna
50:41
say, would Chris
50:44
have clicked it
50:47
if it was
50:50
HD.TAC? Oh wait, Christian's giving
50:52
me, oh kick, Jamie, oh okay,
50:54
sorry, no. I mean, you know,
50:56
as long as, you know, if,
50:58
if, if, if, if, if, if
51:00
you need to attack loads in
51:02
safari, then I'll be, I'll, trust
51:04
that it exists. But if it
51:07
gets like a TLD doesn't
51:09
exist, then, I don't know,
51:11
maybe it's something that only
51:13
works on Linux or weird,
51:15
good news distributions distributions
51:18
of things.
51:20
Yeah, not if you not if you brew
51:22
install tack, wait, I should check. Does that
51:25
work? I don't think that works. I think
51:27
you did brew and install for you. Yeah,
51:29
yeah. So if I did like a
51:31
brew info tack, I'm probably gonna get
51:33
like this thing doesn't exist. Which, is
51:35
that a 404 though? Or is it? How much
51:37
research? Yeah, can't find formula type.
51:39
I do think at the end
51:42
of the day, we're talking about
51:44
these status codes. Is like, pick
51:46
something and be consistent and be
51:48
consistent. Like I think that's
51:50
what really matters here, right?
51:52
I've picked something that says
51:54
well. Oh yeah. Sorry, so what with
51:56
the two or four? I put this
51:58
as a on-pop earlier. but I'm
52:00
burning through them. I would
52:03
generally say don't do 204,
52:05
no content because you're just
52:07
going to paint yourself into
52:09
a corner. At some point
52:11
you do actually want to
52:14
return some information.
52:16
So doing an empty Jason object
52:18
with a 200 gives you the
52:20
chance to change your mind in
52:22
the future without having to do
52:25
like a V2 API, which we
52:27
all know is. Chaos. Definitely
52:29
chaos. Is that empty Jason
52:32
body like an array, an
52:34
object? Does it matter? I'd
52:36
say an empty object. Okay.
52:39
Because returning an empty
52:41
array, I dislike APIs that
52:43
do that. Right. Give me
52:46
an object that can then
52:48
be extended with other things.
52:50
Yeah, and usually the delete case is
52:52
usually one thing at a time.
52:54
So, yeah. I mean like the
52:56
stroke counterpoint return the literal string
52:58
no, I'll lower case. Oh God.
53:00
Oh no. Return in an escape
53:02
sequence or something? No. The so
53:04
the spec does point out like
53:06
a place where 204 is useful
53:08
which is like if you do
53:10
a put and then you can
53:13
just send an e tag back that's
53:15
like the e tag value for
53:17
that put and not send anything
53:19
else. So I think there's really
53:21
really if your if your response
53:23
only needs headers to indicate what
53:25
the application should do or what
53:27
the application can do, then I
53:29
think 204 is appropriate because you
53:31
don't need the body. Yeah, I
53:34
think that's valid. It's a success
53:36
and there's no content so you
53:38
don't have to read the body and
53:40
you can look at the headers. That feels
53:42
like a valid scenario. I don't know.
53:45
I'm with Jamie on this one.
53:47
Just avoid 204. Like I've definitely
53:49
run it, ran into issues where
53:51
like you're expecting it and something
53:53
changed. And I don't know. Even if
53:55
your spec doesn't say this
53:57
will always return 204.
54:00
Right, people, like once you start,
54:02
people expect it and they're going
54:04
to handle errors differently. Like they're
54:06
not, yeah, I don't know. I
54:08
don't know. Just handle. This conversation
54:10
has taught me that HTP is
54:12
a very, very interesting protocol that's
54:14
very extensible in many ways. And
54:16
even in the year of our
54:19
Lord 2025, we still have not
54:21
figured out the best way to
54:23
use HCP tools full's potential. And
54:25
it's. Fairly ironic that the primary
54:27
use case for HCTP now no
54:29
longer involves hypertext whatsoever. Oh, yeah.
54:31
Don't open up that kingdom right
54:33
now. Jane, we're going to bring
54:36
you back on to talk about
54:38
that. HTL is very much hypertext.
54:40
Yeah, but they're far more things
54:42
sending Jason than HTL at this
54:44
point. There's a lot of websites
54:46
in the world. There are a
54:48
lot of sites. And you know
54:50
how many? There's a lot. I
54:53
mean, yes. But also, you know,
54:55
Jason should be hypertext. So that's
54:57
Jason's problem, not HDDP's problem. But
54:59
yes. Although, HDMO is more hypermedia
55:01
than hypertext these days. But yes.
55:03
So anyway. I have one question.
55:05
How do you feel about nonstandard
55:07
HDP status codes? Like when we
55:09
use a lot is 49. Like
55:12
not for the actual clients, but
55:14
to know like if the connect
55:16
it's client closed connection It's what
55:18
like engine X uses. I was
55:20
to say isn't that engine X's
55:22
code? Yeah, and we use that
55:24
like if you're if the client
55:26
does like cancel their request will
55:29
return up 49 even though they'll
55:31
never see it but like all
55:33
of our standing and everything gets
55:35
it picks it up I think
55:37
if like if you have a
55:39
kind of I think it's where
55:41
I fall to one like custom
55:43
HDP methods is that if you
55:46
have a constrained enough environment or
55:48
if you like understand like if
55:50
you as a server know that
55:52
your client knows how to properly
55:54
interpret whatever you're gonna send back
55:56
to them I think it's okay.
55:58
I don't think you should just
56:00
send it back as if like
56:03
if you have to assume at
56:05
first that they're just a spec
56:07
compliant HDP client right so they're
56:09
like okay if I you know
56:11
if you send me back this
56:13
400 I'm gonna interpret it in
56:15
the way of the spec and
56:17
then I think if there's something
56:19
else like a header or something
56:22
that has indicated oh I have
56:24
this expanded functionality then I think
56:26
it's fine to you know use
56:28
that expanded functionality that you were
56:30
just told that it has. That's
56:32
what I, yeah, I included that,
56:34
yeah, same. Oh, sorry, I was
56:36
reading, I was reading a shownote.
56:39
As long as the, as long
56:41
as things have indicated that they
56:43
can accept this or they can
56:45
process it, once again, same kind
56:47
of content negotiation problem, but as
56:49
long as either inbound or out
56:51
of bound, it has been negotiated,
56:53
I think it's returning some random
56:56
error codes that... they used internally
56:58
similar to like the engine X
57:00
one and that just like broke
57:02
stuff both because clients didn't understand
57:04
it but I think there were
57:06
also some like intermediate proxies and
57:08
things that also didn't understand the
57:10
status code so everything just blew
57:12
up as a little bit again
57:15
by common denominator try and stick
57:17
to the things that people don't
57:19
even use because yeah that's a
57:21
that's a very valid point unless
57:23
your CDN's also understand all of
57:25
your non-standard status codes it's probably
57:27
a bad idea yeah that's why
57:29
you have to get it behind
57:32
a way that you know that
57:34
the client will understand it and
57:36
we're back to 200 with response
57:38
body that says we're just going
57:40
back to it I have I
57:42
have one more API design thing
57:44
I'd love to talk about and
57:46
that is IDs Right. This part
57:49
of the show is supporter exclusive
57:51
content. So if you want to
57:53
hear what Ian has to say
57:55
about identifiers, I don't know where
57:57
to fall through dot fm slash
57:59
subscribe. But
58:02
with that, let's do some un-pops.
58:04
Who's got some un-pops? Who's got
58:06
some un-pops? Anybody got un-pops? I
58:09
think Jamie has like 30 un-pops.
58:11
Yeah. Jamie has a long list
58:13
of un-pops. So I'm sure it
58:15
was one of the episodes, Angelica,
58:18
shared an un-pop, back-on, go-time RIP,
58:20
where you mentioned about like storing
58:22
a list of un-pops. So that's
58:25
why I have so many prepared
58:27
so many prepared. Because over time
58:29
I have been thinking of them,
58:31
fighting my time. It's funny because
58:34
I'm reading Jamie's Unpops, like the
58:36
list, and the very first one
58:38
is like, won't do them all
58:41
or won't say them all? Yes,
58:43
you will. You're going to say
58:45
them all, don't you worry? How
58:48
dare you? I have one that
58:50
since we're talking about APIs and
58:52
transfer protocols and bodies, XMLs good.
58:54
Is my is my Unpop, Unpop
58:57
or opinion. I know Jason gets
58:59
all the love these days and
59:01
XML with namespaces with all the
59:04
values as the content between the
59:06
elements was admittedly terrible, but XML
59:08
as a document structure, I prefer
59:10
to Jason for no other reason
59:13
than the element names give you
59:15
a hint of what it what
59:17
it is compared to Jason objects
59:20
which are just open brace. dictionary
59:22
of string names to values. I
59:24
mean, what namespace is, excellent, the
59:27
element name tells you exactly what
59:29
it is. And I will say,
59:31
as long as you also use
59:33
attributes where it makes sense, like
59:36
I'm in favor of like namespaces,
59:38
attributes, and if it's a value,
59:40
put the value in. But I
59:43
enjoy that. I like XML. It
59:45
gives you that flexibility. You can
59:47
include multiple different things in there,
59:49
and you know what they mean.
59:52
It's very defined. Unlike Jason, which
59:54
is just a soup of strings.
59:56
We did a lot of work
59:59
in XML in my Windows days
1:00:01
and we were We leaned hard
1:00:03
into attribute-based XML versus element-based and
1:00:05
avoided overly leaning into namespaces because
1:00:08
namespaces are hard to manage. But
1:00:10
we got a lot of mileage
1:00:12
out of XML without dealing with
1:00:15
all the terribleness that was the
1:00:17
soap protocol. Yeah. XML gets a
1:00:19
bad rat because of soap. that
1:00:22
it's like self-describing though. Like I
1:00:24
don't like using rest. Like not
1:00:26
wrong. You like you like the
1:00:28
aspects of it that are like
1:00:31
it tells it it tells it
1:00:33
tells it tells it tells you
1:00:35
about itself which is just nice.
1:00:38
Yeah like like like like a
1:00:40
rest API I have to go
1:00:42
find the open at API spec
1:00:44
and everything like a soap API
1:00:47
I can just request request request
1:00:49
the whistle and I have it.
1:00:51
Is that how you say that,
1:00:54
Whistle, WSTL? Yes, Whistle. Shutter again.
1:00:56
Yeah, I mean, I'll be pedantic.
1:00:58
A properly designed rest API, however,
1:01:01
you would just have a nice
1:01:03
little spec you can go read,
1:01:05
which is basically a whistle. It
1:01:07
could be machine readable if you
1:01:10
want it to be. But you
1:01:12
would have that. And that would
1:01:14
be, that'd be nice. That'd be
1:01:17
nice. But no one designs API
1:01:19
is that way. I tend to
1:01:21
do. kind of the analog for
1:01:23
protobuff APIs, whereas as part of
1:01:26
building the system you can generate
1:01:28
a The file the binary file
1:01:30
descriptor, which is kind of your
1:01:33
compiled API definition Go embed that
1:01:35
into your binary and include an
1:01:37
in point that returns that descriptor.
1:01:39
Yes. Yeah. Oh, that's good. Then
1:01:42
someone can just ask you for
1:01:44
hey, what is your API look
1:01:46
like here? It's Jamie, did you
1:01:49
decide which of these many un-pops
1:01:51
you want to unpop? Yeah, at
1:01:53
the end of January I wrote
1:01:56
a post about... the new go
1:01:58
tool that's coming in, go 124,
1:02:00
that is now out, obviously, because
1:02:02
it's March. We shipped this episode
1:02:05
early, now you're going to sound
1:02:07
weird. Yeah. I was less excited
1:02:09
once writing the post, once I
1:02:12
actually got to play around with
1:02:14
it. And I still posted it
1:02:16
with the positivity, but I think
1:02:18
it's not quite where it should
1:02:21
be. And I think most people
1:02:23
aren't going to use it. And
1:02:25
yeah, I've also seen lots of
1:02:28
angry people on Reddit and Hacker
1:02:30
News who are like, oh, goes
1:02:32
getting so bloated. And who even
1:02:34
asked for this? And it's like,
1:02:37
well, lots of people ask for
1:02:39
it. great. I was excited about
1:02:41
the feature. I've used Tools.go with
1:02:44
a Plus Build None lots of
1:02:46
times to have references to build
1:02:48
tools in go-out mods to make
1:02:51
them straightforward to use. I've used
1:02:53
it a lot so I was
1:02:55
very excited about the idea of
1:02:57
that being built into the tool
1:03:00
chain. Then I went and read
1:03:02
your blog and yes, it's not
1:03:04
quite there. So I haven't read
1:03:07
how it works or your blog
1:03:09
post. What are the issues? So
1:03:11
have you seen like the tools.go
1:03:13
pattern? Yeah yeah yeah yeah. So
1:03:16
one of the key issues with
1:03:18
that is that it then adds
1:03:20
indirect dependencies on all of the,
1:03:23
oh it adds a direct dependency
1:03:25
for the tool that you're requiring
1:03:27
and then pollutes with indirect. with
1:03:30
go to is it literally doesn't.
1:03:32
same thing but there is an
1:03:34
additional directive in your go dot
1:03:36
mod to say and this module
1:03:39
is a tool so you still
1:03:41
have the indirect dependencies that are
1:03:43
polluting everything which yeah I think
1:03:46
the main positive that I'm taken
1:03:48
away from this is the fact
1:03:50
that with go 124 they're now
1:03:52
cashing things that are either through
1:03:55
go tool or through go run
1:03:57
as far as I'm aware so
1:03:59
The idea will be that even
1:04:02
if you're using the old tools.go
1:04:04
you will get a performance boost,
1:04:06
whereas before it would recompile your
1:04:08
go runs each time. So it
1:04:11
will be a bit better. Yeah,
1:04:13
I'm not really a fan of
1:04:15
the idea of having to run
1:04:18
go tool and the name rather
1:04:20
than run the tool itself. So
1:04:22
the pattern with tools.go is you
1:04:25
have the reference in your tools.go
1:04:27
file, then you can go install.
1:04:29
the name of the tool in
1:04:31
Project Durbin because don't go install
1:04:34
into global directories. But then you
1:04:36
can just run the tool in
1:04:38
your build script, air quotes, whatever
1:04:41
the case may be. So I
1:04:43
would disagree that that's the pattern,
1:04:45
and I would see the pattern
1:04:47
as running go run of the
1:04:50
module path, because then it definitely
1:04:52
is 100%. You literally can just
1:04:54
get clone and run like go
1:04:57
generate and then that works However
1:04:59
by installing them ahead of time,
1:05:01
but you get the performance boost
1:05:04
and everything But then you still
1:05:06
have the problem of if you're
1:05:08
go generates then call a binary
1:05:10
How does that binary get installed?
1:05:13
Yeah, I've kind of in recent
1:05:15
times I've moved away from using
1:05:17
go generate for those kind of
1:05:20
things we get bitten pretty hard
1:05:22
when they fixed, air quotes, the
1:05:24
security hole and go generate, where
1:05:26
you could go generate, it would
1:05:29
run. arbitrary command, including things outside
1:05:31
of your working directory and things
1:05:33
that haven't been verified. So it
1:05:36
was a valid security concern, but
1:05:38
we had a lot of go
1:05:40
generate directives that were running local
1:05:42
scripts and local binaries that started
1:05:45
failing. And the other thing that
1:05:47
we kind of ran into is
1:05:49
go generate only accepts a single
1:05:52
command and The particular thing that
1:05:54
bit us hard was running a
1:05:56
script, a tool in another internal
1:05:59
repo, but you want the version,
1:06:01
kind of the same problem I
1:06:03
brought up in the fall-through channel
1:06:05
and slack, where you want the
1:06:08
version of the tool that goes
1:06:10
with the library that you're using.
1:06:12
We, in go-path days, you could
1:06:15
just run go-path source, whatever. If
1:06:17
it's packaged in a module, the
1:06:19
path is in your module cache
1:06:21
and you need to go list
1:06:24
with a minus F to get
1:06:26
the path to the module on
1:06:28
disk in your local module cache
1:06:31
to then get the thing to
1:06:33
run. You can't do that in
1:06:35
a go generate because there's no
1:06:37
bash subshell to go run the
1:06:40
command and get to do two
1:06:42
steps. So we ended up doing
1:06:44
a lot of work to kind
1:06:47
of pull those kind of things.
1:06:49
out of a go generate directive
1:06:51
in the code into you just
1:06:54
a generate make target. All right,
1:06:56
Matt. Oh yeah. Yeah, mine's very
1:06:58
quick. My unpopular opinion is that
1:07:00
you should not use single letter
1:07:03
CLI options in your documentation. So
1:07:05
when you're like writing docs to
1:07:07
show like your CLI usage for
1:07:10
something and everyone just uses a
1:07:12
bunch of single letter CLI options,
1:07:14
don't do that. It's not. Well
1:07:16
documenting now you're just forcing your
1:07:19
users to go look up. the
1:07:21
help text for your CLI to
1:07:23
see what those options even mean,
1:07:26
use the long options if they're
1:07:28
available. 60 years of Unix Man
1:07:30
pages disagree with you. Sure, but
1:07:33
a lot of them haven't even
1:07:35
thought through what those options mean.
1:07:37
Like a lot of people would
1:07:39
just say, oh I'm going to
1:07:42
use dash I to mean pencil.
1:07:44
And it's just like, what? Have
1:07:46
you thought about any of this?
1:07:49
Similar to what we're talking about,
1:07:51
the API design, like, think through
1:07:53
the design of your CLI, too,
1:07:55
and don't just choose an option
1:07:58
because it's convenient for you to
1:08:00
type. Choose an option because it's
1:08:02
semantically meaningful for the CLI design.
1:08:05
And to be honest, when I
1:08:07
saw you starting typing this one,
1:08:09
I thought you were going to
1:08:11
end as, don't use single letter
1:08:14
CLI options, full stop. No, no,
1:08:16
no, no, no, no, no, no,
1:08:18
you should definitely use, you should
1:08:21
definitely use them, but when you
1:08:23
should definitely use them, but when
1:08:25
you're, but when you're, but when
1:08:28
you're documenting, but when you're documenting,
1:08:30
but when you're documenting something, but
1:08:32
when you're documenting something, but when
1:08:34
you're documenting something, but when you're
1:08:37
documenting something, but when you're documenting
1:08:39
something, but when you're documenting something,
1:08:41
but when you're documenting something, but
1:08:44
when you're documenting something, but when
1:08:46
you're use the full options it
1:08:48
helps them like get a picture
1:08:50
of what SCLI is doing. Otherwise
1:08:53
you just you just lose your
1:08:55
reader right like dash i dash
1:08:57
v dash just like nobody knows
1:09:00
what that means image intent ignore
1:09:02
me differ depending on the operating
1:09:04
system I could go hard on
1:09:06
popular and say use slash instead
1:09:09
of minus ill all right windows
1:09:11
over here no no thank you
1:09:13
Okay Dylan that's a that's and
1:09:16
do you have an unpopular opinion?
1:09:18
I do. We're talking about API
1:09:20
design. So for URL parameters it
1:09:23
seems pretty popular to allow like
1:09:25
a comma separated list as like
1:09:27
you know question mark ID equals
1:09:29
one comma two comma three. I
1:09:32
think I prefer like the spec
1:09:34
compliant ID question mark ID equals
1:09:36
one and then a second one
1:09:39
ID equals one ID equals two
1:09:41
ID equals three. Like I think
1:09:43
I prefer that just. It's the
1:09:45
spec also allows plus so that
1:09:48
there are some parches that will
1:09:50
take ID equals one and ID
1:09:52
equals two and percent ID equals
1:09:55
three and we'll turn them into
1:09:57
ID equals one. plus two plus
1:09:59
three. Who does that? I've seen
1:10:02
it a few times. I don't
1:10:04
know. I think I, yeah, I
1:10:06
just prefer not having like, comma
1:10:08
separated lists places. It just gets
1:10:11
a nice fair. I don't want
1:10:13
to put a parser in my.
1:10:15
Exactly. Yeah, I don't want a
1:10:18
second parse step. Just let the
1:10:20
parser do its thing. Got my
1:10:22
query string, now I got a
1:10:24
parse again. Yikes. Okay. All right.
1:10:27
Well, I don't have any. A
1:10:29
lot of people don't even know
1:10:31
you can do that. Like, put
1:10:34
the same value more than once.
1:10:36
Yeah, you can do with headers
1:10:38
as well. For some headers. Some
1:10:40
headers are not allowed to be
1:10:43
specified more than once. Yeah. All
1:10:45
right. Yeah, I think I said
1:10:47
enough on paper. I think this
1:10:50
episode, don't. It's anything more. Or
1:10:52
I can just say, use HDPP
1:10:54
properly. You're all wrong. People should
1:10:57
just learn. And the world will
1:10:59
be able to be able to
1:11:01
replace for it. it would be
1:11:03
fine. It's not even that. It's
1:11:06
like if everybody could just follow
1:11:08
the specification and follow what rest
1:11:10
actually means instead of what someone
1:11:13
just made up and then the
1:11:15
author of rest was like this
1:11:17
is incorrect and everybody was like
1:11:19
but we think it's correct and
1:11:22
it's like okay well that's weird
1:11:24
why are you doing it this
1:11:26
way it doesn't align with the
1:11:29
values of the thing that I
1:11:31
wrote and but everybody's like no
1:11:33
we're gonna steal your name now.
1:11:36
I mean that's well still the
1:11:38
name of the thing you made.
1:11:40
I will pull a skeleton meme
1:11:42
and as we wrap I'll drop
1:11:45
hadios and then leave. No, we're
1:11:47
leaving now. Oh no, oh no.
1:11:49
Just call it the hyper media
1:11:52
constraint. The fact that hadios or
1:11:54
hadios or whatever, hyper media as
1:11:56
the engine of application state, like
1:11:58
you don't know what that means.
1:12:01
You don't know what that means.
1:12:03
You don't know what those words
1:12:05
mean. You don't know what that
1:12:08
sentence means. Stop using it. It
1:12:10
appears exactly once. Maybe. I think
1:12:12
exactly once in the dissertation. But
1:12:14
like hold out. Just call it
1:12:17
the hyperme. media constraint. James shared
1:12:19
a URL. in chat about like
1:12:21
can we please stop saying just
1:12:24
and I was literally having this
1:12:26
conversation with a friend yesterday about
1:12:28
like we hide so much behind
1:12:31
the word just just implement a
1:12:33
no no I'm going on tangent
1:12:35
I don't care I'm tangent just
1:12:37
I'm tangent just just implement the
1:12:40
API just read the code is
1:12:42
the worst one just read the
1:12:44
code no I don't want to
1:12:47
just read your code because your
1:12:49
code is garbage and you don't
1:12:51
have any documentation and you don't
1:12:53
have any documentation to write documentation
1:12:56
for your code I'm not going
1:12:58
to be bothered reading your code.
1:13:00
Because you clearly didn't care about
1:13:03
my time. Why should I care
1:13:05
about your time? And so yeah,
1:13:07
that word just is so frustrating
1:13:09
and it hides so much complexity.
1:13:12
And it's just like, it's an
1:13:14
excuse for not formulating a proper
1:13:16
thought and like documenting what you're
1:13:19
doing. Just do the thing. Just
1:13:21
leave. self-documented code. Yeah. Okay, we
1:13:23
can't go on any more rams.
1:13:26
I know, I know, I'm sorry,
1:13:28
I'm sorry. We're already at two
1:13:30
hours of recording. Yeah, so we
1:13:32
just, we just, you know, Jamie,
1:13:35
where can people find you? Yeah.
1:13:37
Um, JVT.me slash elsewhere is the
1:13:39
best place. It has all of
1:13:42
my links. Um, yeah. Perfect. Perfect.
1:13:44
And with that, viewers, listeners. As
1:13:46
always, please like. Just like and
1:13:48
subscribe. Oh no. Like, comment, share,
1:13:51
subscribe. If you're on YouTube, hit
1:13:53
the little notification bell and reach
1:13:55
out to us on social media.
1:13:58
Do you agree? Should we use
1:14:00
just? Should we not use just?
1:14:02
What you think about all our
1:14:05
opinions about APIs? Are we wrong?
1:14:07
Do you think we're wrong? You
1:14:09
can reach out or you can
1:14:11
send us a letter once again.
1:14:14
I think by the point this
1:14:16
episode ships, everybody will know what
1:14:18
the PO box is. So just
1:14:21
that does some things, that does
1:14:23
some letters, some nice things. Just,
1:14:25
oh my goodness. Okay, we're done.
1:14:27
Tagline, it's what we do. Yeah.
1:14:30
Uh, thank you so much Jamie
1:14:32
for coming on. Thanks on me.
1:14:34
Yeah, it's been a pleasure. Yeah,
1:14:37
thank you. Bye everybody. Bye. Take
1:14:39
the button. Click the button. Click
1:14:41
the button. Just certified mail, please.
1:14:43
I'm sorry. You said the button.
1:14:46
I'm sorry. Just click the button.
1:14:48
I'm sorry. Just click the button.
1:14:50
Okay. Okay. Okay. The button. On
1:14:53
next week's episode, we have Kelsey
1:14:55
Hightower joining us to talk about
1:14:57
go, AI, Devrel, and so much
1:15:00
more. You really don't want to
1:15:02
miss that episode. This episode of
1:15:04
Fall Through was hosted by Chris
1:15:06
Brando, with co-host Matthew Snobria, Ian
1:15:09
Wester Lopshire, and Dylan Burke. Our
1:15:11
guest this week was Jamie Tana.
1:15:13
Author is produced by Chris Brando
1:15:16
and Angelica Hill, and our beats
1:15:18
are by Breakmaster Cylinder. Somebody
1:15:29
that I used to
1:15:31
go.
Podchaser is the ultimate destination for podcast data, search, and discovery. Learn More