Skip to main content
Blog

Functional Java by Example | Part 5 – Move I/O to the Outside

By 12 november 2018No Comments

This is part 5 of the series called “Functional Java by Example”.

In previous part we stopped mutating our documents and returned copies of the data. Now, we need to move some I/O away.

If you came for the first time, it’s best to start reading from the beginning. It helps to understand where we started and how we moved forward throughout the series.

These are all the parts:

I will update the links as each article is published. If you are reading this article through content syndication please check the original articles on my blog.

Each time also the code is pushed to this GitHub project.

Move I/O to the outside

Remember how we left things previously?

class FeedHandler {

  Webservice webservice
  DocumentDb documentDb

  void handle(List<Doc> changes) {

    changes
      .findAll { doc -> isImportant(doc) }
      .each { doc ->
        createResource(doc)
        .thenAccept { resource ->
          documentDb.update(
            setToProcessed(doc, resource)
          )
        }
        .exceptionally { e ->
          documentDb.update(setToFailed(doc, e))
        }
      }
  }

  private CompletableFuture<Resource> createResource(doc) {
    webservice.create(doc)
  }

  private boolean isImportant(doc) {
    doc.type == 'important'
  }

  private Doc setToProcessed(doc, resource) {
    doc.copyWith(
      status: 'processed',
      apiId: resource.id
    )
  }

  private Doc setToFailed(doc, e) {
    doc.copyWith(
      status: 'failed',
      error: e.message
    )
  }

}

The example I’m evolving in each part of the series is some kind of “feed handler” which processes documents.

What does the processing look like?

  1. one or more documents come in
  2. if a document is “important”, it is saved to a webservice API which creates and returns a resource for it
  3. if this succeeds, the document is marked as processed
  4. if this fails, the document is marked as failed
  5. ultimately, the document’s updated in a database

The webservice could be a REST service (since we’re talking about resources) and the database could be a document store as CouchDB or MongoDB (since we’re talking about documents), but that doesn’t really matter.

What matters is that there’s some I/O (input/output) involved, usually in any system. Reading from the filesystem, loading en storing information in a database, communication across the network between webservices.

As we’ve seen in previous instalment we like our functions to be as pure as possible, without any side-effects. Unfortunately real systems have to interact with the outside world to be any meaningful.

How else would we get input into our system, or output anything to our users? Some examples of I/O are:

  • file system access
  • network sockets
  • HTTP requests
  • JDBC actions
  • starting threads
  • system clock access

We already got rid of our database access from our setToProcessed/setToFailed methods, by moving it one step up the call chain, but it’s still inside the FeedHandler.

The best we can do is move I/O to the outside of the system.

The most obvious change we can do is to get rid of the DB altogether, and just return the new updated documents from handle().

Get rid of the database

Change

.thenAccept { resource ->
  documentDb.update(
    setToProcessed(doc, resource)
  )
}
.exceptionally { e ->
  documentDb.update(setToFailed(doc, e))
}

to

.thenApply { resource ->
  setToProcessed(doc, resource)
}
.exceptionally { e ->
  setToFailed(doc, e)
}

to get rid of documentDb.

We’re just returning any modified documents even further up the call chain. That’s why we have also have to…

…get rid of void

Change the return type from

void handle(...)

to

List<Doc> handle(...)

so handled documents are returned all the way to the outside.

It’s not that we don’t have any interaction any more with any database, but that it’s no longer a concern for our FeedHandler component! By moving any I/O to the outskirts of the system, everything in between can be as pure as possible.

Remember Haskell, which is considered a “pure” functional language? From Learn you a Haskell for Great Good:

It turns out that Haskell actually has a really clever system for dealing with functions that have side-effects that neatly separates the part of our program that is pure and the part of our program that is impure, which does all the dirty work like talking to the keyboard and the screen. With those two parts separated, we can still reason about our pure program and take advantage of all the things that purity offers, like laziness, robustness and modularity while efficiently communicating with the outside world.

When it was invented in the 90s, it introduced the IO monad to deal with I/O. Any function e.g. reading from the outside world must use the return type IO which is actually being checked by the compiler.

This has a few benefits, such as that the Haskell compiler has some freedom in re-ordering all non-IO code for optimisation. From Pure Functions and I/O:

Because pure functional code is like algebra, the compiler can treat all non-IO functions as mathematical equations. This is somewhat similar to how a relational database optimizes your queries.

In Java, we don’t have such specific compiler support for these things, but there’s a few things we can take care of ourselves.

Remember: void is a sink-hole. Any method returning void is either meaningless or operates through side-effects, such as writing to display, network, file or database – i.e. interaction with an external system. Instead of performing I/O as side-effect, return a value to the caller describing the interaction with the external system.

That’s it for now!

Just for giggles: YouTube: Input/Output

This is a cross-post from my personal blog. Follow me on @tvinke if you like what you’re reading or subscribe to my blog on https://tedvinke.wordpress.com.