Projects
-
An Intro to vavr's Either
Either an orange or an apple would be delicious.
Either
is an incredibly useful tool in a Java programmer’s handbook, one that brings functional programming control to Java.If that didn’t make sense, I get it. Either is much easier to use, to me, than to explain.
What
Either
doesAn
Either
is used when you want to return one of two types, typically a failure or a success. If an operation succeeds, we don’t really see any benefit of using Either. But if it fails, instead of throwing an exception and catching it, we can transform all our exceptions into a predictable failure class, which reduces boilerplate code.Here is a wonderful example from Yuri Mednikov on Medium:
From a technical point of view, either is a container, that can hold two values — one for successful result and one for failed result. Imagine, that you write a code, that deals with file reading — you use a file service abstraction, that your colleague wrote for you. You don’t need to know how it works, you just call a method, that returns a content of the file. Yet, you know, that it may throw an exception, if file does not exist. Take a look on the following code snippet:
FileReadService service = new FileReadService(); try { String content = service.readFile("no-such-file.txt"); return content; } catch (BadFilenameException ex){ System.out.println("No file found"); return readFromCache();
Imagine, that in future, the technical design will require the
FileReadService
to determine also other error cases. The number ofcatch
clauses with increase… WithEither
type you can refactor this code as following:FileReadService service = new FileReadService(); String content = service.readFileSafely("no-such-file.txt").getOrElse(readFromCache());
This style helps you not only to eliminate try-catch blocks, but also to specify a single entry point for all errors to make them to recover from a cache.
The full article is a pretty good read, check it out.
Getting fancier
Once you’ve gotten the hang of basic Either usage, know that the real power of Either comes from chaining results. With the
map()
method we can transform anEither.right
(which is by convention the happy-path case) any way we choose, and withmapLeft()
we can transform anEither.left
(by convention the error path case) in the same way.Imagine we have a service that queries for a row in a database and maps it to a database object. Then, since we don’t want our db models to leak into the rest of our service, we convert it to a service-level DTO. Without Either, it might look something like this:
public MyDto findById(int id){ try { MyDbModel result = repo.getById(id); if (result !=null){ return mapToMyDto(result); } return null; //which will likely cause an error upstream } catch (JdbiException e){ throw e; //or return null, which will cause another error upstream } }
Pretty simple, but let’s walk through it.
The code asks our repo class to find something by id. If it’s found, we’ll assume it’s mapped to our MyDbModel class. If the result is null, we have a problem. Similarly, if the database throws any errors, all we can do is try to catch them.
With Either, our code can look like this:
public Either<Problem,MyDto> findById(int id){ return repo.getById(id).map(this::mapToMyDto); }
It’s not just 10 fewer lines of code to write, it’s also, I would argue, easier to read. BTW, in this case Problem is a custom pojo that has a type, a message, and of course anything else we would like to add.
In this example, we are also assuming that the repo method also returns an Either. If it’s a left, it is returned up the call stack until something determines what to do with it. If it’s a right, the model is transformed into a
MyDto
using the same mapper class.My current favorite
Either
method:sequence
One very cool method that comes with
Either
, and one that I find myself using more lately, issequence
, which transforms a list ofEither
s into anEither
of lists. If that sounds confusing, imagine this example:public Either<Problem,<List<String>>> createStrings(List<String> inputs){
Actually, wait, interruption time - I don’t know what this contrived example is supposed to do either. Let’s pretend we’re transforming these strings in a way that is possible to throw an error, but the transform method catches all errors and transforms them into
Problem
s (our custom, predictableEither.left
.). That would look like this:public Either<Problem,<List<String>>> createStrings(List<String> inputs){ List<Either<Problem,String> results = List.of(); for (String myString: inputs){ results.add(performRiskyOperation(myString)); }
At this point we have performed some operation on every string in our list of strings, and added each result to a list to hold the results. Because
performRiskyOperation
returns anEither<Problem,String>
, each item in the list is either aProblem
or a string. In other words, if we were to inspect our list, it might look like this:results.get(0); //Left(Problem.UnsupportedOperation) results.get(1); //Right("hello"); results.get(2); //Right("world"); results.get(3); //Left(Problem.Null)
But what we really want is all the strings, if the operation succeeded each time, or an error, if it didn’t. That’s where
sequence
and its cousinsequenceRight
comes in:public Either<Problem,<List<String>>> createStrings(List<String> inputs){ List<Either<Problem,String> results = List.of(); for (String myString: inputs){ results.add(performRiskyOperation(myString)); } return Either.sequenceRight(results).toJavaList(); }
That one-liner turns the list of Eithers into one Either, which contains either a Problem, or the full, happy-path result (the list of strings). (
toJavaList()
is needed if you want to turn theSeq
object, which is list-like but not a list, into a regular ol’ list.)sequenceRight
will return a single Problem (the first one it encounters) or the full list; regular oldsequence
will return a list of Problems or the full list of strings. The cost is, well, the pretty ugly method signature ofcreateStrings
, but when you are working with lists and Either, it is a small price to pay.There are many other methods available within Either, and I hope you’ll read the docs on them here.
-
How to set up a backend FastAPI server on nginx (or: I am bad at devops)
I’m setting up a new backend server for a silly side project I’m working on, and every time I do this I forget all the steps I need to follow.
These steps are for a FastAPI server, served through nginx running on a Unix box (an Amazon EC2 instance running Ubuntu). My wonderful husband set up the nginx server over a decade ago. I’m just a tenant (hopefully a quiet tenant who is getting their security deposit back). Therefore, some of these instructions might also only work on 10-year-old systems with slightly idiosyncratic setups, but I think most of the steps are the same for most servers.
Step 0: Build your FastAPI server
This blog post doesn’t include how to do that. There are great docs on the project homepage, though. I had 0 experience with FastAPI a week ago and I have a working API today, so the docs must be great.
Step 1: Buy a domain
I used Cloudflare.
Step 2: Point the domain to existing host
What is my IP? IDK. I have other domains proxied by Cloudflare and they all point to the same place, so…that’s my IP. (I think the real answer is, look in the AWS console.) I setup an A record for the root domain (.mydomain.com) pointing to Github Pages and for my API (api.mydomain.com) pointing to my server’s IP.
Step 3: Set up a basic ngnix config listening on port 80.
To do this, we SSH into the server and add a config to
/etc/nginx/sites-available/mysite
.server { root /path/to/site/content; index index.html index.htm; server_name yourdomain.com; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } listen 80; }
-
Some Basic Rake Tasks for Jekyll Users
I recently tweaked a few of the (very basic) rake tasks I’m using to keep this blog going. Ruby isn’t my thing, but writing these was interesting and I figured I’d share in case they are useful to anyone else.
Wait, back up. What’s a rake task?
Rake is a task runner in Ruby. Jekyll, which powers this blog, is written in Ruby. Similar to how a JS-based project might have scripts in
package.json
that allow a developer to run tests or start a server, rake allows devs to define custom tasks and then run them, using the syntaxrake taskname
. In my case, I wanted a task that would create a new draft based on a template, and a task that would automate deployment of the blog, to prevent any mistakes from making it into prod. (I recently erased my site byrsync
ing the wrong directory to my server. I got it back a minute later, but I’m tired of doing that.)Ok, I’m sold. How do I write a rake task?
I created a file in my site’s root directory called
rakefile
that holds the tasks. Then I define my tasks in Ruby. I had to google a lot of syntax for these very basic tasks – Ruby is not a language I know!desc "Create draft with template." task :newdraft do puts "new draft name: " my_file = STDIN.gets.chomp source_file = "path/to/site/root/folder/template.md" destination_file = "path/to/site/root/folder/_drafts/#{my_file}.md" unless File.exist?(destination_file) FileUtils.cp(source_file,destination_file) puts destination_file end cmd = "code #{destination_file}" system(cmd) end
This defines a task that takes input from the user (me), and creates a new Markdown file from copying a basic template with some default front-matter defined, then opens it in VSCode. Notice I have basically no input sanitization here, and things like the default IDE (VSCode) are hardcoded. Change to suit your needs.
The other thing you’ll notice is that the task has a description. This, it turns out, is required for the task to be discoverable by Ruby, so don’t leave it out!
The second task is even simpler:
desc "Build and deploy." task :deploy do system("/path/to/deploy/script.sh") end
The deploy script is a simple bash script that changes to the site’s root directory, runs the jekyll build command, then rsyncs the
_site
folder to another folder on my computer, which is set up with Syncthing to copy new files to the remote web server. (This is not a typical build pipeline and I am thankful to be in a two-nerd household.)One might ask, why have a rake task if all it does is execute a shell script? The cool thing about rake tasks is they can be executed from anywhere in the directory tree at the level of the rakefile or below. As I understand it,
rake
will start in its current directory and then keep going upward until it finds a rakefile with a matching task name.This is why ‘real’ Ruby projects usually namespace their tasks, because this behavior in a big project could lead to issues. In my tiny project, though, it’s exactly what I want. Now, I can do
rake deploy
from anywhere within my site structure.Yes, I could also just run
/the/full/path/to/deploy.sh
from anywhere, but that involves more typing.That got me thinking, too – to start the Jekyll local server, I type
bundle exec jekyll serve --drafts
. This can be shortened tobundle exec jekyll s -D
, and if I use history search it’s really not that bad to retrieve this command, but could I make this more efficent?With a rake task that looks like this:
task :start, :withDrafts do |t, args| trap('SIGINT') { puts "\nI quit."; exit } args.with_defaults(:withDrafts=>"false") puts("Running the server with drafts #{args[:withDrafts]? 'on': 'off'}") system '/path/to/start/file/start.sh', args[:withDrafts] end
and a bash script that looks like this:
#! /bin/bash SITE_ROOT_DIR="/full/path/to/my/site/root" WITH_DRAFTS=$1 if [ $WITH_DRAFTS = 'true' ]; then cd $SITE_ROOT_DIR && bundle exec jekyll serve --drafts else cd $SITE_ROOT_DIR && bundle exec jekyll serve fi
I can start a rake task that starts the server and waits until I ctrl-c to end the task. There are two somewhat interesting bits in the Ruby code:
trap
works the same as abash
trap, in that it listens for the ctrl-c signal and then executes the code in the function. This is just to make the exit look cleaner, the task works fine without it.And adding the
:withDrafts
parameter to the task, with a default set of false, allows me to pass true or false into the shell script. This syntax is pretty bonkers to me as a non-Ruby user but it seems to work fine.Note that for both of the tasks that run shell scripts,
chmod +x script.sh
is required to make Ruby able to run these as executables.This was a fun exercise. I have now written nearly 900 words on this topic, plus the testing and writing of the rake file itself, which means I have not yet saved myself any time with these new commands. But maybe a hundred or so blog posts later I’ll be singing a different tune. :)