Application Design in Ember.js
Clean == Maintainable == Beautiful != Stressful
Strive to create beautiful clean design, and you'll have a maintainable application which is a joy to work with.
Let's look at some of the ways you can do that.
Creating Clean Architecture in Ember.js 2.0
Small is beautiful.
Lots of small components, services and templates which each have small, clean interfaces and work together are much easier to write, test and maintain than large monolithic page-level code. You can then build up a page from your small components.
If your code files go beyond two pages each, you're probably building a spaghetti monster. If an individual function is 20+ lines, you're probably eating code soup with a fork.
In this document, I've deliberately not given code examples. I'm looking to help you architect your code better, not write your code better.
Real World Examples
Below I've given a few simple examples of how to solve real world problems with good architecture design.
My back-end has a non standard API
Some of us are lucky enough to have control of the back-end API. Wouldn't that make a nice change?!
If your API isn't what ember-data
would like to work with, or you need to work with a number of back-ends (e.g. that user story simply import the user's picture from LinkedIn), then you'll be working with lots of different APIs, very few of which look like the nice standard REST/JSON-API.
Use an SDK Service
Create an SDK service which wraps up the AJAX (or something else) calls to the server into nice clean JS functions with POJO input and output. Return the raw data from the server. You might consider stripping out metadata that the server API might return such as the server software version that are not related to the data you were querying for.
Then you can write code like
this.myServerSdk.updatePerson({
name: "steve",
age:57
});
Services are singletons, so the SDK service can also handle backing off during server downtime, limiting concurrent tasks, injecting auth tokens etc.
Use ember-data Adapters and Serializers
ember-data
's store deals with storage of models. When it needs data from the server it will make a call into an Adapter
and translate the data it receives back with a Serializer
. When saving data to the server your adapter needs to call snapshot.serialize()
to get the data to send to the server. This will delegate the serialization to your Serializer
.
When you write Adapters and Serializers, the key thing is to not muddle up responsibilities.
- Adapters wrap up method calls to the server. Serializers convert data formats to and from the server.
- Adapters are asynchronous (return a promise). Serializers are synchronous (return raw data).
If you're using an SDK service, the Adapter should merely map a call from the ember store into one or many calls into the SDK service. Don't try and convert the data in here - let the Serializer do that.
On save
the Serializer should convert data from your ember-data
model's snapshot
to whatever your SDK service expects to see. On find, query etc, the Serializer should convert the other way, from whatever your SDK service returns into a JSON API payload.
<span style="color:maroon;font-weight:bold;">I'm finding myself copy-pasting code between objects</span>
This one's easy - that's what mixins are for. Create a mixin, and test it, then use it in your other objects.
<span style="color:maroon;font-weight:bold;">I have background tasks to be monitored</span>
You might have various things going on in the background:
- downloads
- uploads
- server-side document creation
Create a service which manages all background tasks and has a startTask()
method. It should have a computed property giving a status for each task. You might pass in a function that returns a promise, or the service could already know how to execute the task, just taking a task config POJO if you don't have very many different types of task.
Then create and test a component which displays the status for each task. You might also consider creating an alerting component which pops up an alert message when a task completes.
Now finally you can choose where to put that status component on your app pages. This bit is the easy bit! If you've designed your service to execute any arbitrary task, then you can reuse this code in other projects. Think about creating an ember-cli addon to house it.
<span style="color:maroon;font-weight:bold;">All my projects have common code</span>
Create a set of small add-ons each with a small set of your reusable objects. For example you might create one for the above background task monitor. It would contain the service and the status components. Then you can test them in isolation and install them into each application that needs them.
If you have a common set of add-ons that you always use, create them individually and then create a library addon which just has dependencies on each of the individual add-ons. Then if you need a different subset for another application you could import them individually or create a different library. You should always minimise your imported add-ons so that you minimise the size of your vendor.js
.
In Conclusion
Clean architectures are based on lots of small objects, each doing exactly one thing and doing it well. Think about composing your application from small pieces - if a job can be split in two then you should almost certainly do that. If those two pieces can be split then do that too.
You'll find yourself quickly creating a list of little jobs to do, which are easy to estimate, and easy to test. You should be able to code each one in less than a day if you've gone far enough, so you'll have a constant feeling of progress and completion to keep you sleeping well at night!