Sunday, January 31, 2021

testing your backend without getting testy

 Testcontainers is a wonderful library. It allows you to quickly add Docker containers to run services that are needed for your tests. It is the ultimate mock object, since it is nearly identity in function to the service backing your application. In this post, I will use Testcontainers to create a MySQL database to test my Spring Boot application. This uses JUnit 5, Spring Boot 2.4.2, and the current release of MySQL. Full source is here.

Saturday, January 23, 2021

a beautiful option

Recently, I have seen examples of the use and misuse of Java's humble Optional that reduced code readability. One just example was an article on Medium, and showed a few examples in which the code was redundant and verbose, and generally difficult to follow. And while you can misuse Optional in this way, it does not have to result in convoluted code. Instead we can apply the same functional principles that gave us the Optional in the first place and create beautiful code Code that is clean and concise, uncluttered by null checks and if statements.

In this article, I will borrow the same scenario - building a shelf of book's authors - to show that readability can be increased, insomuch as something as subjective as code readability can be.
You have a Book object which you pass to a buildBookshelf method, which returns an enriched Shelf object. 

The Book class, might be defined like this:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Long id;
private String name;
private Optional<Long> authorId;
}

While Author, I presume, was defined something like this:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Author {
private Long id;
private Optional<String> name;
}

If you wanted to get the Author object to add to the Shelf, and were not using JPA to find the Author, you might do the following:

var authorOptional = repository.findById(authorId);

The example next made a check to see that the Optional was not empty, then used the get() method to return the Author, and again for the Author's name.

However, Optional has a method tailor made to make this code more concise.

public Shelf buildShelf(final Book book) {
var shelf = new Shelf();
book.getAuthorId().ifPresent(authorId -> {
var authorOptional = repository.findById(authorId);
authorOptional.ifPresent(author -> {
author.getName().ifPresent(authorName -> {
shelf.addAuthorName(authorName);
});
});
});

return shelf;
}

That is not terrible. It is null safe, and fairly readable. However, it does have a faint, inoffensive odor, reminiscent of the one that waifs off of a callback hell code block. 

I think that we can do better. 

And we can, however, we should not merely think of Optional as a stand-in for a class that can be nullable. It is far more versatile than just a wrapper for null. It is a functional construct, and we should use it as such. We should afford it the same respect as any Collection. In fact, it helps to think of it more as a Collection of zero or one elements. Then we can unlock its true potential. 

Like a stream of a Collection, Optional has the method map that will allow you to apply a Function to each element to transform it to some other output. If we took our nested Optionals and construct a chain of maps instead, then we could use this to “lift” the Optionals to continue the computation, if there are values, or short circuit the computation if any one is empty.

public Shelf buildShelf2(final Book book) {
var shelf = new Shelf();
book
.getAuthorId()
.map(repository::findById)
.orElse(Optional.empty())
.map(Author::getName)
.orElse(Optional.empty())
.ifPresent(shelf::addAuthorName);
return shelf;
}

This is better. The whiff of callback hell is diluted, while preserving the null-safety of the previous attempt. However, the interlaced orElses obfuscates the intent of the code. You could add a comment, but that would be tantamount to an admission of failure. 

But can we do better still?

We can, but first, a slight detour.

Scala has this wonderful language construct called the for comprehension. It is very different than a for loop in Java. It can be used to do exactly what we are looking to do: chain together computation or halt computation if it encounters an empty Option -  Scala’s equivalent to the Optional – which is called None. If we were defining this method in Scala, we might do it like this. 

def bookShelf(book: ScalaBook) : ScalaShelf = {
val name = for {
authorId <- book.authorId
author <- repository.findById(authorId)
name <- author.name
} yield name

val shelf = new ScalaShelf()
shelf addAuthorName name
shelf
}

Sheer elegance. Clear intentions. 

Now there is a little slight of hand embedded in there. I changed the definition of Shelf::addAuthorName to accept an Option[String], rather than just a String. 

But why shouldn’t we do that? 

def addAuthorName(name : Option[String]) : Unit = {
name match {
case Some(authorName) => addAuthorName(authorName)
case None =>
}
}

Here, I match the types of Option that it might receive, either Some, which has a value, or None, which does not. If it receives a Some then it adds the author’s name that the Some wraps to its Set of names. 

By now, hopefully, you are wondering why Java withholds this beautiful functionality from us. If only we need not care if our computation encounters an empty Optional. It would just handle it gracefully, and not throw an NPE. 

Well, we can get the same effect in Java, using Optional’s flatMap method. In fact, Scala’s for comprehension is more or less just doing that. 

We can try to construct this method using flapMap, it might look something like this:

public Shelf buildShelf3(final Book book) {
var shelf = new Shelf();
var authorName = book
.getAuthorId()
.flatMap(repository::findById)
.flatMap(Author::getName)
;

shelf.addAuthorName(authorName);
return shelf;
}

We will have to go back and update our Shelf class to have its method addAuthorName accept an Optional<String>, but I ask you again, why would we not do this?

public void addAuthorName(final Optional<String> optionalAuthorName) {
    optionalAuthorName.ifPresent(authorNames::add);
}

Conversely, we might accept nulls and test for them prior to adding the author's name. Or we might add the ifPresent to the end of our computation.

