Friends of OpenJDK Today

Lessons learned from previous projects

March 14, 2022

Author(s)

  • Avatar photo
    Nicolas Frankel

    Nicolas is a developer advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). ... Learn more

An exciting part of software development is what was unanimously considered good practice at one point in time can be more ambiguous years later. Or even plain wrong. However, you generally need to do it multiple times over time to realize it. Here are my top learnings from my experience in Java projects.

Packaging by layers

When I started my developer career in Java, every project organized their classes by layers - controllers, services and DAOs (repositories). A typical project's structure would look like this:

ch.frankel
  ├─ controller
  │  ├─ FirstController
  │  └─ SecondController
  ├─ service
  │  ├─ FirstService
  │  └─ SecondService
  └─ dao
     ├─ FirstDao
     └─ SecondDao

This approach has two main disadvantages:

  • From a visibility point-of-view, to use classes outside their package, you need to mark them as public. FirstController uses FirstService, hence the latter must be public. Because of this, any other class can use it, whereas I want it to be used only for "First"-related classes.
  • If you want to split the application, you'll first need to analyze the dependencies to understand the coupling between packages.

To fix these issues, I found that packaging by feature is a much more natural fit:

ch.frankel
  ├─ first
  │  ├─ FirstController
  │  ├─ FirstService
  │  └─ FirstDao
  └─  second
     ├─ SecondController
     ├─ SecondService
     └─ SecondDao

This way, the controller is public and represents the entry point in the feature. Services and DAOs are an "implementation detail": they have the package visibility and can only be accessed from inside their package.

As an added benefit, if you need to split your code, you only need to do it by package.

Blindly obey quality tools

I found myself using a quality tool named Hammurapi a long time ago. For the record, it still has an online presence, even if it feels like it hasn't been updated in ages. Anyway, when I ran the engine on my codebase, the most reported violation was the lack of JavaDocs on public methods. Given that all getters and setters were public, I got many of them.

It was easy to automate adding JavaDocs via a program:

/**
 Get the <code>foo</code>.

 @return Current value of <code>foo</code>
*/
public Foo getFoo() {
  return foo;
}

/**
 Set the <code>foo</code>.

 @param foo New value of <code>foo</code>
*/
public void setFoo(Foo foo) {
    this.foo = foo;
}

It satisfied the side of me that loves green checks. However, there was no added value.

In fact, most quality tools have a pretty low return over investment. It's not because you used tabs instead of spaces that your project's quality decreases drastically. Code quality is hard to define, complicated to measure, and doing so in an automated way even more so.

While I'm not saying to avoid quality tools, be careful with metrics they give you. Engineers and managers love metrics, but it can lead your team/organization to places you don't want to go, even with the best intentions.

Setters

After creating a class, Java developers always generate accessors for it, i.e., getters, and setters.

public class Money {

    private final Currency currency;
    private BigDecimal amount;

    public Currency getCurrency() {
        return currency;
    }

    public BigDecimal getAmount() {
        return balance;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }
}

public class Account {

    private Money balance;

    public Currency getBalance() {
        return balance;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }
}

It's like a Pavlovian reflex. Worse, it's part of the JavaBean conventions, so that a lot of tools rely on them: ORM frameworks, serialization libraries, e.g. Jackson, mapping tools, e.g. MapStruct, etc.

Hence, if you rely on any of those tools, you have no choice. If you don't, then you should probably think about whether you want to go this way or not.

Here's an alternative (and simplified) design to the above class:

class Account {

    // Field and getter
    // NO SETTER!

    public BigDecimal creditFrom(Account account, Money amount) {
        // Check that currencies are compatible
        // Do the credit
    }

    public BigDecimal debitFrom(Account account, Money amount) {
        // Check that currencies are compatible
        // Do the debit
    }
}

Note that getter alternatives make for a more complex design without many added benefits. I'm willing to keep them if they don't expose private data - either immutable objects or copies.

Abstractions everywhere

One of the first lessons I was taught in enterprise was that "good" developers always design their implementation around the following three components:

The problem is that FooImpl is the only Foo implementation, and it becomes apparent when you need to name the classes. The most common scheme is to prefix the abstract class with Abstract and suffix the concrete one with Impl. Another way to spot the issue is where to implement the method: between the abstract class and the concrete one, there's no easy way to decide the best place.

Abstractions do lower coupling. However, coupling in applications has much less impact than in libraries, if at all.

Data Transfer Objects

I've used DTO for a very long time. One of my earliest blog posts is actually about DTOs, bean mapping, and the Dozer library to automate the mapping process. I even remember that a fellow architect advised me to design a dedicated class for each layer:

  • Entities for the DAO layer
  • Service objects for the layer of the same name
  • View objects for the controller layer

Moreover, since PKs are not supposed to leak outside the database, we had a dedicated identifier column to pass around.

Did I hear you say over-engineering? Well, you might not be completely wrong.

It got me thinking about DTOs. They probably are a good idea if your view is very different from the underlying table(s). However, it was not the case in most, if not all, of the applications I worked on. They perfectly mimicked the database structure.

In that case, I'll probably favour one of the techniques listed in this previous post.

Conclusion

In this post, I've described five techniques I'd probably not use anymore, or at least be very careful on the context I apply them to.

The more years you have behind you, the more mistakes you'll probably have made. The idea is to build upon your experience to avoid repeating the same mistakes. As the Latin would say, errare humanum est, sed perseverare diabolicum.

Originally published at A Java Geek on March 13th , 2022

Topics:

Related Articles

View All
  • 10 Basic Questions About PDF Files for Java Developers

    PDF files are the world’s most common file format, defining 70% of the world’s documents. But they are also complex and poorly supported by Java.

    As I have spent over 20 years working with Java and PDF files, I thought a useful contribution to the excellent new foojay.io (a place for friends of OpenJDK), where you are reading this now, would be a quick guide for Java Developers!

    Read More
    August 28, 2020
  • 3 Ways to Refactor Your Code in IntelliJ IDEA

    In this blog, we’re going to look at 3 ways to refactor your code in IntelliJ IDEA.

    Simplifying your code has lots of advantages, including improving readability, tackling technical debt, and managing ever-changing requirements. The three types of refactoring we will look at in this blog are:

    – Extracting and Inlining
    – Change Signature
    – Renaming

    Read More
    January 12, 2021
  • 7 Functional Programming Techniques in Java: A Primer

    There is a lot of hype around functional programming (FP) and a lot of cool kids are doing it but it is not a silver bullet.

    Like other programming paradigms/styles, functional programming also has its pros and cons and one may prefer one paradigm over the other.

    If you are a Java developer and wants to venture into functional programming, do not worry, you don’t have to learn functional programming oriented languages like Haskell or Clojure(or even Scala or JavaScript though they are not pure functional programming languages) since Java has you covered and this post is for you.

    Read More
    May 11, 2021

Author(s)

  • Avatar photo
    Nicolas Frankel

    Nicolas is a developer advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). ... Learn more

Comments (0)

Your email address will not be published. Required fields are marked *

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard