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.
When starting a project with Arrow first go to arrow-kt.io 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:
the run:
./gradlew clean build
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/endpoints/CharacterDetails.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"
Once this exercise is completed the following test should pass:
1a should extract params from request
If you want to keep test running while making changes you may prepend the -t
modifier to the after --tests
in the command above.
Reference Links:
Once we receive the endpoint parameters as Option<String>
we need to contemplate the Some
and None
cases. 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 test 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:
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:
When querying the database with a set of Long
values the returned objects may not be found if the ids
are not recognized in the db.
Return the db object in the context of IO
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:
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
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:
1e handle DB exceptions preserving NotFoundExceptions
1e handle DB exceptions preserving NotFoundExceptions but translating all others to InvalidIdException
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
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.