+ - 0:00:00
Notes for current slide
Notes for next slide

Microapplications & API First Development

Deliver Awesome Stuff Fast

Jon Johnson

Jon's avatar

Slides at: https://jrjohnson.dev/talks/2017-01-apifirst-microapps.html

jrjohnson
jrjohnson_
jrjohnson https://symfony-devs.slack.com/

1 / 78

Who am I?

  • Full stack web developer (PHP, Javascript)
  • Technical lead for the Ilios Project open source curriculum management system for health science education. (http://iliosproject.org)

github contributions

2 / 78

Who am I?

  • Full stack web developer (PHP, Javascript)
  • Technical lead for the Ilios Project open source curriculum management system for health science education. (http://iliosproject.org)

github contributions with time off in green

3 / 78

A brief history of Ilios

  • Curriculum management system for health science education. (http://iliosproject.org)
  • Deployed at dozens of medical schools on 5 continents
  • A product of UCSF School of Medicine and the MedBiquitous Consortium

map of international Ilios schools

4 / 78
  • Other Campuses that didn't make the map
    • Ireland
    • Nigeria
    • Qatar
    • the Netherlands
    • Australia

A brief history of Ilios

The Boom!

  • Ilios was born a few years after the web
  • A bunch of faculty got together and built something that "Just worked"
  • MS Access Database
  • Served through Citrix

Buffy playing with a stake

5 / 78
  • AKA The Nineties!
  • The Babycenter opened for business

A brief history of Ilios (v1)

The Bust!

  • Woot! The Web!
  • ASP application using SQL Server 2000
  • Was (according to faculty) "much more functional" than what we have now

Bubble Bursting in slow motion

6 / 78

Credit: Daniel M. Harris et al, American Physical Society

A brief history of Ilios (v2)

The Rewrite!

  • In 2009 Ilios was re-written in PHP
  • CodeIgniter
  • YUI
  • Lots of Stored Procedures
  • No Tests
  • Basically a perfect example of good design from 2009
7 / 78
  • Spent 18 months designing a new system and wrote no code
  • Then threw out all of the design and built the whole thing in about 8 months
  • A single Developer / Mathematician

deepCloneAssociativeArray

/**
* Written because slice() is dysfunctional with associative arrays.
*
* @return a deep copy of the associative array (the returned array is its own unique
* Array instance, and the objects in the array have clone() called on them -
* so they must implement that). If originalArray is null, null is returned.
*/
ilios.utilities.deepCloneAssociativeArray = function (originalArray) {
var rhett = null;
if (originalArray != null) {
rhett = new Array();
for (var key in originalArray) {
rhett[key] = originalArray[key].clone();
rhett.length++;
}
}
return rhett;
};
8 / 78
  • Javascript doesn't have associative arrays -> it has objects
  • We can laugh (and sometimes we do) but this is a great lesson in the inevitable result of a truly talented developer hunkering down to get it done in a vacuum.

A brief history of Ilios (v3)

The (second) Rewrite!

  • In 2015 we embarked on this journey
  • Open API
  • Ease community contributions:
    • Good framework docs
    • Standardization of tools
    • Fork and Pull Development Model
    • Automated Tests and Code Style Checking
  • SO MANY TESTS!!!
9 / 78
  • Open API
    • We interconnect with a lot of other systems on campuses and with organizations like the American Association of Medical Colleges so having an open and well documented API allows data to flow
  • Community
    • We provide Ilios for Free to other medical schools around the world
    • Sometimes they have some developer time to give us back a few weeks or a few months
    • It is critical that work done outside our core team be of high quality and that we have clear expectations.

Our path to success

  • Split Ilios
  • Release independently
  • Build the API within the existing PHP codebase
  • Release constantly
  • Write as little code as possible
10 / 78
  • Split Ilios
    • API in PHP
    • Single Page App User interface
  • Test
    • Never a bug fix without a test
    • Test Reminder Development
  • Inside Codebase
    • Talk about /sfi directory and handing off routing to Symfony
  • Release constantly
    • Constantly not continuously so we can be clear about new features
    • 3.0.0 Dec 14, 2015
    • Since then we've tagged 68 releases
    • We've tagged 28 backend releases
    • We've tagged 41 frontend releases
  • Write No Code
    • Had to be done Fast, Good, and Cheap
    • Very small team with only a few developers (1.5 - 4)

Our path to success

  • Split Ilios
  • Release independently
  • Build the API within the existing PHP codebase
  • Release constantly
  • Write as little code as possible

symfony logo emberjs logo

10 / 78
  • Split Ilios
    • API in PHP
    • Single Page App User interface
  • Test
    • Never a bug fix without a test
    • Test Reminder Development
  • Inside Codebase
    • Talk about /sfi directory and handing off routing to Symfony
  • Release constantly
    • Constantly not continuously so we can be clear about new features
    • 3.0.0 Dec 14, 2015
    • Since then we've tagged 68 releases
    • We've tagged 28 backend releases
    • We've tagged 41 frontend releases
  • Write No Code
    • Had to be done Fast, Good, and Cheap
    • Very small team with only a few developers (1.5 - 4)

Why Symfony?

  • Run old and new at the same time if necessary
  • Great docs to point contributors to
  • Stable core and excellent bundles
11 / 78

Why Symfony?

  • Run old and new at the same time if necessary
  • Great docs to point contributors to
  • Stable core and excellent bundles Our Complex Data ilios data sample
11 / 78
  • Our Data model is a proven winner
  • In house PHP experience
  • Semantic Versioning
  • We will live with this decision for at least 5 years
  • api-platform

Why Ember?

  • Integrated Build System (ember-cli)
  • URL based state
  • Great docs to point contributors to
  • Stable core and excellent addons
12 / 78
  • Stability without stagnation
  • Semantic Versioning
  • We will live with this decision for at least 5 years

What is a single page app?

A single-page application (SPA) is a web application or web site that fits on a single web page with the goal of providing a more fluid user experience akin to a desktop application. In a SPA, either all necessary code – HTML, JavaScript, and CSS – is retrieved with a single page load,[1] or the appropriate resources are dynamically loaded and added to the page as necessary, usually in response to user actions. The page does not reload at any point in the process, nor does control transfer to another page, although modern web technologies (such as those included in the HTML5 pushState() API) can provide the perception and navigability of separate logical pages in the application. Interaction with the single page application often involves dynamic communication with the web server behind the scenes.

https://en.wikipedia.org/wiki/Single-page_application

13 / 78

No. Seriously.

... all necessary code – HTML, JavaScript, and CSS – is retrieved with a single page load ... The page does not reload at any point ... dynamic communication with the web server behind the scenes.

14 / 78

No. Seriously.

... all necessary code – HTML, JavaScript, and CSS – is retrieved with a single page load ... The page does not reload at any point ... dynamic communication with the web server behind the scenes.

All Javascript, all the time

14 / 78

All PHP has to do is REST

REST or RESTfull APIs deliver stateless data based on URL and allow for authorized CRUD operations on that data.

15 / 78

All PHP has to do is REST

REST or RESTfull APIs deliver stateless data based on URL and allow for authorized CRUD operations on that data.

--Jon Johnson, SFPHP Meetup, Just Now

15 / 78

Anatomy of a REST request

simple REST GET request

16 / 78

Breaking it down further

Full map of REST GET request

17 / 78

What we get from Symfony and Friends

free REST from bundles

18 / 78

We don't have a lot to do

19 / 78

Support Many Configurable Authentication Solutions

Shibboleth LDAP Form Based Active Directory

20 / 78
  • Extremely configurable security system is a big draw of Symfony
  • Lots of Bundles which focus on authentication
  • Too many options and too much configuration
  • Hard to write documentation authentication is ever changing
  • Schema creep
  • Complicated frontend requests with so much uncertainty

Authenticate with JSON Web Token (JWT)

21 / 78

What is JWT?

Signed Token Base64 Encoded

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJpbGlvcyIsImF1ZCI6ImlsaW9zIiwiaWF0IjoiMTQ0NjA2MDU4NCIsImV4cCI6IjE0NDYwODkzODQiLCJ1c2VyX2lkIjoyMDAwfQ.
khdJqxEbXZwudOjjUr8_fs4BJmespfb8qAx2v_zIleU

Payload

{
"iss": "ilios",
"aud": "ilios",
"iat": "1446060584",
"exp": "1446089384",
"user_id": 2000
}
22 / 78
  • Customizable signing / encryption

All your payloads are belong to you

{
"iss": "ilios",
"aud": "ilios",
"iat": "1446060584",
"exp": "1446089384",
* "user_id": 2000,
* "roles": ['user', 'admin'],
* "favoriteTypeOfBacon": 'smoked applewood'
}
23 / 78
  • No database lookup without a signed token
  • No session storage
  • Token lifetime is under your control and easily observable on the client
  • We store a invalidateTokensIssuedBefore value for each user
  • Token can be generated anywhere the key is known

authentication complete

24 / 78

With the user authenticated we can now fetch some data

authorization complete

25 / 78

With the user authorized we can now display data

Light Controllers

public function getAction($id)
{
$manager = $this->get('course.manager');
$course = $manager->findBy(['id' => $id]);
$authChecker = $this->get('security.authorization_checker');
if (! $authChecker->isGranted('view', $course)) {
throw $this->createAccessDeniedException('Unauthorized access!');
}
$answer['courses'][] = $course;
return $answer;
}
26 / 78
  • $course can be a doctrine entity or a Plain Object
    • There is no explicit transformer in the controller

Transformation to JSON

GET => /api/v1/courses/619

"courses":[
{
"id":619,
"title": "PRIME 2014-15",
"startDate": "2014-08-23T00:00:00+00:00",
"deleted": false,
"school": "1",
"directors": ["3625", "3906"],
"topics": [],
"objectives": [],
"sessions": ["17713", "17714", "17715"]
}
]
27 / 78

TADA!

rabit out of a hat

28 / 78

Almost Magic

Our Course Entity

class Course
{
/**
* @Type("string")
*/
public $title;
composer require jms/serializer
29 / 78
  • A shrinking of annotation serializes the data from Object to JSON
    • No need to implement toJson() or any other method
    • You could also convert to XML or even HTML

Tests

public function testGetCourse()
{
$course = $this->container->get('dataloader.course');
$this->createJsonRequest('GET', '/api/v1/courses/' . $course['id']);
$response = $this->client->getResponse();
$this->assertEquals(
$course,
json_decode($response->getContent(), true)['courses'][0]
);
}
30 / 78
  • Happy Path tests are pretty straight forward

Test some kinds of failure

public function testPostBadCourse()
{
$invalidCourse = $this->container
->get('dataloader.course')
->createInvalid()
;
$this->createJsonRequest(
'POST',
$this->getUrl('post_courses'),
json_encode(['course' => $invalidCourse]),
$this->getAuthenticatedUserToken()
);
$response = $this->client->getResponse();
$this->assertEquals(Codes::HTTP_BAD_REQUEST, $response->getStatusCode());
}
31 / 78

Test Different options

public function testFilterByLevel()
{
$courses = $this->container->get('dataloader.course')->getAll();
$this->createJsonRequest(
'GET',
$this->getUrl('cget_courses', ['filters[level]' => 3]),
null,
$this->getAuthenticatedUserToken()
);
$response = $this->client->getResponse();
$this->assertJsonResponse($response, Codes::HTTP_OK);
$data = json_decode($response->getContent(), true)['courses'];
$this->assertEquals(1, count($data), var_export($data, true));
$this->assertEquals(
$this->mockSerialize(
$courses[3]
),
$data[0]
);
}
32 / 78

Is Everything the Same?

puppies all the same

33 / 78

Is Everything the Same?

puppies all the same

Why not generate it?

33 / 78
  • If you're still not sure what your perfect world will look like, thats ok
  • Generate more code than you need and then remove stuff
  • You will probably end up writing custom twig templates
  • Generate your tests too!

Or Maybe Inject it?

35 / 78
  • We started out generating everything so we could fiddle with it
  • In the end our controllers (aside from the names of things) are identical
  • We are starting the process of using a controller service

We have an API now what?

finding nemo now what

36 / 78
  • API is tested, stable and workign really well

Microapps! That's What!

emeril saying bam

37 / 78
  • Probably you will build a Large application first - but then - MicroAPPS!
  • Other half of this talk
  • This is really about Single Page javascript apps, but being a PHP meetup I'm going to try and confine myself to what the backend needs to know about.

Developer Life Cycle

developer life cycle

38 / 78

Developer Life Cycle

developer life cycle

39 / 78

Developer Life Cycle

developer life cycle

40 / 78

Developer Life Cycle

developer life cycle

41 / 78

Developer Life Cycle

developer life cycle

42 / 78

Developer Life Cycle

developer life cycle

43 / 78

Developer Life Cycle

technical debt circle

44 / 78

Developer Life Cycle

build circle

45 / 78

Developer Life Cycle

developer life cycle

46 / 78

Profound and Unstoppable Power of Yes

UCSF profound and unstoppable power of yes

47 / 78
  • As developers we want to say yes
  • As people we want to help other people
  • The developer life cycle has taught us to be cautious.

Microservices

  • Design software applications as suites of independently deployable services
  • Decentralized control of languages and data
  • Expose a consistent versioned API
48 / 78

Microservices

Microapps

  • Take advantage of existing data through APIs
  • Single Responsibility Principle
  • Throw away things that don't work
  • Low cost of enhancement
  • Freedom to choose different technologies
49 / 78
  • We're building using EmberJS
  • We LOVE EmberJS
  • That is just about all I'm going to say about EmberJS

Infrastructure code

  • Build Pipeline
  • Request Handling
  • Testing
  • Configuration
  • Internationalization
  • Continuous Integration
50 / 78
  • Taking us on a little detour to talk about Infrastructure Code
  • Can't understand microapps without it.

Infrastructure code

Build Pipeline

compile transpile preprocess transform bundle minifi fingerprint sourcemaps tree shaking autoreload

51 / 78
  • compile
  • transpile
  • preprocess
  • bundle
  • minify
  • fingerprint

Infrastructure code

Request Handling

flow chart of simple user flow request

52 / 78

Infrastructure code

Request Handling (for realz)

simple request flowchart with confusing arrows

53 / 78

Infrastructure code

Request Handling (for realzy realz realz)

simple request flowchart with more confusing arrows

54 / 78

Infrastructure code

So Much More

package lint build test deploy translation ci accessibility

55 / 78

Big Application

large application graph

56 / 78

Medium Application

medium application graph

57 / 78

Small Application

small application graph

58 / 78

Microapp

micro application graph

59 / 78

Microapps

small microapp graph small microapp graph small microapp graph small microapp graph small microapp graph small microapp graph small microapp graph small microapp graph

60 / 78

Serving Assets

  • Javascript
  • CSS
  • Images
  • Fonts
  • Files (crossdomain.xml, robots.txt, etc...)
61 / 78

Serving Assets

CDN

  • Pros:
    • Build and then upload
    • Content close to the user
    • Configuration can be done elsewhere
  • Cons:
    • Keep around old versions in case they are used
    • Ad and content blockers might block requests
    • Costs $
    • Configuration must be done elsewhere
62 / 78

Serving Assets

Build Service (Netlify, Forge, AWS Lambda)

  • Pros:
    • Build in place
    • Host the entire application
    • Custom domain
    • Other services (SSL, logging)
    • Probably connected to a CDN anyway
  • Cons:
    • Configuration cannot be distributed
    • Costs $$
63 / 78

Serving Assets

BYOS

  • Pros:
    • Full Control
  • Cons:
    • Complete Responsibility
    • Costs $$$$
64 / 78
  • BYOS = Bring Your Own Server/Stack
  • Lots of files to backup and version over time

SPA root (index.html)

  • The users entrance point into the app
  • Must be configured to point to remote fingerprinted assets
  • The only thing that actually shows the user your domain
  • From here everything is javascript
65 / 78

SPA root

index.html sample

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Ilios</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="ilios/config/environment" content="%7B%22modulePrefix%22%3A%22ilios%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22auto%22%2C%22redirectAfterShibLogin%22%3Atrue%2C%22contentSecurityPolicy%22%3A%7B%22default-src%22%3A%5B%22%27none%27%22%5D%2C%22script-src%22%3A%5B%22%27self%27%22%2C%22%27unsafe-eval%27%22%2C%22www.google-analytics.com%22%5D%2C%22font-src%22%3A%5B%22%27self%27%22%5D%2C%22connect-src%22%3A%5B%22%27self%27%22%2C%22www.google-analytics.com%22%2C%22http%3A//localhost%3A8000%22%5D%2C%22img-src%22%3A%5B%22%27self%27%22%2C%22data%3A%22%2C%22www.google-analytics.com%22%5D%2C%22style-src%22%3A%5B%22%27self%27%22%2C%22%27unsafe-inline%27%22%5D%2C%22media-src%22%3A%5B%22%27self%27%22%5D%7D%2C%22flashMessageDefaults%22%3A%7B%22timeout%22%3A2000%2C%22extendedTimeout%22%3A3000%2C%22types%22%3A%5B%22success%22%2C%22warning%22%2C%22info%22%2C%22alert%22%5D%7D%2C%22ember-simple-auth%22%3A%7B%22authorizer%22%3A%22authorizer%3Atoken%22%7D%2C%22ember-simple-auth-token%22%3A%7B%22serverTokenEndpoint%22%3A%22/auth/login%22%2C%22serverTokenRefreshEndpoint%22%3A%22/auth/token%22%2C%22tokenPropertyName%22%3A%22jwt%22%2C%22authorizationHeaderName%22%3A%22X-JWT-Authorization%22%2C%22authorizationPrefix%22%3A%22Token%20%22%2C%22refreshLeeway%22%3A300%7D%2C%22i18n%22%3A%7B%22defaultLocale%22%3A%22en%22%7D%2C%22froalaEditorDefaults%22%3A%7B%22toolbarInline%22%3Afalse%2C%22placeholderText%22%3A%22%22%2C%22allowHTML%22%3Atrue%2C%22saveInterval%22%3Afalse%2C%22pastePlain%22%3Atrue%2C%22spellcheck%22%3Atrue%2C%22toolbarButtons%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%2C%22toolbarButtonsMD%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%2C%22toolbarButtonsSM%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%2C%22toolbarButtonsXS%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%7D%2C%22serverVariables%22%3A%7B%22tagPrefix%22%3A%22iliosconfig%22%2C%22vars%22%3A%5B%22api-host%22%2C%22api-name-space%22%5D%2C%22defaults%22%3A%7B%22api-name-space%22%3A%22api/v1%22%2C%22api-host%22%3A%22http%3A//localhost%3A8000%22%7D%7D%2C%22ember-metrics%22%3A%7B%22includeAdapters%22%3A%5B%22google-analytics%22%5D%7D%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22String%22%3Atrue%2C%22Array%22%3Atrue%2C%22Function%22%3Afalse%2C%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22apiVersion%22%3A%22v1.12%22%2C%22name%22%3A%22ilios%22%2C%22version%22%3A%221.30.2+c4cf1099%22%7D%2C%22IliosFeatures%22%3A%7B%22allowAddNewUser%22%3Atrue%7D%2C%22contentSecurityPolicyHeader%22%3A%22Content-Security-Policy-Report-Only%22%2C%22something%22%3A%22test%22%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22noScript%22%3A%7B%22tag%22%3A%22noscript%22%2C%22content%22%3A%22%3Cp%3EFor%20full%20functionality%20of%20this%20site%20it%20is%20necessary%20to%20enable%20JavaScript.Here%20are%20the%20%3Ca%20href%3D%27http%3A//www.enable-javascript.com/%27%20target%3D%27_blank%27%3Einstructions%20how%20to%20enable%20JavaScript%20in%20your%20web%20browser%3C/a%3E.%3C/p%3E%22%7D%7D" />
<link rel="stylesheet" href="https://d26vzvixg52o0d.cloudfront.net/assets/vendor-f5d338174298b781680826893d072095.css">
<link rel="stylesheet" href="https://d26vzvixg52o0d.cloudfront.net/assets/ilios-555dc260d918b4f9b035ec1db2c35944.css">
<!-- Ilios variables set by the web server -->
<meta name='iliosconfig-api-name-space'content='api/v1'>
<meta name='iliosconfig-api-host'content='http://localhost:8000'>
</head>
<body>
<noscript><p>For full functionality of this site it is necessary to enable JavaScript.Here are the <a href='http://www.enable-javascript.com/' target='_blank'>instructions how to enable JavaScript in your web browser</a>.</p></noscript>
<div id='initialpageloader' class='ember-load-indicator'><header class='main'><div class='logo'><span class='image'></span></div></header><div id='site-container'><h1><i class='fa fa-spinner fa-pulse fa-3x'></i></h1><p id='browsererrormessage' class='hidden'>It is possible that your browser is not supported by Ilios. Please refresh this page or try a different browser.</p></div></div></div>
<script src="https://d26vzvixg52o0d.cloudfront.net/assets/vendor-176c84d4f1739a5cbd0342f2dae4984e.js"></script>
<script src="https://d26vzvixg52o0d.cloudfront.net/assets/ilios-008c1daa7f9adbaf79122bce1c44a4b2.js"></script>
<script src="https://d26vzvixg52o0d.cloudfront.net/ilios-prerender/scripts-0184e9b3a1818f3c046d9d735319dd44.js"></script>
</body>
</html>
66 / 78

SPA root

index.html config

<meta
name="ilios/config/environment"
content="%7B%22modulePrefix%22%3A%22ilios%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22auto%22%2C%22redirectAfterShibLogin%22%3Atrue%2C%22contentSecurityPolicy%22%3A%7B%22default-src%22%3A%5B%22%27none%27%22%5D%2C%22script-src%22%3A%5B%22%27self%27%22%2C%22%27unsafe-eval%27%22%2C%22www.google-analytics.com%22%5D%2C%22font-src%22%3A%5B%22%27self%27%22%5D%2C%22connect-src%22%3A%5B%22%27self%27%22%2C%22" />
modulePrefix: 'ilios',
environment: environment,
rootURL: '/',
locationType: 'auto',
redirectAfterShibLogin: true,
contentSecurityPolicy: {
'default-src': ["'none'"],
'script-src': ["'self'", "'unsafe-eval'", 'www.google-analytics.com'],
'font-src': ["'self'"],
'connect-src': ["'self'", 'www.google-analytics.com'],
'img-src': ["'self'", 'data:', 'www.google-analytics.com'],
'style-src': ["'self'", "'unsafe-inline'"],
'media-src': ["'self'"]
},
...
67 / 78

SPA root

index.html config

<meta
name='iliosconfig-api-name-space'
content='api/v1'>
<meta
name='iliosconfig-api-host'
content='http://localhost:8000'>
68 / 78

SPA root

index.html styles

<link
rel="stylesheet"
href="https://d26vzvixg52o0d.cloudfront.net/assets/vendor-f5d338174298b781680826893d072095.css">
<link
rel="stylesheet"
href="https://d26vzvixg52o0d.cloudfront.net/assets/ilios-555dc260d918b4f9b035ec1db2c35944.css">
69 / 78

SPA root

javascript

<script
src="https://d26vzvixg52o0d.cloudfront.net/assets/vendor-176c84d4f1739a5cbd0342f2dae4984e.js">
</script>
<script
src="https://d26vzvixg52o0d.cloudfront.net/assets/ilios-008c1daa7f9adbaf79122bce1c44a4b2.js">
</script>
70 / 78

SPA root

noscript

<noscript>
<p>
For full functionality of this site it is necessary to
enable JavaScript. Here are the <a href='http://www.enable-javascript.com/' target='_blank'>instructions on
how to enable JavaScript in your web browser</a>.
</p>
</noscript>
71 / 78

SPA root

loading indicator

<div class='load-indicator'>
<h1><i class='fa fa-spinner fa-pulse fa-3x'></i></h1>
<p class='browsererrormessage hidden'>
It is possible that your browser is not supported by Ilios. Please refresh this page or try a different browser.
</p>
</div>
<script src="https://d26vzvixg52o0d.cloudfront.net/ilios-prerender/scripts-0184e9b3a1818f3c046d9d735319dd44.js"></script>
  • Shows a spinner while the page loads
  • JS causes the error block to be reveled after a few seconds
72 / 78

Configuration through the API server

  • Ilios is distributed and run by schools
  • The have to be able to configure lots of things on their own
  • We distribute our SPA root as a JSON file and then turn it back into HTML on the API server
73 / 78

Configuration through the API server

index.json

"meta": [
{
"name": "ilios/config/environment",
"content": "%7B%22modulePrefix%22%3A%22ilios%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22/%22%2C%22locationType%22%3A%22auto%22%2C%22redirectAfterShibLogin%22%3Atrue%2C%22contentSecurityPolicy%22%3A%7B%22default-src%22%3A%5B%22%27none%27%22%5D%2C%22script-src%22%3A%5B%22%27self%27%22%2C%22%27unsafe-eval%27%22%2C%22www.google-analytics.com%22%5D%2C%22font-src%22%3A%5B%22%27self%27%22%5D%2C%22connect-src%22%3A%5B%22%27self%27%22%2C%22www.google-analytics.com%22%2Cnull%5D%2C%22img-src%22%3A%5B%22%27self%27%22%2C%22data%3A%22%2C%22www.google-analytics.com%22%5D%2C%22style-src%22%3A%5B%22%27self%27%22%2C%22%27unsafe-inline%27%22%5D%2C%22media-src%22%3A%5B%22%27self%27%22%5D%7D%2C%22flashMessageDefaults%22%3A%7B%22timeout%22%3A2000%2C%22extendedTimeout%22%3A3000%2C%22types%22%3A%5B%22success%22%2C%22warning%22%2C%22info%22%2C%22alert%22%5D%7D%2C%22ember-simple-auth%22%3A%7B%22authorizer%22%3A%22authorizer%3Atoken%22%7D%2C%22ember-simple-auth-token%22%3A%7B%22serverTokenEndpoint%22%3A%22/auth/login%22%2C%22serverTokenRefreshEndpoint%22%3A%22/auth/token%22%2C%22tokenPropertyName%22%3A%22jwt%22%2C%22authorizationHeaderName%22%3A%22X-JWT-Authorization%22%2C%22authorizationPrefix%22%3A%22Token%20%22%2C%22timeFactor%22%3A1000%7D%2C%22i18n%22%3A%7B%22defaultLocale%22%3A%22en%22%7D%2C%22froalaEditorDefaults%22%3A%7B%22toolbarInline%22%3Afalse%2C%22placeholderText%22%3A%22%22%2C%22allowHTML%22%3Atrue%2C%22saveInterval%22%3Afalse%2C%22pastePlain%22%3Atrue%2C%22spellcheck%22%3Atrue%2C%22toolbarButtons%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%2C%22toolbarButtonsMD%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%2C%22toolbarButtonsSM%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%2C%22toolbarButtonsXS%22%3A%5B%22bold%22%2C%22italic%22%2C%22subscript%22%2C%22superscript%22%2C%22formatOL%22%2C%22formatUL%22%2C%22insertLink%22%2C%22html%22%5D%7D%2C%22serverVariables%22%3A%7B%22tagPrefix%22%3A%22iliosconfig%22%2C%22vars%22%3A%5B%22api-host%22%2C%22api-name-space%22%5D%2C%22defaults%22%3A%7B%22api-name-space%22%3A%22api/v1%22%2C%22api-host%22%3Anull%7D%7D%2C%22ember-metrics%22%3A%7B%22includeAdapters%22%3A%5B%22google-analytics%22%5D%7D%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22String%22%3Atrue%2C%22Array%22%3Atrue%2C%22Function%22%3Afalse%2C%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22apiVersion%22%3A%22v1.12%22%2C%22name%22%3A%22ilios%22%2C%22version%22%3A%221.30.2+cc2625f3%22%7D%2C%22IliosFeatures%22%3A%7B%22allowAddNewUser%22%3Atrue%7D%2C%22contentSecurityPolicyHeader%22%3A%22Content-Security-Policy-Report-Only%22%2C%22something%22%3A%22test%22%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22noScript%22%3A%7B%22tag%22%3A%22noscript%22%2C%22content%22%3A%22%3Cp%3EFor%20full%20functionality%20of%20this%20site%20it%20is%20necessary%20to%20enable%20JavaScript.Here%20are%20the%20%3Ca%20href%3D%27http%3A//www.enable-javascript.com/%27%20target%3D%27_blank%27%3Einstructions%20how%20to%20enable%20JavaScript%20in%20your%20web%20browser%3C/a%3E.%3C/p%3E%22%7D%7D"
}
],
"link": [
{
"rel": "stylesheet",
"href": "https://d26vzvixg52o0d.cloudfront.net/assets/vendor-f5d338174298b781680826893d072095.css"
},
{
"rel": "stylesheet",
"href": "https://d26vzvixg52o0d.cloudfront.net/assets/ilios-555dc260d918b4f9b035ec1db2c35944.css"
}
],
"script": [
{
"src": "https://d26vzvixg52o0d.cloudfront.net/assets/vendor-b2e879b4494e5f1bb5108d090e9a684b.js"
},
{
"src": "https://d26vzvixg52o0d.cloudfront.net/assets/ilios-8ff96141a6193e852624d3b5ee4114d1.js"
},
{
"src": "https://d26vzvixg52o0d.cloudfront.net/ilios-prerender/scripts-0184e9b3a1818f3c046d9d735319dd44.js"
}
]
74 / 78
  • Push this file to the CDN as well
  • Grab it from the API server and use templating to make it back into html
  • Can inject and modify configuration as needed

Keeping the user up to date

With API Changes

  • Must account for changes in the API version of the server and prevent the user from writing bad data
  • Want to notify the user when they should refresh to see new features
75 / 78
  • API Version:
    • In an ideal world server would be able to handle several simultaneous API versions
    • We haven't figured out how to do that yet
    • We have elected to use minor version number to represent additions and major numbers for removals
    • Launched a v1.0 and after a year we are at v1.12
    • That's a version number every other release so keeping track of this matters.
  • App Updates
    • SPA's can be very long lived without refreshing
    • Polite reminder to refresh is really the only way to keep apps up to date.

Keeping the user up to date

With the latest features

  • Every 5 minutes ask the server what API version they are running
  • Require the user to refresh their browser if there is a mismatch
76 / 78
  • Easy to implement
  • The higher the traffic the worse this solution is
  • Doing this on a fixed interval is a bad solution for mobile devices instead it has to be tied to another network operation

So...

butts on a power wire - bottom line

77 / 78
  • You can build an API in place and if you're migrating to PHP 7 anyway...
  • Frameworks are our friends, they are how we raise the shoulders of giants so we can all stand on them
  • The less code you are responsible for the more problems you can solve

Discussion

Jon's avatar

Slides at: https://jrjohnson.dev/talks/2017-01-apifirst-microapps.html

jrjohnson
jrjohnson_
jrjohnson https://symfony-devs.slack.com/

78 / 78

Thanks

  • to the Babycenter and the SF PHP group for the oportunity to speak
  • to the UCSF library for giving me the time to do this and other open source work
  • and the Ilios team who picked up a lot of slack this week as I started to panic about getting this talk finished.

Who am I?

  • Full stack web developer (PHP, Javascript)
  • Technical lead for the Ilios Project open source curriculum management system for health science education. (http://iliosproject.org)

github contributions

2 / 78
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow