Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Functional RESTful Backends for DataTables

DZone's Guide to

Functional RESTful Backends for DataTables

If you need an easy way to aggregate multiple functionalities into a single user interface, look no further than DataTables.

· Database Zone
Free Resource

Navigating today's database scaling options can be a nightmare. Explore the compromises involved in both traditional and new architectures.

Functional programming presents itself as a great building block for composing RESTful backends that handle errors and behaviors elegantly.

With my previous project, we wanted to give to our development teams the ability to start in functional programming and compose reusable backends for UIs that use the DataTables Plugin for JQuery. DataTables, in general, are an easy way to aggregate multiple functionalities into a single UI. Using one component, we can have search and pagination together. Once we have all these functionalities associated, we need a good programming paradigm for composing controllers and services as well as separating concerns.

Functional programming comes in handy once we can use its paradigms and apply it to solve some RESTful back-end challenges.

UI

The purpose of our UI is to give an intuitive component where users can filter, order, and paginate results in a DataTable.

Image title

Global Shared State

Properly designed, RESTful APIs will avoid the use of the global shared state.

Shared mutable state is the root of all evil.

When it comes to Web APIs, there's no reason why you should share state between calls, and even though REST acronym mentions state, they do so in the aspect of propagating the current state of the domain or changing it. It all happens in one atomic operation. When the call is over, the state is either propagated from client to server or from server to client.

Within Java EE, there is some shared state considering Servlet implementations will create server session (javax.servlet.http.HttpSession) to store user data.

request.getSesstion.setAttribute(java.lang.String name, java.lang.Object value)

The use of user session on the server side creates a global shared state and consumes significant server resource to store user data. Even though we try to minimize the memory footprint by not storing data in servlet session with the method shown above, one possible alternative is to use a different web framework.

Reactive frameworks like Spring Web Reactive and Play Framework use a different user session management. What they usually do is propagate an encrypted session cookie with 4 KB of user information back and forth. This approach decreases memory footprint for our application while favoring scalability.

With that in mind, let's see how our backend makes use of Jersey to map RESTful calls to our services.

Backends for Frontends

With the growth of different clients for services like mobile and desktop, and with the explosion of JS libraries (each requiring proper semantics to publish its data model), an easy way to overcome this challenge is to design backends that support the client dynamics. While doing this, we start to see the semantics of DataTables in our controller.

@Named
@Path("/customers")
public class CustomerQueries {

    @Context
    protected HttpServletRequest request;

    @Inject
    private CustomerService service;

    private final String[] cols = new String[]{"id", "name","email"};

    @GET
    @Path("/paginate")
    @Produces({DataTableResponse.JSON})
    public Response list(@QueryParam("draw")   final Integer draw,
                         @QueryParam("start")  final Integer start,
                         @QueryParam("length") final Integer length,
                         @QueryParam("search[value]") final String searchValue,
                         @QueryParam("columns[0][search][value]") final Long id,
                         @QueryParam("columns[1][search][value]") final String name,
                         @QueryParam("columns[2][search][value]") final String email,
                         @QueryParam("order[0][column]") final Integer order,
                         @QueryParam("order[0][dir]") final String orderDir) {
    }
}

The main purpose here is to guarantee that every param from the component can be mapped to a Jersey REST param call and a proper Response can be generated.

Monadic Design

During a REST call, many things can happen. Different errors can occur, the client might have insufficient permission to the operation one's trying to accomplish, the data might be gone, and the data might be incomplete.

If we try-catch, if-else, or loop all these possibilities, we end up with a code base that's hard to maintain, hard to read, and not reusable. Thus comes monadic design; the idea is to incorporate all these aspects into well-known types in functional programming, all being a monad. Failures, the absence of values, and granularity can all be designed around this concept, and the logic becomes part of the API.

The user of this API can then compose on top of it, creating an elegant and fluent way to express these different aspects and add behavior to all of them.

We used Vavr, formerly Javaslang, to bring functional extensions to the Java language.

There are different monads for different purposes, especially when asynchronous programming is involved. In our case, we just wanted a container type to encapsulate Success and Failure abstraction, representing success with some value or failure with no value but an exception. The Try monad does just that, therefore this is what we used to design our API.

Notice how we use the Try monad to model our countAll method for our service. If the call succeeds, we'll have a Success type with the total number of rows. If it fails, we'll get a Failure type with an exception.

public Try < Long > countAll() {
 return Try.of(() -> repository.count());
}

For the purpose of better understanding let's show our Customer entity with its specification methods.

@Entity
@Table(name = "Customers")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Customer {

    @Id
    @Column
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @Column
    private String name;

    @Column
    private String email;

...

    public static Specification<Customer> id(final Long id) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.<Long>get("id"), id);
    }

    public static Specification<Customer> nameStarts(final String name) {
        return (root, criteriaQuery, criteriaBuilder) ->
                criteriaBuilder.like(root.get("name"), name+"%");
    }

    public static Specification<Customer> withEmail(final String email) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email);
    }

}

The main purpose of our API is to build a pagination method. This method takes input parameters to build a list of Spring Data JPA specifications that is later reduced to a single specification combining all elements of the list with an AND operator.

