Decoupling Applications from Architectures - Jeff Hoffer
Hello, all right.
Thank you everybody for coming.
My name is Jeff Hoffer, and my talk is decoupling applications from architecture.
I gave a talk on this side last year and there was literally 10% of the people in the room
that there is now, so thank you all for showing up.
I really appreciate it.
The lights are really bright so I can't see anybody's face, which is probably a good thing.
All right.
So a little bit more about me.
I've been professionally developing software for 20 years, and I recently joined a company
called bluebeam.
It's for the design bid and build phases of projects.
You can find me on GitHub at @eudaimos, it's a word I made up, kind of.
It was inspired by Aristotle's eudaiminea.
The inspiration is my son Eric who's here with my wife this week, if you see us around,
he's nine now, please say hi.
So it's appropriate that I work in a software company for construction.
I notice that as human beings, we've been in the practice of building things for a long
time.
We built some amazing things that have stood the test of time, beyond which most of us
can even conceptualize the length of that time.
And with software we've come up with a single-most malleable thing which we've ever had to build
anything, and it's been pretty great.
But still there's something not quite right.
When I worked in Santa Monica, there was a lot of construction nearby all the time, and
as we were walking our VP of Engineering, my boss used to point out how quickly they
could build these buildings versus how long it took us to deliver software.
[laughter] So how do we keep ending up here?
[laughter] We either build it too slow, we build it wrong,
or both.
And how does that happen?
We get asks from people who don't really know the difference between building application
and building it well.
But they want to make sure that we don't build it well, sometimes.
That' d be great.
So we end up in this Kobayashi Maru scenario and for the younger folks out there who haven't
seen Star Trek II.
It's designed to be a no-win scenario.
It's there to determine how we will respond, and unfortunately most of our responses are
this.
[laughter] Where the needs of the many outweigh the needs
of the few, right?
And then we get back to our brittle code.
So what do I mean exactly by application versus architecture?
So we should probably define this.
And we can take a step and define it based on who defines each for our software.
Product team will define what the application does, engineers will build it, QA validate.
Engineers work with software architects to define the needs.
They build the architecture, QA validates nonfunctional constraints.
So I'd like to take this and define these as layers.
So we have an application we call that the development layer and architecture is the
architecture layer.
And if we can organize these into layers, then we can separate them like what Docker
was able to do for apps in DevOps.
There's also four aspects of software.
Taking another view here, what an application represents to a user, how it does its job,
where it executes, and when it does what it's designed to do.
Unfortunately, when working between the development and architecture layers, we commingle these
four aspects into a kind of a mess.
So let's go back to our layer definition and define them using these four aspects.
If we say the development layer is the what and the when, and the architecture layer is
how and where.
We can organize them this way and if we can organize them this way, along these layers,
and make them orthogonal to each other, then we have the decoupling we're looking for.
So if we take these aspects and graft them on to our layers, the development layer has
what the context of the application is for a user, when does a user go from one context
to another.
architecturally how will the app move from context to context and where will the code
reacting to this actually execute?
So if we get into the details of that so that it actually matters to us who have to build
this stuff, we can borrow some concepts from computer science where we say the what these
contexts are signals, when is triggering and the reaction to these signals.
And the how is -- and the where is a signal network and the implementation of where that
signal network will run.
So I want to introduce a few things for the demo first.
There's RealWorld.
If you haven't gone to realworld.io, it's beyond a standard to do app.
It involves conduit which is a Medium-like blog platform and there's lots of concepts
which are integrating different front ends and different back ends all doing basically
the same functionality as this Conduit app.
I also wanted to introduce TAO.js.
There's a project that I've been working on as a solution for this.
It has a meta language for defining signals that end up in the code, using trigrams.
It provides interaction primitives to determine when to interact with these signals
and it implements signal networks.
So a trigram, the library TAO actually comes from term action orient, so we want to describe
these contexts with these three dimensions.
So term is something, action is an action taking place, and orientation -- orient is
a perspective.
Three ways to interact and respond to signals is inline, do them in order.
Async, do it in parallel as a side effect.
And intercept is do it first and optionally you can interrupt the flow.
And implementing signal networks, it abstracts that away from the application code, and it
also has the implements a signal chaining, so that if you are reacting to a signal from
the network, you can provide it with another signal that will chain itself, so this gives
us very small bits of code that we can implement now as it -- using the signal network as the
medium for the interaction.
So taking this back to our notion of aspects to layers and now to TAO.js, what we're able
to do with the development layer using TAO.js is the powers that engineers use to define
these trigrams and define the desired behaviors when these are signaled in the signal network.
The engineers implement these behaviors in the code using these interaction primitives
on the signal network.
And the architectural layer, we allow the architect or whoever's implementing the architecture
to choose the signal network, and implement using that, and decide where that signal network
will run, so we've now abstracted away where the code has to run in order to interact and
provide the development layer with what it needs.
A quick note about trigrams versus an application context, so think of a trigram, it's a 3-dimensional
signal, also an event.
You can subscribe to trigrams on the signal network and it's like a class in an OOP where
ar application context would be an object in OOP, and so it's a specific context and
so it combines this trigram with the actual data associated with the event that's happening
the signal.
So when going through the real world conduit application, what we'll do first is define
the terms.
We have an app, user, article, comment.
We'll define some actions, init, load, find, exit.
We'll define the orientation so we have a portal and an anonymous orientation, so portal
is when you're logged in.
Anonymous is when you're not.
And then we'll define some paths.
This allows us to understand how to react to signals on the network, and then throwback
new signals in order to keep this chain going.
And this is why we also develop or define very granular actions, so that we can have
more granular chains and we can hook into these at any time.
So I'm going to go into the demo portion.
OK.
So we're going to start with our Conduit app.
And -- of course! [laughter]
OK.
Oh, I understand why.
All right.
Um, so this is working with their -- with the default API, which is using a service
somewhere.
And we're going to -- oops, wrong one.
Sorry.
Of course this all worked in the room.
(Whispers) now completely broken.
So I'm going to skip showing it as a ... because it doesn't seem to want to behave with Redux.
So it was a Redux React based application.
That still doesn't want to play nice.
Very sorry for this.
AUDIENCE: You got it!
>> Thank you.
I can't see who you are.
But thank you.
All right, let's just turn all of it on.
Anybody know which screen I'm on?
I can't imagine what changed.
Oh, I, yeah, now I know.
This is the best!
OK.
[applause]
Getting the applause for code you didn't write, it's, thank you!
[laughter] So this is Conduit.
This is the basic React Redux example I think probably the first one they did.
So we're going to modify this, time willing.
To use TAO.js.
So this is actually hitting the API for that, we can sign in, go to home page, so you see
there's all this functionality.
There's lots of ...
so this application has a bunch of reducers that we're used to, all the Redux boilerplating,
the components are React, they have a lot of baked-in Redux, so we've sort of embedded
all of this together, what these -- how we call the API, what to do on various -- within
various components.
So if we go ahead and -- I'm going to turn off Redux logging.
We're going to switch to using some alternative components and not all of
them are necessary.
So we notice, still looks the same.
Still works.
I didn't get that tab thing working yet.
We see the results change.
And the way this is implemented is -- so for the home, the original home is here.
It's home page loaded, home page unloaded, applying the tag filter, we now take and add
a handler.
We import a TAO signal network that is the default signal network, and in order to chain
a signal into the network from a handler, we use this app context constructor to return
that as a new signal to follow, and so when we receive the signal to enter home, we want
to tell the application to view home.
Alternatively, we use a data handler from the React library to capture data from a signal,
and store it where we can utilize it again in our render handler, and this -- you provide
it with a context to say any data handlers above, they create this context for me, I
will now have these available to me in my handler that I use.
It's a function as a child component.
And so when it -- this will react when we reach the trigram -- in the application context
that reaches the trigram of home and view and we actually don't care about the orientation.
So it allows for wild-carding, so if you don't provide a term, it's a wild card on that term
-- on that attribute of the trigram.
And so when we render the home, we actually want to use our home component.
This one's new.
We pass down the token, the app name, we get from the app data, which tabs are active,
a tag, and then what to do if they click "All" or click on the feed.
So this is a -- just a standard React component.
There's no logic or anything in here to interact with the signal layer, signal network and
we can treat our components completely on their own, makes them easier to test, easier
to design using storybook, and this works for many things, so this uses a main view.
Vue, and you notice that the interactions are -- there you go.
We pass down this "Click all" which will then --
this will create a signal into the network.
Actually, this is not implemented right.
So going in here and signing in, I come back here and see the signed-in view and we did
that with using a different header.
Nope, wrong window again.
So we have our login component, which is saying if I'm trying to view -- if I'm trying to
enter a user anonymously, I want to view that user anonymously, so that's going to render
-- use a render handler, user view, anonymous, and that will give us our login control.
When we submit, it's going to generate a user find action anonymously.
Because this is just working with what's already here, we have these handlers all defined around
users.
So we find a user anonymously, we have a handler for that, which will grab the email and password
out of the data -- out of the user portion of the data passed in the application context
to our handler, we will leverage the agent and do a login, and then based on how that
responds, we'll either get a user or it will catch an error.
If we get a user, then we want to enter that user in the portal orientation, and provide
the token so that that can be used.
So what's nice about the ability to have these wild card handlers, when we pass the token
and we're in the portal orientation for any of our signals, we make sure to set the token
on the agent every time based on that.
So we don't have to call this anywhere else, but it will always be accurate.
If we are going anonymous, we change -- we set the token to empty.
We also can react to initializing the -- so our initial application context will be appinit
anon, we can grab the token from local storage.
If there is a token, we'll switch to the portal view.
If there's not, intercept sends nothing back, it will chain to the next appinit anon.
That's when it will call load.
So using this, we now can refactor all of the application logic into these handlers
that are small and bite-size and easy to change, and we don't have to worry about how we're
making calls necessarily to move the user around the application.
On the -- not sure how much time I have left.
So going into the API side -- so if I switch this to using the local API -- let me sign
out first.
Clears the token.
And we also see, through logging these, that these chain to each other, -- now, let's ...
We'll use the local API.
Now for this, with the same user, we set up -- we convert these routes from the API where
instead of doing what it was -- what it had here, gets the user find by, it's going directly
against mongo, we can instead create a transponder on our signal network and what a transponder
does is just provide us with a promise when we set the context that we can await.
And we add some handlers to the transponder, so that these will only be handled by the
transponder and do -- perform this action in other handlers we've added to the signal
network.
They'll all be called except for other transponders will not see the signal that I put into the
network.
So when we come in here, we want to get the user, and so this is a -- this is when the
user is coming back to the portal or -- when the user is coming back to the portal, we
want to be able to retrieve that user from the portal, so we will send the signal off
to find the user in the portal and we pass what that user ID came in and what the token
was.
And now in here we have a user find portal, and here's where we implement going to the
database.
And if they're not found, we say it's a fail.
And that the auth failed.
If it's -- if we did find the user, we grab that user and we send it back and saying we
retrieved the user from the portal.
So now on a retrieve, we can send that back in the JSON.
I won't have time to get to it, but what this allows us to do is because we've put the -- we've
put the actual interaction with Mongo here, we can add other handlers and one, put an
intercept handler to handle caching.
If we have a token and we already know the user, we can just get that user from cache,
so return the correct thing and not even go to Mongo.
Or if we want to change our database from Mongo to Postgres or mySQL or a relational
store, we can just implement new handlers and refactor that on the fly without having
to worry about all -- changing the the code of the actual API that the code is relying
upon.
Now, what I've shown is an example of how to do this directly inside of an app or an
API, and then they're still using a REST API as an interface to talk to each other, but
once I have these signals set up on both sides, then I can use the socket io package
without having to go through this REST medium.
I think I'm almost out of time.
Scoot back to -- -- I really want to thank a few people.
I want to thank the people at Thinkster.io for creating the real world app.
I will be developing this further and supplying it to anybody who wants to see -- oh, is my
display thing?
That's nasty!
Let's go ahead and close out of there.
JSConf for being able to have these talks.
I really appreciate to come up here and share my work with everyone, and JS.org for hosting
DNS for so many great projects.
A few resources.
The website for the project is tao.js.org.
The GitHub repo.
There are six cores published.
The transponder came out of the utils package.
You don't need it in the core package.
And so the for build extensions on signal networks.
And the -- and then coming soon, I'll get the 100% test coverage back on the packages.
I did a lot of building for a little while, and updated documentation, especially with
this decoupled architecture focus, so thank you very much for coming, and I really appreciate
it.