Frameworks such as PhoneGap or Sencha created a great promise of rapid development of fast and impressive apps across different platforms by focusing on client code reuse. We want to share a more holistic architecture across the server and client that let you go native on each client platform and still take advantage of code reuse.
The idea is simple: if you have control over both the server and the client design, you should not settle for a solution that only optimizes one. If you “look at the whole board” and optimize the server for the clients, you won’t need to sacrifice the user experience for the sake of client code reuse.
Primary design goals for Personal Capital include:
- Perfect balance between an feature-rich application and an intuitive navigation scheme
- Creating the absolute best user experience; and
- 100% accurate data presentation
With these design goals, we’re not satisfied with the defaults of each client platform, let alone defaults of shared code across multiple client platforms! At Personal Capital we have customized every component and every interaction in the pursuit of user happiness and achieving UX nirvana: a wowed and happy user each time that she uses our service.
Our approach is simple: Go Native but optimize the server’s Web Services APIs for the client by following six simple tips to gain the time and flexibility we need to make our app stand out, on each platform, in its own way. We started experimenting with these rules two years ago when we created our Second Generation APIs, and optimized them as much as possible for the clients. These six principles allow us to streamline design and QA, and free up our client developers to spend the majority of their time on what they do best: making amazing user experiences.
Tip #1 Let Your Client Developers Write the Server API Definitions
API definitions are normally dictated by the server engineers, and REST enthusiasts and typical server-driven development paradigms have made it uncommon for client developers to be more than tangentially involved in server API definition before coding starts. We flipped this model on its head and relied on our client developers to drive the API definition, and as a result the server became much more attuned to how the clients expect the data to be structured, and minimized re-work later by maximizing communication early on.
Tip #2 Let Your Server Developers Take As Much Logic Out of Client Code as Possible
Avoid replicating code across many clients that you support and move as much logic as possible to the server. Having business logic in the client code is analogous to hard coding a configuration value. The pivotal point for us was realizing it’s better to have simple, thin, and pretty clients than complex, thick ones that have duplicated and hard coded business logic. Smart clients are a burden. Now each of the discussions between client and server developers is about “how can the server make the client’s code simpler?”
Tip #3 Don’t Be Afraid of Specialized APIs
Let’s just say this: it’s OK to serve the same data from more than one API. Why? Because when clients get data in the format they need, directly from the server, they can get it on screen faster by skipping complex transformations. And when the data format changes on the server, those changes are transparent to the clients. This has the added benefit of also not needing to care how the data is being used elsewhere in the client, making your client more loosely coupled and modular.
Tip #4 Let the Server Enforce Uniformity Across Clients
When it comes time to support multiple clients, the more you push responsibilities like rounding of amounts, calculation, and string formatting up to the server, the more time you will save your development and QA teams. You won’t need to spend time re-writing the same formatting and calculation rules on each client and fixing the same bugs on each platform. If it’s correct coming from the server, it will be correct everywhere.
Tip #5 No Workflow State Machines on the Clients
Likewise, when dealing with multiple clients, the more you push the complex state machines that deal with business logics into the server, the faster you will be able to iterate. For example instead of having a logic on the client that says “if user is in state x, and this is the first time that she is attempting to do y, show message z” just tell the client to show message z. All that logic can be encapsulated on the server and the API can just tell the client what to do. The time to market gained between each client writing and testing a complex flow versus each client simply responding to server flags is huge. It’s the difference between crazy nested ifs and a simple switch statement. Keep the complex state machines tucked safely in an API. Let your clients focus on display logic, and not managing business state.
Tip #6 Fast, Rich and Flexible APIs
If you follow all these tips, the payoff is huge: shorter time to market, simpler client code, less bugs. But if you want to pull this off, you must:
- Make calling an API fast, really fast. Round-trip time of a request has to be as short as possible. This means server-side caching, is a must.
- Create rich APIs that can deliver a lot of data in one call; this is especially important for mobile applications where the network overhead is much greater.
- Add enough controls in the API definition to allow the client to ask for the right amount of data based on their flow. E.g. iPhone may not show transaction details and would just need the summary, but iPad and web do want to show these data. Give the clients the control to request the right data amount.
- Gzip your responses as much as possible. The performance lift you get on mobile and web apps from this simple change are amazing!
- Client-side caching of the API responses is just as important and reduces reliance on network stability to a great deal.
With loosely coupled client modules that receive pre-formatted data, and client developers that don’t need to implement tons of complex business logic, you can focus your client developers on what they do best. You’ve successfully freed up enough cycles that you can afford the extra spit and polish that will make your app stand out from the rest.
Last month we held a Meetup that we discussed these principles and how through this architecture you can reduce your client code base by not sharing client code, but rather sharing server code. You can watch the video here.