Legacy web applications tended to be monolithic and difficult to scale. As we saw in Chapter 4, it’s possible to move legacy applications to Platform-as-a-Service, but it can be tedious as the architecture is generally quite different. The true power of Platform-as-a-Service is realized when you start from scratch, writing new applications.
Before we take a look at creating new apps on PaaS, it will be useful to examine the monolithic nature of old-school applications.
Monolithic code is where you take one application and keep adding more capabilities to it, sticking on features and adding search functions, account management, and blog posts. It’s a Frankenstein monster. It’s a patchwork. You get memory leaks, and the app consumes more and more RAM.
Debugging these apps becomes a maze, whereas if each of your individual components is separate—i.e., if the search component is separate from the blog post component, which is separate from the user component, etc.—and each of those components can run and scale independently, you have much more flexibility. For example, if your service depends more highly on a search function than it does on managing and registering users, you can scale the search component up very high, giving it many resources, while on the other hand the user component can be a very small application. But if it’s a monolithic application, every component is part of the big app.
Even if you start trying to rope off components of an application that you suspect have memory leaks by routing those requests to a separate pool of processes, that is about as effective as throwing a carpet over dry-rotted wood floors. The problem not only does not go away, but actually gets worse.
A best practice for programming and within Platform-as-a-Service is to think of each of your data sources and each of your application’s areas of expertise as a small, independently running service. When you decompose monolithic applications into smaller ones that all are tied together and interact, the benefits of PaaS become clear very quickly.
Small applications that do one thing well are much easier to scale because you can pick different backend technologies that are right for the tool. In a large monolithic application, you might shove one backend technology into it simply because that’s the technology used for everything else. You might use MySQL where Redis might be the better option, or you might use PostgreSQL where a MongoDB database might make more sense.
Debugging smaller applications is significantly easier as well. The more code there is within an individual application, the more interdependent the application becomes, making it harder to test because the interdependencies require testing more and more edge cases.
A best practice for professional developers is to constantly test their code, through unit, functional, and integration tests. This is also much easier to do with smaller, service-based applications than it is with a large interdependent monolith. With smaller services, you end up with components that can more easily be exhaustively and independently tested.
Programming history is littered with examples of large monolithic applications being built up and then torn apart, sometimes many times, over and over. Twitter started out in 2006 as a relatively monolithic Rails application. In 2010, it was decoupled into an API serving a lightweight JavaScript frontend. Since then, it has added functionality in a service-oriented way, incorporating technologies like Scala and Python and even Drupal to create a highly distributed scalable system. You can track the history of Twitter’s adventures in breaking down the monolith at the High Scalability blog. Eventually, Twitter replaced its JavaScript API−consuming frontend with a more dynamic frontend, for various reasons; however, the dynamic frontend still consumes the same distributed APIs underneath.
Similar stories can be found at many large and small companies today. Developers are starting to learn lessons from these large-scale services and to develop best practices around them, so that they can balance speed of development with an overall architecture better suited to the needs of scaling. It is essentially a reemergence of service-oriented architecture (SOA) for the Web using new technologies to make it easier to build and maintain, as well as standards (like REST) to make services more manageable and uniform.
Consider a common scenario for large businesses and financial or government institutions, where you’re building an application that has data-constrained portions that can only run in house. This data-restricted part of your application usually ends up being a small fraction of the app, perhaps 5% to 10%.
If you build your application monolithically, the entire application will have to be run in house due to the data constraints. However, if you build it to run as a distributed system, with many APIs feeding into a lightweight JavaScript frontend, or even a simple dynamic frontend that consumes other service APIs, you can still host 90% of your application safely in the public cloud. All the mundane pieces that do not interact with sensitive data can be run on commodity hardware that is cheaper to run in the cloud.
We’ve seen many successful examples of this modern service-oriented, API-driven architecture. We already mentioned Twitter, but Gmail also took a very API-driven, client-side approach, putting together APIs that interact with the mail and having the frontend JavaScript client consume it all. Apple did something similar with MobileMe: the backends are simple APIs, and the frontend is a thick, client-side application that handles much of the logic in real time. This enables more flexibility, more scalability, and more foresight, leading to better, more maintainable applications.
This approach of building many smaller services works especially well with Platform-as-a-Service. In fact, it is a best practice to do it this way with PaaS. Making small independent services that all tie together on the frontend is not only a modern approach to developing both web and mobile applications, but also ties in well when you are trying to decide how to incorporate a Platform-as-a-Service strategy.
One of the driving motivations behind the decomposition and servicification of application development also happens to be one the biggest, most disruptive technologies that we face today: mobile technology. The mobile sphere has grown quickly to become a vital piece of our lives. In 2012, there were 54 billion cumulative downloads of native apps in app stores, adding more than 2 million downloads a month. Some estimates say that this represents only one-tenth of the entire market for mobile applications.
In a world where there are half a trillion mobile applications, the computing power and programming paradigms for systems supporting these applications will need to keep up. Many mobile applications are fed by backend services to register users; share photos, news, and information; offer advertisements; and provide many more dynamic features. Apps like Instagram, Pinterest, and even Facebook would be nothing without the backend API infrastructure to feed them.
One of the biggest success stories in recent mobile application development is Instagram. With a team of only 13 people, its creators built a billion-dollar company from a mobile application. What programming work were those 13 people actually doing? The native mobile application side of Instagram is not the most complex technology in the world. Significantly harder was building the backend to support the native mobile application for millions of concurrent users. Building the infrastructure underneath the mobile application to gather and share the information and process the photos was an incredibly difficult challenge. This team used the Amazon cloud to accomplish this feat. For more details about how they did it, you can read the High Scalability blog post, “The Instagram Architecture Facebook Bought For A Cool Billion Dollars”.
The success of Instagram is indicative of the potential for modern application developers, both in enterprises and in small startups, to get in front of millions, or even tens of millions, of smartphone users. However, you need to be prepared to manage and deal with the backend constraints that such a deluge of customers puts on your service. Those kinds of constraints make it necessary to build scalable API services.
The beauty of these services is that these APIs, if well constructed and well thought out, are exactly the same APIs that can power your modern web applications as well. If you approach building these applications from a service-oriented perspective, the services can be the backend for both your mobile and your web applications. If you think about building your web application this way from the start, when you are ready to add a mobile application, it is already prepared. It already has the same data sources for your mobile application to consume. If you have a mobile application and you need a web presence in front of it, this is a great way to leverage that kind of technology.
In the early incarnations of service-oriented architecture, standards like SOAP, XML-RPC, and WSDL were common for structuring data and communicating through APIs. These were heavyweight and inflexible standards that were typically pretty hard to work with.
In the reemergence of service-oriented architecture, there are new, more nimble and lightweight concepts like JSON and REST, which enable agility and velocity while keeping the benefits of a loosely coupled distributed system.
JSON (short for JavaScript Object Notation) has emerged as a standard format for transferring data within API services, serving as an alternative to XML. JSON is used in a wide range of applications, including Gmail, MobileMe, iCloud, Twitter, LinkedIn, and almost every major website you’re likely to interact with. One of the reasons that it has been so predominant is because it is easy and natural for JavaScript on the web frontends to consume the JSON data. JSON has strong support on native mobile clients as well. The great libraries for parsing JSON within mobile and web APIs have made it a natural standard for generating and presenting the data for modern application design.
The JSON scheme is not hard to read, both for computers and for humans. Here is an example:
{"id":1,"name":"Programming for PaaS","price":123,"tags":["PaaS","Programming"]}
One of the nice aspects of the JSON format is how easy it is for web browsers to parse. This makes it a great format for Ajax programming techniques. Here is an example of how easy it is for a native JavaScript client like the one in your browser or in any Node.js application to parse:
varobject=eval(text);
This is not the most secure technique (validating the text before executing it is always best practice). However, it shows the power of the JSON format.
REST (short for Representational State Transfer) is an architecture pattern (or style) for distributed
systems like the Web. REST has become a predominant service design
model, reusing HTTP’s basic vocabulary like GET, POST,
PUT, and DELETE. Because REST uses
simple HTTP commands, it uses much less bandwidth than services like
SOAP that demand lengthy headers and heavy XML parsing.
The basic principle behind REST is to provide a shared network vocabulary for transferring resources in a standard and predictable format. What does that mean? Let’s look at an example of what a RESTful JSON service for serving and managing user data might look like:
GETPUTUpdates the resource (not cacheable)
http://example.com/users
Header:[{'id':123,'name':'x'},{'id':124,'name':'y'}]Response:200OK
http://example.com/users/123
Header:{'id':123,'name':'x'}Response:200OK
http://example.com/users?name=b
Header:[{'id':124,'name':'y'}]Response:200OK
POSTCreates a new resource (not cacheable)
http://example.com/users
Header:{'id':125,'name':'c'}Response:200OK
http://example.com/users/123
Not used
http://example.com/users?name=b
Query strings are not needed when creating new resources
DELETERemoves the resource (not cacheable)
http://example.com/users
Header:NoneResponse:200OKNote:Deletedallusers
http://example.com/users/123
Header:NoneResponse:200OKNote:Deletedonlyuser123
http://example.com/users?name=b
Header:NoneResponse:200OKNote:Deletedallusersnamedb
With a RESTful approach, getting and managing services becomes predictable and straightforward. Creating and scaling any particular RESTful service is not usually very challenging.
For example, if your service is unlikely to get more than 100,000 rows in the next few years, then there is little need to worry about scaling beyond a traditional MySQL or PostgreSQL database table. These traditional SQL tables can easily scale to millions or even tens of millions of rows on a single server.
However, if you anticipate the service growing to 100,000,000 rows, you might want to consider a database better suited to highly distributed growth, like Riak or CouchDB.
A metaservice is a RESTful application for serving collections of data.
In slightly more detail, it is a lightweight application that consumes a core service (e.g., MySQL, MongoDB, Redis, memcached, etc.) and produces a REST API, which generally serves JSON or XML serialized data.
A metaservice is like a core service wrapped in REST with limited application logic specific to whatever the metaservice specializes in. Examples include a user registration metaservice, a commenting metaservice, a message metaservice, and a blog metaservice. Sometimes the same database serves various metaservices, but in other cases the metaservices will have their own databases and core services.
Traditionally, instead of building metaservices for data sources, you would build a lot of code that essentially did the functionality of REST, but mixed a bunch of services together, cross-function the logic, and, instead of generating simple JSON serialized objects, resulted in a complex mess of dynamically generated HTML, CSS, and JavaScript. When you wanted to swap out components, you found it difficult, if not impossible. When you wanted to scale components, they would be intertwined. The same table that would only need 100 records accessed frequently was on the same database as the one that needed to grow to 100,000,000 records, which would be infrequently accessed but needed to support fast indexed searching. This is the nightmare of scaling web applications in a traditional way—and it is a path that has been trodden by thousands of developers before you. Those developers usually ended up decomposing these applications into metaservices, so why not go with metaservices from the start?
A metaservice is built on top of the idea of a core service. It’s powered by these services—by the MySQLs, MongoDBs, and PostgreSQLs of the world. However, they are wrapped within a lightweight API that can still talk directly to the drivers and services. Instead of presenting HTML, or a finished frontend to be delivered directly to the user, these are typically much simpler. They simply generate JSON: serialized data structures built on the existing services. So, a metaservice delivers serialized data to be consumed by the client frontend, typically using a serialization format like JSON. These metaservices have become the building blocks of modern, scalable web architecture.
There are frameworks well suited to quickly generating RESTful applications in every language you can think of. Chapter 10 has a full list of frameworks specifically built for a wide variety of languages. No matter what language you use, you should be able to find an appropriate REST framework.
The one thing all of these frameworks have in common is that they
make it easy to develop RESTfully routed URLs and provide structure to
the code that responds to each HTTP verb (GET,
PUT, POST,
DELETE, etc.).
Why use metaservices? Metaservices are very simple to build and simple to consume. They don’t usually use as much logic as monolithic applications. That leads to getting work done faster.
Once they are created, metaservices can be consumed in multiple ways. They can be consumed on mobile clients or on the Web, and they can be put together using different technologies on the frontend.
There are many libraries in every programming language built to consume metaservices. You give the library a RESTful URL, and it will model the result for you in your code. This lets you stitch together many metaservices in a lightweight, dynamic backend that can serve the compiled data to the end user. Here is a code example using Ruby’s ActiveResource library that shows an example of how easy these libraries can be to interact with:
classPerson<ActiveResource::Baseself.site=ENV["PEOPLE_REST_URL"]//BestPracticeendryan=Person.new(:first=>'Ryan',:last=>'Daigle')ryan.save# => trueryan.id# => 2Person.exists?(ryan.id)# => trueryan.exists?# => trueryan=Person.find(1)# Resource holding our newly created Person objectryan.first='Rizzle'ryan.save# => trueryan.destroy# => true
You can see how powerful REST clients can be. They can be consumed very similarly to core services like MySQL and PostgreSQL, and serve many more purposes as well.
This will be covered in Chapter 6.
Metaservices can be consumed directly in the browser. In fact, you
do not need any frameworks at all to process a standard RESTful URL. Any
browser-based Ajax call with an exec() function can do the trick:
varurl="http://example.com/users";varreq=newXMLHttpRequest();req.onreadystatechange=function(){if(req.status==200){varjson=req.responseText;// this is not secure, only// showing this for examplevarobject=exec(json);// do something with// the object...}}req.open("GET",url,true);req.send();
This code can be a little tricky and cumbersome, but some really great modern JavaScript frameworks have been developed that you can add to any website that will not only automate the consumption of these metaservices, but also display the data in a structured way. They include:
For a deeper discussion on how to select the proper JavaScript framework for your needs, see Steven Anderson’s blog post, “Rich JavaScript Applications—the Seven Frameworks”.
Impressive technologies like SproutCore and Cappuccino are able to provide building blocks to bind your data against. This is a well thought out and proven method for developing applications in modern desktop environments, incorporating data sources and data bindings.
As our JavaScript frontend client layers get thicker and thicker, we’re starting to see more and more composition of data sources in the frontend. Frontends are being presented through very dynamic, well presented, structured web interfaces.
Tools like SproutCore and Cappuccino provide building blocks, the foundational pieces that are tied together in a very standard way to build modern applications that feel like desktop applications on your web or mobile clients. In fact, SproutCore and Cappuccino are similar to using Apple’s Xcode or Microsoft’s Visual Studio. To a traditional web developer who has never created a desktop or native mobile application before, these tools can feel strange at first. The visual components are more strictly defined for you, and once you bind data sources to them, they seem to magically just work.
One of the benefits of using these technologies is that they are so prescribed, they have such a strong experience tied into them, that some of these clients can actually be compiled into native mobile applications. If you write your application in Cappuccino, for example, there are services like NativeHost that will compile your application without having to change anything to run in a desktop environment as a native app, which leads to a cross-media way of thinking about application development.
These technologies are so rich and becoming so well supported that we are seeing even large companies starting to use them. Apple has used SproutCore to build out its entire iCloud suite of web applications, supporting Mail, Contacts, Calendar, Notes, Reminders, Find My Phone, and iWork. NPR has built a beautiful Cappuccino-based player to stream its radio content. There are many new examples of adoption of these thick client interfaces every day.
The weaknesses of migrating legacy applications into Platform-as-a-Service are exactly the kinds of weaknesses that are counterbalanced when you develop applications using metaservices.
In a monolithic app, you have to worry about all sorts of things, like which parts of your app are using the disk, where it is leaking memory, and what code is running where. It becomes very complicated to move legacy applications because there are simply so many components to them.
When you’re using the new approaches listed in this chapter to develop applications, the benefits include the ability to build simpler, more lightweight backend clients that have less dependence on filesystems and more dependence on scalable services. It is much easier to add more instances of whatever part of your app needs to scale. Need more capacity on the search component of your site? Focus just on the search metaservice.
Using these best practices and developing an app from scratch, you’re thinking about how each component can survive and scale independently; these are exactly the kinds of problems that you would need to solve when migrating an application from a monolith into Platform-as-a-Service. In fact, one of the most effective ways to migrate legacy code into PaaS is by separating logical chunks of your monolith into metaservices and starting to decompose your app piece by piece.
This new form of development yields significant benefits in four important areas: agility, time to market, scalability, and interoperability.
Agility is being able to move quickly with the needs of an application. It’s the ability to add new features. It’s the ability to move features that aren’t working out of the program and to rebuild features. Building applications for PaaS using a service-oriented metaservice approach increases that agility manyfold.
PaaS increases agility when you’re adding features and functionality, because independent teams can be working on these metaservices independently. If you need to add functionality, you can do so without having to deconstruct and understand the entire monolith of your application. In short, Platform-as-a-Service lets you be much more agile when thinking about application deployment.
Time to market is shorter with these new techniques. You spend less time thinking about maintaining and scaling an application, and much more time thinking about how all the components work together.
You are not spending time building up a huge monolith. You can prototype a simple backend, make sure that it works and is consumable on the frontend, and get your application up and running very quickly (much faster than if you had had to architect the entire system up front).
It becomes much easier to scale an application if the pain points of the application can be limited to one or two different services that are the key components. If you break those out and they become independent of each other, if the data sources can be independent, you can think about the scalability of much smaller components, which turns out to be a much easier problem to solve.
One of the great benefits of building applications in this manner is that the frontend rarely cares where the services and metaservices are hosted, making it a generational leap forward in interoperability. In fact, an application can now be consumed from many different data sources, many different data centers, many different regions, and many different vendors. It becomes an interoperable patchwork of scalable modern architecture.
One of the most vexing problems for enterprises and governments is how to realize the benefits of utilizing public cloud systems while keeping sensitive data restricted. Using the methods we’ve discussed in this chapter, businesses and governments can get the best of both worlds. They can enjoy the best agility and fastest time to market. They can utilize business-critical IT applications as services that are consumed in public cloud−hosted applications and mobile apps, which are delivered to their employees and customers in a safe and secure manner. This means they can start to experience the benefits that have already been realized by many smaller technology companies.
In addition, with metaservices, there can be independent teams working together in parallel on many different aspects of an application. Thus, tackling the development of large applications becomes a simpler problem of small teams focused on doing one thing very well.
The reason that client-side JavaScript and thick clients are becoming more popular isn’t simply a matter of fashion. It is not simply the “cool” way to develop applications. It’s actually driven by Moore’s law. It’s driven by the nature of computation itself.
In the earliest days of web application development, users’ computers were very, very slow. Remember Netscape Navigator? The only way to develop productive web applications was to build large applications that ran on very large servers. Early applications like Amazon had to invest in hardware much faster than desktop PCs. Since just loading web pages at all put a strain on PCs, the hard work had to be done on the server side.
As we have seen Moore’s law taking effect, we’ve seen users’ computers getting faster and faster. Now even a smartphone has more processing power than the fastest desktops of the ’90s. This is what has driven the new type of development that we’ve been discussing. Modern computers and smartphones are immensely faster and more capable than the computers in the early days of the Web; this is driving the migration to client-side composition and thick client-side logic.
Frameworks like SproutCore and Cappuccino would have been completely untenable in the days of Internet Explorer 3. Today, the hardware capabilities have increased to the point where client-side logic can run so quickly that it can create a better user experience for data to be composed in real time, like with Gmail and Twitter. It also provides a much easier, much more scalable system for modern application developers on the backend.
Monolithic web application development wasn’t the best practice. It was simply the most practical method that you could take advantage of in the earliest days of the Web. Now we have progressed to the point where client-side computing is fast enough to enable the development of far more capable applications than we’ve ever seen before.
The best practices that we’ve discussed in this chapter have evolved based on the increasing speeds of CPUs and are a reflection of the effects of Moore’s law. It is not a fashion statement, and it’s transforming the way generations of developers are getting their jobs done.