Six Tips on Defining WebService APIs

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 Architecture

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.

Go Native!

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.

3 comments

  1. Nice point. Native does have some differences from Web (HTML + JS + Server). JS is dynamically downloaded from the server, and is typically developed by the same team who also wrote the server side. So the rich JS client takes care of the navigation and user experience, and the “simple” server takes care of the data and scalability.

    On the other hand, this paradigm may not work for Native. As you point out, the native side is “hardcoded”. The “logic” can be “interaction logic”, “business rules” and “data logic”, depends on what type of people, Hardcore REST people may model every thing as “interaction + data access”. To strike the right balance is an art.

  2. I don’t know if anyone is still responding to this thread, since it was posted some time ago. But if so, I’ve been wondering about this balance between client and server computation.

    If you do all or most of the computation on the server (via Java Servlets, for example) I am concerned that computation must be scaled as more users are added.

    With computation on the client, you are doing distributed computing, using client resources, so the issue of scaling may be somewhat reduced.

    Like you, I’m using Amazon Web Services. I’ve been wondering how my instances will have to expand if I use a relatively pure servlet based architecture. Any comments on this…

    1. Ian, thanks for the comment. A “pure servlet based architecture” *can* certainly scale. Without knowing more about your application or architecture, a couple of considerations are: (1) pre-compute and stage CPU-intensive derived data, rather than doing it in real time. Even if you aren’t explicitly running any dedicated back-end workload servers, you can just use Quartz task-scheduling framework to do this kind of pre-computing, in your servlet containers, during low load conditions. And (2) the biggest benefit of async back-ends (like we use) is that you don’t tie up servlet threads (and impact web-service response time or browser response time) blocking on downstream calls (e.g. to 3rd-party data services) that may have highly variable response times. You can also simulate this by using your own async thread pools (we make heavy use of java.util.concurrent.Executor pattern) – while this doesn’t offload the processing from your servlet tier, it *does* alleviate long (or highly variable) response times to your clients.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

code

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>