Java

Java Web App Architecture In Takes Framework

I used to utilize Servlets, JSP, JAX-RS, Spring Framework, Play Framework, JSF with Facelets, and a bit of Spark Framework. All of these solutions, in my humble opinion, are very far from being object-oriented and elegant. They all are full of static methods, untestable data structures, and dirty hacks. So about a month ago, I decided to create my own Java web framework. I put a few basic principles into its foundation: 1) No NULLs, 2) no public static methods, 3) no mutable classes, and 4) no class casting, reflection, and instanceof operators. These four basic principles should guarantee clean code and transparent architecture. That’s how the Takes framework was born. Let’s see what was created and how it works.

Making of The Godfather (1972) by Francis Ford Coppola
Making of The Godfather (1972) by Francis Ford Coppola

Java Web Architecture in a Nutshell

This is how I understand a web application architecture and its components, in simple terms.

First, to create a web server, we should create a new network socket, that accepts connections on a certain TCP port. Usually it is 80, but I’m going to use 8080 for testing purposes. This is done in Java with the ServerSocket class:

import java.net.ServerSocket;
public class Foo {
  public static void main(final String... args) throws Exception {
    final ServerSocket server = new ServerSocket(8080);
    while (true);
  }
}

That’s enough to start a web server. Now, the socket is ready and listening on port 8080. When someone opens http://localhost:8080 in their browser, the connection will be established and the browser will spin its waiting wheel forever. Compile this snippet and try. We just built a simple web server without the use of any frameworks. We’re not doing anything with incoming connections yet, but we’re not rejecting them either. All of them are being lined up inside that server object. It’s being done in a background thread; that’s why we need to put that while(true) in afterward. Without this endless pause, the app will finish its execution immediately and the server socket will shut down.

The next step is to accept the incoming connections. In Java, that’s done through a blocking call to the accept() method:

final Socket socket = server.accept();

The method is blocking its thread and waiting until a new connection arrives. As soon as that happens, it returns an instance of Socket. In order to accept the next connection, we should call accept() again. So basically, our web server should work like this:

public class Foo {
  public static void main(final String... args) throws Exception {
    final ServerSocket server = new ServerSocket(8080);
    while (true) {
      final Socket socket = server.accept();
      // 1. Read HTTP request from the socket
      // 2. Prepare an HTTP response
      // 3. Send HTTP response to the socket
      // 4. Close the socket
    }
  }
}

It’s an endless cycle that accepts a new connection, understands it, creates a response, returns the response, and accepts a new connection again. HTTP protocol is stateless, which means the server should not remember what happened in any previous connection. All it cares about is the incoming HTTP request in this particular connection.

The HTTP request is coming from the input stream of the socket and looks like a multi-line block of text. This is what you would see if you read an input stream of the socket:

final BufferedReader reader = new BufferedReader(
  new InputStreamReader(socket.getInputStream())
);
while (true) {
  final String line = reader.readLine();
  if (line.isEmpty()) {
    break;
  }
  System.out.println(line);
}

You will see something like this:

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4

The client (the Google Chrome browser, for example) passes this text into the connection established. It connects to port 8080 at localhost, and as soon as the connection is ready, it immediately sends this text into it, then waits for a response.

Our job is to create an HTTP response using the information we get in the request. If our server is very primitive, we can basically ignore all the information in the request and just return “Hello, world!” to all requests (I’m using IOUtils for simplicity):

import java.net.Socket;
import java.net.ServerSocket;
import org.apache.commons.io.IOUtils;
public class Foo {
  public static void main(final String... args) throws Exception {
    final ServerSocket server = new ServerSocket(8080);
    while (true) {
      try (final Socket socket = server.accept()) {
        IOUtils.copy(
          IOUtils.toInputStream("HTTP/1.1 200 OK\r\n\r\nHello, world!"),
          socket.getOutputStream()
        );
      }
    }
  }
}

That’s it. The server is ready. Try to compile and run it. Point your browser to http://localhost:8080, and you will see Hello, world!:

$ javac -cp commons-io.jar Foo.java
$ java -cp commons-io.jar:. Foo &
$ curl http://localhost:8080 -v
* Rebuilt URL to: http://localhost:8080/
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection 0
Hello, world!

That’s all you need to build a web server. Now let’s discuss how to make it object-oriented and composable. Let’s try to see how the Takes framework was built.

Routing/Dispatching

The most important step is to decide who is responsible for building an HTTP response. Each HTTP request has 1) a query, 2) a method, and 3) a number of headers. Using these three parameters, we need to instantiate an object that will build a response for us. This process, in most web frameworks, is called request dispatching or routing. Here is how we do it in Takes:

