Road to a more functional Java with Javaslang - example refactoring
It was a Friday like every other in SoftwareMill: I have implemented some features, prepared a pull request and waited
patiently for my teammate to review it. After a few minutes I had my feedback: “Why don’t you use Try
from
Javaslang?” - Maciek said. I have done it and shared the new version in our #java
channel on Slack. Suddenly (it was Friday, right?),
a discussion about additional improvements ignited and my code became a base for several evolutions. This blog post shows
how a small, relatively simple Java class could evolve from an old fashioned approach to a more object oriented one
and then into something (to some extent) functional. Time to start our road to better and cleaner code. All code presented
below is available on our GitHub repo.
Requirements
We need to extract the first image from a blog post that could be later used as a thumbnail in our system. Nowadays most
services support sharing their content on Facebook and almost every page has og:image
element in <meta>
section
that could be used to implement our business case.
Planned flow:
- Load a blog post page
- Extract the first
og:image
from the meta tag in<head>
section - Return an URL of this image
- If there is no such page, we encounter any exception or there is no
og:image
, log a warning and return an URL to a default image as a fallback.
Initial code
@Slf4j
public class FacebookImage {
private final static String FACEBOOK_IMAGE_TAG = "og:image";
private final static int TEN_SECONDS = 10_000;
public String extractImageAddressFrom(String pageUrl) {
Document document;
try {
document = Jsoup.parse(new URL(pageUrl), TEN_SECONDS);
} catch (IOException e) {
log.error(
"Unable to extract og:image from url {}. Problem: {}",
pageUrl,
e.getMessage()
);
return DEFAULT_IMAGE;
}
List<Element> ogImages = List
.ofAll(document.head().getElementsByTag("meta"))
.filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property")));
if (ogImages.isEmpty()) {
log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl);
return DEFAULT_IMAGE;
}
return ogImages.get(0).attr("content");
}
}
As you can see it is not very complicated. But there is an issue that should struck us immediately: this code is
everything but object oriented. FacebookImage
class in its current shape would be used in a following way:
FacebookImage facebookImage = new FacebookImage();
String imageAddress = facebookImage.extractImageAddressFrom(blogPostAddress);
So we create an empty class object and put all the logic in the only public method. To highlight where the problem lies,
let’s try to make our method static:
public class FacebookImage {
public static String extractImageAddressFrom(String pageUrl) {
// ...
}
}
Apart from adding static
keyword, we did not have to change anything else. It turns out that we have a stateless thing
that could be renamed to FacebookImageUtils
and it will still compile and pass all tests without any other changes in the code!
public class FacebookImageUtils {
public static String extractImageAddressFrom(String pageUrl) {
// ...
}
}
So instead of wiring object-oriented code, we have a well hidden procedural code pretending to be something else.
Towards a more object-oriented approach
Our FacebookImage
class does not hold any state. We treated it as a dumb container for a single stateless method,
which is unacceptable in a language where real, proud objects should be the first citizens.
Luckily, making our class great again is not hard. Instead of returning only a state we care about (our image URL),
we should embed it in our class as a field, so FacebookImage
can stand proudly next to other objects in our system:
@Slf4j
public class FacebookImage {
private final static String FACEBOOK_IMAGE_TAG = "og:image";
private final static int TEN_SECONDS = 10_000;
private final String url;
public FacebookImage(String pageUrl) {
Document document;
try {
document = Jsoup.parse(new URL(pageUrl), TEN_SECONDS);
} catch (IOException e) {
log.error(
"Unable to extract og:image from url {}. Problem: {}",
pageUrl,
e.getMessage()
);
url = DEFAULT_IMAGE;
return;
}
List<Element> ogImages = List
.ofAll(document.head().getElementsByTag("meta"))
.filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property")));
if (ogImages.isEmpty()) {
log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl);
url = DEFAULT_IMAGE;
} else {
url = ogImages.get(0).attr("content");
}
}
public String getUrl() {
return url;
}
}
Now, when our class is used, its instance really holds a real value:
FacebookImage facebookImage = new FacebookImage(blogPostAddress);
String imageAddress = facebookImage.getUrl();
This looks so much better. Now it is a real object with something inside!
Towards a more functional approach - step one
Ok, since we have our class written in a more object-oriented way, it is time to apply some functional programming
concepts available in Javaslang to FacebookImage
internals. Let’s begin with Try
. If this concept is new to you,
here is a short description from their javadocs:
Try is a monadic container type which represents a computation that may either result in an exception, or return
a successfully computed value. It’s similar to, but semantically different from Either. Instances of Try, are either
an instance of Success or Failure.”
So, we will try to replace try-catch
section with Try.of
and use javaslang.collection.List
. It provides streams
capabilities with no explicit stream()
call required.
@Slf4j
public class FacebookImage {
private final static String FACEBOOK_IMAGE_TAG = "og:image";
private final static int TEN_SECONDS = 10_000;
private final String url;
public FacebookImage (String pageUrl) {
Try<String> imageTry = Try.of(() -> {
Document document = Jsoup.parse(new URL(pageUrl), TEN_SECONDS);
List<Element> ogImages = List.ofAll(document.head().getElementsByTag("meta"))
.filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property")));
if (ogImages.isEmpty()) {
log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl);
return DEFAULT_IMAGE;
} else {
return ogImages.get(0).attr("content");
}
});
url = imageTry
.onFailure(error -> log.error(
"Unable to extract og:image from url {}. Problem: {}",
pageUrl,
error.getMessage()
))
.getOrElse(DEFAULT_IMAGE);
}
public String getUrl() {
return url;
}
}
This is definitely a step into the right direction, but we are still far from a fluent, functional execution with actions
coming seamlessly one after another.
Towards a more functional approach - step two
To achieve this we need to go deeper. Luckily, Try
is not only a variation of Either
for success/failure scenarios.
It is also a fully functional concept with a handy mapTry
method, allowing us to chain the executions into a single
flow. And when we extract steps into functions, the entire logic starts to look really nice and clean:
@Slf4j
public class FacebookImage {
private final static String FACEBOOK_IMAGE_TAG = "og:image";
private final static int TEN_SECONDS = 10_000;
private final String url;
public FacebookImage(String pageUrl) {
CheckedSupplier<Document> parseDocument = () -> Jsoup.parse(new URL(pageUrl), TEN_SECONDS);
CheckedFunction<Document, List<Element>> findElementsWithPropertyTag =
document -> List.ofAll(document.head().getElementsByTag("meta"));
CheckedFunction<List<Element>, List<Element>> findElementsWithFacebookImageProperty =
elements -> elements.filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property")));
Consumer<List<Element>> warnIfEmpty = elements -> {
if (elements.isEmpty()) {
log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl);
}
};
CheckedFunction<List<Element>, Element> findFirst = elements -> elements.get(0);
Function<Element, String> content = getContentValue -> getContentValue.attr("content");
url = Try.of(parseDocument)
.mapTry(findElementsWithPropertyTag)
.mapTry(findElementsWithFacebookImageProperty)
.peek(warnIfEmpty)
.mapTry(findFirst)
.toOption()
.map(content)
.getOrElse(DEFAULT_IMAGE);
}
public String getUrl() {
return url;
}
}
Wrap up
Now, after all these steps, we have ended up with a pretty nice and still readable solution that is way nicer than the
initial version. Along the road of this refactoring, we have learnt a bit about designing our classes to be more
object-oriented and even a bit more functional. Getting more familiar with Javaslang was not a bad thing either.
This is how our transformation of FacebookImage
class looks like. What are your thoughts about it? Does it appeal
to you or maybe you are more into stopping it just after we made FacebookImage
class more object-oriented? Please
share your thoughts, we are eager to see different points of view from other developers.