Here, we use the Try monad in our service to model the paginate method. If our repository calls to find customers succeed, we'll get a Success container type with a Page of Customer; if not, we'll get a Failure container type with an exception.

public Try<Page<Customer>> paginate(final Long id, final String name,
                                    final String email, final PageRequest pr) {

        List<Specification<Customer>> specs = List.empty();

        specs = id != null && id > 0? specs.append(id(id)) : specs;
        specs = Strings.isNullOrEmpty(name)? specs : specs.append(nameStarts(nome));
        specs = Strings.isNullOrEmpty(email)? specs : specs.append(withEmail(email));

        final Specification<Customer> spec = specs.reduce((a1, a2) -> where(a1).and(a2));

        return Try.of(() -> specs.isEmpty()? repo.findAll(pr) : repo.findAll(spec, pr));

    }

The idea here is to use Spring Data API semantics to build a query that represents UI events described above.

The methods nameStarts, withEmail, and id are all part of our DSL to build the query. They are statically imported from our Customer Entity. Each one of them is associated with an input box in the UI and can be composed together, depending on the presence of absence of value.

The most import part is the return line. Notice how there is no exception handling and especially no logging, therefore no side effect.

Side Effects and Referential Transparency

Side effects can be viewed as anything that is done in the function aside from its core purpose, like logging, disk writing, and changing variables from outside scope.

We can look at the above function as a single piece of code, one that can be tested and mocked without worrying about side effects. This function can even be replaced by its return value and keep the same behavior promoting reusability and easy testing. This feature in functional programming is called referential transparency.

Referential transparency guarantees that this function can be called many times with the same arguments and produce the same result once it's not influenced by external state or side effects. That's really good for testing and code reusability, and many would say it's one of the most important factors of functional programming.

Chaining Operations

An article about functional programming wouldn't be complete if didn't mention PF jargon like monoids, monads, and functors. We gotta cover them but my best advice for enthusiasts like myself it to read Kelly Robinson's article about it.

Long story short, we use these structures to operate on wrapped values and unwrap them on the right moment or take an action depending on the method result. All this is done by chaining operations together in a fluent manner using flatMap and map functional operations.

A function f that converts values of type A to values of type B can be said to be a function from A to B.

Therefore, the map function for the Try monad can be described as a function that takes a function f from T to U and applies the given function to its existing value T returning its same type Try. In other words, it knows how to convert the value inside the monad without unwrapping it.

Function flatMap for the Try monad can be described as a function that takes a function f from T to Try of T returning the same type Try.

function sCALA JAVA
map

map[U](f: T => U): Try[U]

<U> Try<U> map(Function<? super T, ? extends U> mapper)

flatMap

flatMap[U](f: T => Try[U]): Try[U]

<U> Try<U> flatMap(Function<? super T, ? extends Try<? extends U>> mapper)

Evaluating the flatMap signature, we see how we can operate in a wrapped value (and in case of success return, another wrapped value) for our chaining purposes.

In our case, we were able to count all results, fetch page data, transform it, and build a response to the user request.

    @GET
    @Path("/paginate")
    @Produces({DataTableResponse.JSON})
    public Response list(@QueryParam("draw")   final Integer draw,
                         @QueryParam("start")  final Integer start,
                         @QueryParam("length") final Integer length,
                         @QueryParam("search[value]") final String searchValue,
                         @QueryParam("columns[0][search][value]") final Long id,
                         @QueryParam("columns[1][search][value]") final String name,
                         @QueryParam("columns[2][search][value]") final String email,
                         @QueryParam("order[0][column]") final Integer order,
                         @QueryParam("order[0][dir]") final String orderDir) {

        final Integer page = new Double(Math.ceil(start / length)).intValue();
        final PageRequest pr = new PageRequest(page, length,
                new Sort(new Sort.Order(Sort.Direction.fromString(orderDir), cols[order])));

        final Try<Response> response = service.countAll().flatMap(qt ->
            service.paginate(id, name, email, pr).flatMap(p ->
                transform(p).map(data -> {

                    final DataTableResponse dtr = DataTableResponse.builder()
                            .data(data)
                            .draw(draw)
                            .recordsFiltered(qt)
                            .recordsTotal(qt)
                            .build();

                    return Response.status(Response.Status.OK).entity(dtr).build();

                })
            )
        ).recover(e -> {

            //Async LOG
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();

        });

        return response.get();

    }

Here, we have the Sucess and Failure abstracted by Try. Notice how it's used as the return value for the methods countAll and paginate from service as well as method transform from our controller. Once all of them use the same Try abstraction, in the case of success, we end up with all variables to construct our response. In case any error occurs during counting, fetching or transforming our Try will be an instance of Failure containing an exception and we can handle that accordingly.

Just in case you really want to remove all side effects from our function, handling logging as an asynchronous message would be the right choice.

We've learned about side effects of RESTful APIs, embraced errors as part of our domain using Monads, and seen our application as a referentially transparent function composed of other referentially transparent functions with no global shared state and no side effects. These principles make functional programming the right paradigm for scalable RESTful APIs in any language.

Please make sure to check the project's source code for deeper understanding.

Understand your options for deploying a database across multiple data centers - without the headache.

Topics:
functional programing ,database ,tutorial ,ui ,datatable

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}