final Take take = takes.route(request);
final Response response = take.act();

There are basically two steps. The first one is creating an instance of Take from takes, and the second one is creating an instance of Response from take. Why is it done this way? Mostly in order to separate responsibilities. An instance of Takes is responsible for dispatching a request and instantiating the right Take, and an instance of Take is responsible for creating a response.

To create a simple application in Takes, you should create two classes. First, an implementation of Takes:

import org.takes.Request;
import org.takes.Take;
import org.takes.Takes;
public final class TsFoo implements Takes {
  @Override
  public Take route(final Request request) {
    return new TkFoo();
  }
}

We’re using these Ts and Tk prefixes for Takes and Take, respectively. The second class you should create is an implementation of Take:

import org.takes.Take;
import org.takes.Response;
import org.takes.rs.RsText;
public final class TkFoo implements Take {
  @Override
  public Response act() {
    return new RsText("Hello, world!");
  }
}

And now it’s time to start a server:

import org.takes.http.Exit;
import org.takes.http.FtBasic;
public class Foo {
  public static void main(final String... args) throws Exception {
    new FtBasic(new TsFoo(), 8080).start(Exit.NEVER);
  }
}

This FtBasic class does the exact same socket manipulations explained above. It starts a server socket on port 8080 and dispatches all incoming connections through an instance of TsFoo that we are giving to its constructor. It does this dispatching in an endless cycle, checking every second whether it’s time to stop with an instance of Exit. Obviously, Exit.NEVER always responds with, “Don’t stop, please”.

HTTP Request

Now let’s see what’s inside the HTTP request arriving at TsFoo and what we can get out of it. This is how the Request interface is defined in Takes:

public interface Request {
  Iterable<String> head() throws IOException;
  InputStream body() throws IOException;
}

The request is divided into two parts: the head and the body. The head contains all lines that go before the empty line that starts a body, according to HTTP specification in RFC 2616. There are many useful decorators for Request in the framework. For example, RqMethod will help you get the method name from the first line of the header:

final String method = new RqMethod(request).method();

RqHref will help extract the query part and parse it. For example, this is the request:

GET /user?id=123 HTTP/1.1
Host: www.example.com

This code will extract that 123:

final int id = Integer.parseInt(
  new RqHref(request).href().param("id").get(0)
);

RqPrint can get the entire request or its body printed as a String:

final String body = new RqPrint(request).printBody();

The idea here is to keep the Request interface simple and provide this request parsing functionality to its decorators. This approach helps the framework keep classes small and cohesive. Each decorator is very small and solid, doing exactly one thing. All of these decorators are in the org.takes.rq package. As you already probably understand, the Rq prefix stands for Request.

First Real Web App

Let’s create our first real web application, which will do something useful. I would recommend starting with an Entry class, which is required by Java to start an app from the command line:

import org.takes.http.Exit;
import org.takes.http.FtCLI;
public final class Entry {
  public static void main(final String... args) throws Exception {
    new FtCLI(new TsApp(), args).start(Exit.NEVER);
  }
}

This class contains just a single main() static method that will be called by JVM when the app starts from the command line. As you see, it instantiates FtCLI, giving it an instance of class TsApp and command line arguments. We’ll create the TsApp class in a second. FtCLI (translates to “front-end with command line interface”) makes an instance of the same FtBasic, wrapping it into a few useful decorators and configuring it according to command line arguments. For example, --port=8080 will be converted into a 8080 port number and passed as a second argument of the FtBasic constructor.

The web application itself is called TsApp and extends TsWrap:

import org.takes.Take;
import org.takes.Takes;
import org.takes.facets.fork.FkRegex;
import org.takes.facets.fork.TsFork;
import org.takes.ts.TsWrap;
import org.takes.ts.TsClasspath;
final class TsApp extends TsWrap {
  TsApp() {
    super(TsApp.make());
  }
  private static Takes make() {
    return new TsFork(
      new FkRegex("/robots.txt", ""),
      new FkRegex("/css/.*", new TsClasspath()),
      new FkRegex("/", new TkIndex())
    );
  }
}

We’ll discuss this TsFork class in a minute.

If you’re using Maven, this is the pom.xml you should start with:

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>foo</groupId>
  <artifactId>foo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.takes</groupId>
      <artifactId>takes</artifactId>
      <version>0.9</version> <!-- check the latest in Maven Central -->
    </dependency>
  </dependencies>
  <build>
    <finalName>foo</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>${project.build.directory}/deps</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Running mvn clean package should build a foo.jar file in target directory and a collection of all JAR dependencies in target/deps. Now you can run the app from the command line:

