Introduction: Lambda.World Cádiz 2018 Workshop - Encoding Functional web services using Arrow and Ktor
More: Author   ReportBugs   

Build Status

Lambda.World Cádiz 2018 workshop hosted by @JorgeCastilloPR and @raulraja from @47Degrees.

This workshop starts with a basic RESTful API coded using ktor (@JetBrains). Our intention is to iterate over it converting it to a more functional style by putting Arrow into practice.

Some key points you'll learn:

  • How to handle absent values incoming from an HttpRequest with the Arrow data types.
  • How lift values and raise errors inside the context of IO
  • How to defer side effecting and exception throwing computations with IO
  • How to recover from failed computations in the context of IO
  • How to reduce a set of potential values into a single one with fold
  • How to encode sequential operations with Monad Comprehensions.
  • How to encode non-dependent operations using the Applicative Builder.


0. Quick Start

When starting a project with Arrow first go to to include the necessary dependencies. The dependencies used in this work shop are included below for convenience

compile "io.arrow-kt:arrow-effects-instances:$arrow_version"
compile "io.arrow-kt:arrow-instances-data:$arrow_version"

To enable Arrow in your project include these dependencies in the dependencies section in build.gradle file. Then run the following command:

./gradlew clean build

Don't worry about the compile time warnings and errors. It's intended, the exercises are all marked as TODO() which means they're not implemented yet and therefore making tests fail. Hopefully you'll be the one fixing those!

For what is worth, you should have your gradle dependencies fetched. Alternatively, you can also click on the gradle icon in IntelliJ IDEA to fetch those.

Gradle Sync

1a. Handling nullability

All details endpoint implementations are non optional typed values now coming from the Database. These values may be absent when using an Id to look them up. You must translate that concern to a FP related data type such as arrow.core.Option.

Make the function com.fortysevendeg.arrowinpractice.workshop.ex1.paramOf located at com/fortysevendeg/arrowinpractice/workshop/ext1/Workshop.kt return an Option<String> instead of a String?.

To double check your changes, run com.fortysevendeg.arrowinpractice.workshop.ex1.WorkshopTests suite.

You may run this test in IntelliJ IDEA (right click and Run in the test file) or via the command line with:

./gradlew test --tests "com.fortysevendeg.arrowinpractice.workshop.ex1.WorkshopTests"

If you want to keep test running while making changes you may prepend the -t modifier to the after --tests in the command above.

Once this exercise is completed the test with the following name should pass:

  • 1a should extract params from request

Reference Links:

1b. Folding over optional values

Once we receive the endpoint parameters as Option<String> we need to contemplate its two possible implementations: Some and None. You may use here when or fold in order to contemplate both Some and None cases.

Modify com.fortysevendeg.arrowinpractice.workshop.ex1.idOrNotFound such as that if a value is found it's returned in IO and if the value is missing we raise a NotFoundException error in IO.

Once this exercise is completed the following tests should pass:

  • 1b should return a character Id or a raised NotFound exception in the context of IO
  • 1b should return a NotFound exception in the context of IO when an id is not found

Reference Links:

1c. Handling and recovering from errors

Converting string to long values may fail with an exception since it relies in a third party api String.toLong. Modify com.fortysevendeg.arrowinpractice.workshop.ex1.stringIdToLong so it captures this effect in IO and translates any thrown exceptions to a InvalidIdException. This may be implemented in a few different ways depending on whether you use handleErrorWith, attempt + fold + just/raiseError, etc.

Once this exercise is completed the following test should pass:

  • 1c should properly handle String#toLong with valid Long values
  • 1c should properly handle String#toLong raising errors as InvalidIdException

Reference Links:

1d. Returning db results and raising errors for missing db objects

When querying the database with a set of ids (Long values) the returned objects may not be found (as in the given ids do not correspond to any DB items). Return the db object in the context of IO if it's found, or raise a NotFoundException when the db returns an absent value.

Once this exercise is completed the following test should pass:

  • 1d fetch a character by id from the database for a given valid character id
  • 1d fetch a character by id from the database results in a NotFound raised error for invalid ids

Reference Links:

1e. Translating raised errors in the context of IO

Handle all database errors in com.fortysevendeg.arrowinpractice.workshop.ex1.handleDBExceptions so that NotFoundException is preserved but all other exceptions are translated into InvalidIdException. You may use handleErrorWith to recover from existing errors.

Once this exercise is completed the following test should pass:

  • 1e handle DB exceptions preserving NotFoundExceptions
  • 1e handle DB exceptions preserving NotFoundExceptions but translating all others to InvalidIdException

2a. Hanlding independent computations with Applicative#map()

When handling multiple ids for unrelated database objects we may independently fetch the objects (Applicative) vs explicitly fetching them one after another (Monad).

Once this exercise is completed the following test should pass:

  • 2a Return a house Location pairing a house and castle

Reference Links:


You can get a working workspace where all test pass with solutions in the solutions git tag.

git fetch --all --tags --prune
git checkout solutions

Or revert back to the original workshop state with

git fetch --all --tags --prune
git checkout workshop


Copyright (C) 2018 47 Degrees

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and
limitations under the License.
Support Me
About Me
Google+: Trinea trinea
GitHub: Trinea