public Shelf buildShelf4(final Book book) {
var shelf = new Shelf();
book
.getAuthorId()
.flatMap(repository::findById)
.flatMap(Author::getName)
.ifPresent(shelf::addAuthorName);

return shelf;
}

The choice is yours. Happy hacking.

Saturday, January 5, 2019

Generic JDBC Writes using Consumers



One might argue that this is the exact scenario that Hibernate was made for: a series of interconnected and related tables. Each subsequent table needs the primary key from the prior table. This might be the case when you have tables that decorate the one another.

In the following example, we will create a user object, which will be decorated by a web user object. The user object holds general information about the user (e.g. their name), while the web user object contains their login credentials.

We will start by spinning up a Postgres Docker container.

$ docker run --name oms -e POSTGRES_PASSWORD=D3faulTp455w0rD -d -p 5432:5432 postgres

Once the container is up, we can create the tables that we will use. We will use the Postgres command line tool to connect to the Postgres server embedded within the Docker container and create the tables.

$ docker run -it --rm --link oms:postgres postgres psql -h postgres -U postgres
Password for user postgres:
psql (11.1 (Debian 11.1-1.pgdg90+1))
Type "help" for help.

First, we shall create the users table.

postgres=# CREATE TABLE IF NOT EXISTS users (id serial NOT NULL, first_name VARCHAR(255), 
last_name VARCHAR(255));

Next, we will shall create the web_users table, which is the table that has the extended attributes.

postgres=# CREATE TABLE IF NOT EXISTS web_users (id serial NOT NULL, user_id INT, 
user_name VARCHAR(255), pass_word VARCHAR(255));


postgres=# \q

Now it is time to move onto the Java code. This first thing that we need is a Connection.

Connection getConnection() {
    Connection conn = null;
    String url = "jdbc:postgresql://localhost:5432/postgres?user=postgres" + 
                 "&password=D3faulTp455w0rD";

    try {
        conn = DriverManager.getConnection(url);
    } catch (SQLException e) {
        e.printStackTrace();
    }

    return conn;
}

Next, we will define two Functional Interfaces. They will behave like Consumers, but as they are JDBC based, they will throw SQLExceptions.

@FunctionalInterface
interface PreparedStatementArgumentSetter {
    void setArgs(PreparedStatement ps) throws SQLException;
}

@FunctionalInterface
interface ResultSetExtractor {
    void extract(ResultSet rs) throws SQLException;
}

We will need Domain objects to use in the JDBC operations. We are going with minimalism and brevity in this example, rather than entirely production ready code.

class User {
    public int userId;
    public String firstName;
    public String lastName;

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                '}';
    }
}

class WebUser {
    public int webUserId;
    public int userId;
    public String userName;
    public String passWord;

    @Override
    public String toString() {
        return "WebUser{" +
                "webUserId=" + webUserId +
                ", userId=" + userId +
                ", userName='" + userName + '\'' +
                ", passWord='" + passWord + '\'' +
                '}';
    }
}

We will now combine these two concepts into a method that will consume the Functional Interfaces to perform the JDBC operations.

void writeEntry(String sql, PreparedStatementArgumentSetter setter, 
                            ResultSetExtractor extractor) {
    PreparedStatement stmt = null;
    try {
        stmt = getConnection().prepareStatement(sql);
        setter.setArgs(stmt);
        ResultSet rs = stmt.executeQuery();

        if (rs.next()) {
            extractor.extract(rs);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

We will define two methods that use this method in order to create the User object and the connected WebUser object.

User createUser(final User user) {
    writeEntry("INSERT INTO users (first_name, last_name) VALUES (?,?) RETURNING id",
            ps -> {
                ps.setString(1, user.firstName);
                ps.setString(2, user.lastName);
            }, rs -> {
                user.userId = rs.getInt(1);
            });
    return user;


 WebUser createWebUser(final User userfinal String userName, final String passWord) {
    final WebUser webUser = new WebUser();
    writeEntry("INSERT INTO web_users (user_id, user_name, pass_word) VALUES (?,?,?)" +
               " RETURNING id",
            ps -> {
                ps.setInt(1, user.userId);
                ps.setString(2, userName);
                ps.setString(3, passWord);
            }, rs -> {
                webUser.webUserId = rs.getInt(1);
                webUser.userId = user.userId;
                webUser.userName = userName;
                webUser.passWord = passWord;
            });

    return webUser;
}

All that is left is to run this code.

void run() {
    var u = new User();
    u.firstName = "Nathan";
    u.lastName = "Crocker";

    createUser(u);
    System.out.println("created user: " + u);

    var w = createWebUser(u, "ncrocker", "D3faulTp455w0rD__again");
    System.out.println("created webUser: " + w);
}


created user: User{userId=1, firstName='Nathan', lastName='Crocker'}
created webUser: WebUser{webUserId=1, userId=1, userName='ncrocker', 
passWord='D3faulTp455w0rD__again'}

And now we clean things up:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  
e9dd1ef16476        postgres            "docker-entrypoint.s…" 
 
$ docker stop e9
 
$ docker rm $(docker ps -a -q)
e9dd1ef16476
 
$ docker rmi $(docker images -q)

Further details about the Postgres Docker image can be found here: https://hub.docker.com/_/postgres/