$ mvn clean package
$ java -Dfile.encoding=UTF-8 -cp ./target/foo.jar:./target/deps/* foo.Entry --port=8080

The application is ready, and you can deploy it to, say, Heroku. Just create a Procfile file in the root of the repository and push the repo to Heroku. This is what Procfile should look like:

web: java -Dfile.encoding=UTF-8 -cp target/foo.jar:target/deps/* foo.Entry --port=${PORT}

TsFork

This TsFork class seems to be one of the core elements of the framework. It helps route an incoming HTTP request to the right take. Its logic is very simple, and there are just a few lines of code inside it. It encapsulates a collection of “forks”, which are instances of the Fork<Take> interface:

public interface Fork<T> {
  Iterator<T> route(Request req) throws IOException;
}

Its only route() method either returns an empty iterator or an iterator with a single Take. TsFork goes through all forks, calling their route() methods until one of them returns a take. Once that happens, TsFork returns this take to the caller, which is FtBasic.

Let’s create a simple fork ourselves now. For example, we want to show the status of the application when the /status URL is requested. Here is the code:

final class TsApp extends TsWrap {
  private static Takes make() {
    return new TsFork(
      new Fork.AtTake() {
        @Override
        public Iterator<Take> route(Request req) {
          final Collection<Take> takes = new ArrayList<>(1);
          if (new RqHref(req).href().path().equals("/status")) {
            takes.add(new TkStatus());
          }
          return takes.iterator();
        }
      }
    );
  }
}

I believe the logic here is clear. We either return an empty iterator or an iterator with an instance of TkStatus inside. If an empty iterator is returned, TsFork will try to find another fork in the collection that actually gets an instance of Take in order to produce a Response. By the way, if nothing is found and all forks return empty iterators, TsFork will throw a “Page not found” exception.

This exact logic is implemented by an out-of-the-box fork called FkRegex, which attempts to match a request URI path with the regular expression provided:

final class TsApp extends TsWrap {
  private static Takes make() {
    return new TsFork(
      new FkRegex("/status", new TkStatus())
    );
  }
}

We can compose a multi-level structure of TsFork classes; for example:

final class TsApp extends TsWrap {
  private static Takes make() {
    return new TsFork(
      new FkRegex(
        "/status",
        new TsFork(
          new FkParams("f", "json", new TkStatusJSON()),
          new FkParams("f", "xml", new TkStatusXML())
        )
      )
    );
  }
}

Again, I believe it’s obvious. The instance of FkRegex will ask an encapsulated instance of TsFork to return a take, and it will try to fetch it from one that FkParams encapsulated. If the HTTP query is /status?f=xml, an instance of TkStatusXML will be returned.

HTTP Response

Now let’s discuss the structure of the HTTP response and its object-oriented abstraction, Response. This is how the interface looks:

public interface Response {
  Iterable<String> head() throws IOException;
  InputStream body() throws IOException;
}

Looks very similar to the Request, doesn’t it? Well, it’s identical, mostly because the structure of the HTTP request and response is almost identical. The only difference is the first line.

There is a collection of useful decorators that help in response building. They are composable, which makes them very convenient. For example, if you want to build a response that contains an HTML page, you compose them like this:

final class TkIndex implements Take {
  @Override
  public Response act() {
    return new RsWithStatus(
      new RsWithType(
        new RsWithBody("<html>Hello, world!</html>"),
        "text/html"
      ),
      200
    );
  }
}

In this example, the decorator RsWithBody creates a response with a body but with no headers at all. Then, RsWithType adds the header Content-Type: text/html to it. Then, RsWithStatus makes sure the first line of the response contains HTTP/1.1 200 OK.

You can create your own decorators that can reuse existing ones. Take a look at how it’s done in RsPage from rultor.com.

How About Templates?

Returning simple “Hello, world” pages is not a big problem, as we can see. But what about more complex output like HTML pages, XML documents, JSON data sets, etc? There are a few convenient Response decorators that enable all of that. Let’s start with Velocity, a simple templating engine. Well, it’s not that simple. It’s rather powerful, but I would suggest to use it in simple situations only. Here is how it works:

final class TkIndex implements Take {
  @Override
  public Response act() {
    return new RsVelocity("Hello, ${name}")
      .with("name", "Jeffrey");
  }
}

The RsVelocity constructor accepts a single argument that has to be a Velocity template. Then, you call the with() method, injecting data into the Velocity context. When it’s time to render the HTTP response, RsVelocity will “evaluate” the template against the context configured. Again, I would recommend you use this templating approach only for simple outputs.

For more complex HTML documents, I would recommend you use XML/XSLT in combination with Xembly. I explained this idea in a few previous posts: XML+XSLT in a Browser and RESTful API and a Web Site in the Same URL. It is simple and powerful — Java generates XML output and the XSLT processor transforms it into HTML documents. This is how we separate representation from data. The XSL stylesheet is a “view” and TkIndex is a “controller”, in terms of MVC.

I’ll write a separate article about templating with Xembly and XSL very soon.

In the meantime, we’ll create decorators for JSF/Facelets and JSP rendering in Takes. If you’re interested in helping, please fork the framework and submit your pull requests.

What About Persistence?

Now, a question that comes up is what to do with persistent entities, like databases, in-memory structures, network connections, etc. My suggestion is to initialize them inside the Entry class and pass them as arguments into the TsApp constructor. Then, the TsApp will pass them into the constructors of custom takes.

For example, we have a PostgreSQL database that contains some table data that we need to render. Here is how I would initialize a connection to it in the Entry class (I’m using a BoneCP connection pool):

public final class Entry {
  public static void main(final String... args) throws Exception {
    new FtCLI(new TsApp(Entry.postgres()), args).start(Exit.NEVER);
  }
  private static Source postgres() {
    final BoneCPDataSource src = new BoneCPDataSource();
    src.setDriverClass("org.postgresql.Driver");
    src.setJdbcUrl("jdbc:postgresql://localhost/db");
    src.setUser("root");
    src.setPassword("super-secret-password");
    return src;
  }
}

Now, the constructor of TsApp must accept a single argument of type java.sql.Source:

final class TsApp extends TsWrap {
  TsApp(final Source source) {
    super(TsApp.make(source));
  }
  private static Takes make(final Source source) {
    return new TsFork(
      new FkRegex("/", new TkIndex(source))
    );
  }
}

Class TkIndex also accepts a single argument of class Source. I believe you know what to do with it inside TkIndex in order to fetch the SQL table data and convert it into HTML. The point here is that the dependency must be injected into the application (instance of class TsApp) at the moment of its instantiation. This is a pure and clean dependency injection mechanism, which is absolutely container-free. Read more about it in “Dependency Injection Containers Are Code Polluters”.

Unit Testing

Since every class is immutable and all dependencies are injected only through constructors, unit testing is extremely easy. Let’s say we want to test TkStatus, which is supposed to return an HTML response (I’m using JUnit 4 and Hamcrest):

import org.junit.Test;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
public final class TkIndexTest {
  @Test
  public void returnsHtmlPage() throws Exception {
    MatcherAssert.assertThat(
      new RsPrint(
        new TkStatus().act()
      ).printBody(),
      Matchers.equalsTo("<html>Hello, world!</html>")
    );
  }
}

Also, we can start the entire application or any individual take in a test HTTP server and test its behavior via a real TCP socket; for example (I’m using jcabi-http to make an HTTP request and check the output):

public final class TkIndexTest {
  @Test
  public void returnsHtmlPage() throws Exception {
    new FtRemote(new TsFixed(new TkIndex())).exec(
      new FtRemote.Script() {
        @Override
        public void exec(final URI home) throws IOException {
          new JdkRequest(home)
            .fetch()
            .as(RestResponse.class)
            .assertStatus(HttpURLConnection.HTTP_OK)
            .assertBody(Matchers.containsString("Hello, world!"));
        }
      }
    );
  }
}

FtRemote starts a test web server at a random TCP port and calls the exec() method at the provided instance of FtRemote.Script. The first argument of this method is a URI of the just-started web server homepage.

The architecture of Takes framework is very modular and composable. Any individual take can be tested as a standalone component, absolutely independent from the framework and other takes.

Why the Name?

That’s the question I’ve been hearing rather often. The idea is simple, and it originates from the movie business. When a movie is made, the crew shoots many takes in order to capture the reality and put it on film. Each capture is called a take.

In other words, a take is like a snapshot of the reality.

The same applies to this framework. Each instance of Take represents a reality at one particular moment in time. This reality is then sent to the user in the form of a Response.

Reference: Java Web App Architecture In Takes Framework from our JCG partner Yegor Bugayenko at the About Programming blog.

Yegor Bugayenko

Yegor Bugayenko is an Oracle certified Java architect, CEO of Zerocracy, author of Elegant Objects book series about object-oriented programing, lead architect and founder of Cactoos, Takes, Rultor and Jcabi, and a big fan of test automation.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Herman Barrantes
Herman Barrantes
9 years ago

You could incorporate jirm https://github.com/agentgt/jirm for persistence layer

Long
Long
9 years ago

you will want to see Lift in action http://www.liftweb.net
That’s the kind of framework that has everything you listed and much more.

Sean K
Sean K
9 years ago

import org.takes.Takes does not exists, so your examples do not work.

Back to top button