Share
Embed
Marrying front with backend
Bastian Hofmann
August 24, 2013
Transcript
151 slides
-
Let's talk about... In the next hour I want to
talk a bit about -
Application architecture application architecture and with that I mean how
to structure your code -
Rapid Development so that you can develop new features quickly
without breaking the rest of your application or accumulation lots of technical debt -
Handling large code-bases especially if your code base becomes larger
and larger, developed by big, growing teams -
Frameworks to the rescue Normally if faced with these problems
people say: don't reinvent the wheel, let's look what's already out there, let's use some framework (or some libraries), or if you have a bad case of the not-invented-here syndrom in your company, you are going to develop your own -
? languages? one/their own product? agency? long lived, short lived
projects? large codebases? frameworks? -
Most Web Frameworks are incomplete But I want to be
a bit opinionated here: ... -
they oftentimes either care about your backend on the server
and serve html pages or apis -
webserver HTML browser JS so the normal flow of a
web application is that some server code serves some html (and maybe some apis) to the browser, in the browser there is some (or a lot of) javascript that manipulates the DOM -
webserver HTML browser JS Ajax and either links to other
pages served by the server or does ajax requests back to the server. this concept is mostly true for either traditional apps as well as single page applications -
webserver HTML browser JS Ajax but the code in both
worlds, server and client, is seldom connected -
duplication de duplication ode duplication code duplication code duplication code
duplication code duplication code duplication code duplication code duplication code duplication code duplication code duplication code duplication code duplicatio code duplicat code duplic code dup code du code cod co co which leads to lot of code duplication in templates, validation logic, models and lots of boiler plate code for communication between server and client -
So? so, how can we change this? before i answer
this question -
ResearchGate gives science back to the people who make it
happen. We help researchers build reputation and accelerate scientific progress. On their terms. ‟ the goal is to give... -
Questions? Ask by the way, if you have any questions
throughout this talk, if you don't understand something, just raise your hand and ask. -
if we take a small peek into a possible future
of web application development -
Meteor http://www.meteor.com or meteor, that try to bridge the gap
between server and client. but they are still very alpha, good for prototyping, writing small apps, trying things out, but personally I wouldn't create a big million lines of code web application with 40+ developers yet -
What about existing applications? and regardless of that, most of
you have existing applications -
Incremental Refactoring the way to make the architecture of your
existing application better and connect your backend with your frontend code is incremental refactoring, since you hopefully can't afford to sit down, stop development for 6 months and rewrite everything -
many roads of course like always there are many ways
to go about that, which totally depends on you existing code -
http://www.youtube.com/watch?v=fqULJBBEVQE what's interesting about all these approaches and with all
these totally different applications and totally different use cases and totally different technologies, that the underlying concepts people come up with are strikingly similar. basically it's thinking about your app in small components, that are as independent as possible. this may even make it directly into browsers: if you haven't heard about it yet, check out the proposed web components standard. -
or what i'm going to talk about for the reminder
of the talk, what we did at RG -
status quo to volker: so RG: when we started about
over a year ago, the status quo was: -
self-written framework, Large, old codebase, some architecture and code smells,
but also some nicely done things -
lazy DB queries in view thanks to doctrine1 with db
queries executed in the view -
on the frontend yui3, but every page was built with
custom modules, not much reuse of existing modules but very basic ones (also some legacy pages with yui2, prototype.js and scriptaculous) -
Components let's rethink the way we are building pages or
our whole application and separate it into small components -
so looking at a page (that's what our profile looked
like over a year ago), you can identify lots of small components nested into each other on a page -
Server JS Browser JSON HTML HTML so it should have
it's own url, and for seo reasons can just be included in a page rendered to a browser by the server or can be fetched separately or nested within other components by the apps javascript, where we only wanted to transport the data to the client and render it into html there -
JS is part of the component the javascript to bind
on dom events and manipulate the components should be part of the component -
Share code between server and client and we wanted to
share as much code between server and client as possible (considering our heterogeneous architectur, js client, php server) -
It needs to be fast of course ... concerning performance
as well as development speed and productivity -
PHP Controller Mustache Template JavaScript view class Widget Providing data
Handling browser events Displaying data so to sum it up an widget contains -
Why no models in the frontend? http://backbonejs.org/#Model compared to flight.js,
backbone we decided against having dedicated models in the frontend where views could subscribe on because of legacy reasons. so the data is coupled to the component, and if needed events synchronize data between components, a bit overhead, a bit duplication of data, but made development for us much faster, also we have a lot of different entities, so for us taking this route worked out very well. " -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
we see, that it's actually kind of a tree structure, for the sake of this presentation i simplified it slightly, actually our profile consists of over 200 components -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
Server Request Response i said earlier, that all these components have their own URL. So they can be requested and rendered by the client seperately. For better user experience on the first load or SEO reasons these components can also be bundled together and rendered in a single page load on the backend. -
Widget Requirement this tree structure is achieved through so called
widget requirements, why they are called requirements: more to this later -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
that means the profile widget requires: publications, aboutMe, leftColumn and institution -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
and Publications requires several publicationItem widgets and leftColumn the Image and the Menu widget -
Remember: self contained remember all these 200 components are supposed
to be self contained -
Do not fetch data directly so something you shouldn't do
is fetching this data directly on you server when composing a page or a subpage -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
Account Account Account Account Account Publication1 Publication2 Publication3 if we take our simplified example, a lot of components need the account of the user that's displayed, while that could be solved with in memory caching, a list of publications each need the publication entity, if we display 20 publications, that would mean 20 database queries, doing it in one query would be much faster though, and you have lot's of stuff like this -
Require stuff so instead of fetching stuff directly, it's way
better to require stuff (that's also why building up the widget tree was done with widget "requirements" -
http://www.infoq.com/presentations/Evolution-of-Code- Design-at-Facebook/ this concept is actually not that new, one
small company who is doing this very much is for example facebook, who also did a very good talk about this a few years back -
so how does it work? around all your components that
just state data (or widget) requirements you need an instance called a preparer -
Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces
Connector Implementations -
Widget Widget Widget Widget Preparer Fetch Requirements Resolver Resolver Services
Connector Interfaces Connector Implementations the preparer iterates over all components and fetches their requirments -
Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces
Connector Implementations Batch requirements and pass them to resolvers it batches them together as intelligently as possible, passes the requirements to resolvers -
Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces
Connector Implementations Call Services as effective as possible (Multi-GET,...) which call the services/storages/etc as effective as possible, or just execute some service class methods -
Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces
Connector Implementations Attach fetched data to Requirements and pass them back to the preparer the fetched data is attached to the requirements, passed back to the preparer -
Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces
Connector Implementations Distribute fetched data to the widgets that required it who in turn distributes the fetched data to the components -
Request Cache nice thing is you can cache this stuff
intelligently in memory of your current request -
Required / Optional requirements can be required or optional, meaning
either it's ok if a call fails or not (important for error handling). if it's not ok we just deactivate the whole component -
RequestData Requirement or getting data out of the request scope
can be a requirement -
Data dependencies within a widget but oftentimes we have ..
so it gets a bit more complex -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
CALLBACK CALLBACK CALLBACK so in the first iteration the profile widget returns its requirements as well as a callback function that should be executed once all the requirements have been fullfilled -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
CALLBACK CALLBACK CALLBACK after the first iteration this callback is then executed while collecting all the requirements of the new subwidgets. the callback can then return further requirments as well as also further callbacks -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
CALLBACK CALLBACK CALLBACK and so on, this iterative process is done as long as there are components or callbacks available that return new requirements -
in code... you can imagine if you are not careful
this can become some kind of a callback hell -
PHP 5.5 Generators https://wiki.php.net/rfc/generators that's why i'm very excited about
generators in php 5.5. -
public function collect() { yield array( new EntityRequirement( 'account', Account::class,
array('accountId' => $this->requestContext->getAccountId()) ), ); yield array( new ServiceRequirement( 'scienceDisciplines', AccountService::class, 'getScienceDisciplines', array('account' => $this->account) ) ); } because then you can write it like this: -
Data dependencies between Widgets also oftentimes you may have... if
only for performance reasons, -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
e.g. for a list of publications that you got from a solr search, you may not want to go to the database again to fetch each publication in each list item, but since a list item is supposed to be renderable separately as well, it needs this logic in there -
Prefills what you can do then is prefilling this data
from the parent widget -
that means if the subwidget has a requirment like this,
the preparer would directly put the prefilled data in there instead of passing it on to the resolvers -
Whoa, this looks awfully complicated to debug you're right, it
takes a bit of time to get used to program this way, but everyone who joined our company in the last few months since we are doing it says after a week or so, that they actually can't imagine working any other way again (i'll talk more about the benefits later) -
but still it's complex, so a good thing is taking
a bit of effort to make it as transparent as possible to see what happens in your application (which is a very good thing anyways). we have a debug toolbar that shows all the component on the page -
and you can also drill down into all the iterations
the preparer did -
Rendering so we have now all the data fetched together
on our server lets render it now -
HTML i said earlier we want our widget tree to
be renderable on the server side as html -
JSON and just put out as json and then be
rendered on the client side by our javascript -
Mustache } http://mustache.github.com/ so we decided to use mustache templates,
we are actually using twitters mustache implementation called hogan -
Helper Methods there is also a php implementation of mustache,
but it's not the fastest and one thing is, that mustache oftentimes relies on small helper functions -
•nl2br •truncate •pluralize •wordwrap •highlight •... like ... if you
now use a php implementation on the server and a js implementation on the client, we have to develop these functions twice ... not a good idea -
http://pecl.php.net/package/v8js V8js that why we actually execute javascript through a
php extension that makes the v8 js library available on the server. so we are executing the same code on the server and on the client to render the same templates. suprise: it's acutally really really fast -
JavaScript so we handled templates and rendering, the backend controllers
that provide the data, on the javascript side in the client each component can have a view object to bind on DOM events -
Easier Refactoring even if the implementation of one component is
very ugly and crude, at least it's encapsulated (hopefully tested) and you can either leave it or revisit this component later on -
Error Handling also error handling can be really simplified and
end-user friendly -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
so going back to our widget tree -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
EXCEPTION if an exception occurs in one of our components while fulfilling a requirement that is not optional -
Profile Publications Publication Publication Publication LeftColumn Image Instiution Menu we
just deactivate this component and the rest of the page is still functional -
Testing also this approach simplifies testing, and with that i
mean A/B testing -
we are doing this excessively, and have over 90 or
so experiments with sometimes over 20 variants running. the way we can do this quickly and easily is by just switching components for the different variants -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
<esi:include src="..." /> because every component has it's own url you can just render out a esi placeholder instead of the widget to tell varnish to fetch it separately and provided it has caching headers, get it out of the cache -
Load components asynchronously the same way you can also load
components of the widget tree asynchronously -
Profile Publications Publication Publication Publication AboutMe LeftColumn Image Instiution Menu
<div id="placeholder"></div> <script>loadWidget('/aboutMe', function(w) { w.render({ replace : '#placeholder' }); })</script> so instead of rendering the widget you render a placeholder dom element and a script tag that loads the widget with an ajax request and then renders it on the client side -
if you look at your widget tree you can mostly
identify larger parts, which are widgets itself -
Profile Menu Header LeftColumn RightColumn like this, so what you
can do to dramatically increase the perceived load time is prioritizing the rendering -
so our http request looks like this, first you compute
and render the important parts of the page, like the top menu and the profile header as well as the rest of the layout, for the left column and right column which are expensive to compute you just render placeholders and then flush the content to the client so that the browser already renders this -
still in the same http request you render out the
javascript needed to make the already rendered components work, so people can use the menu for example -
still in the same http request you then compute the
data for the left column and render out some javascript that takes this data and renders it into the components template client side and then replaces the placeholder with the rendered template -
still in the same request you then can do this
with the right column -> flush content as early as possible, don't wait for the whole site to be computed -
pushState and if you are at that, when you switch
pages, you can also just load the differences between and use pushState to change the url (if supported) to make your app faster