Java – Service method, bean as an argument and mandatory attributes

designjavaobject-oriented-design

There is a service:

createUser(User user)

where you can create a new user, with so many attributes (name, address lines…). Now, some user attributes are mandatory. Other attributes are not mandatory (like nickname or image).

The problem is that, from the outside, you can't tell the difference (in compile time). You can't force yourself to set only mandatory fields; moreover, the list of mandatory fields can change in the future and your code will stop working (while still compiling).

So we can

  • createUser(int userId, String username...) – explode the bean properties and don't use bean as an argument. Now every change will be reflected in the API, but method signatures may become huge. Also, when overloading methods for various combinations of mandatory fields with non-mandatory – that can be a mess, too.
  • use multiple beans, and have methods like (given names are just illustrative) createUser(UserCoreData user), createUser(UserFull user) but that still would not help much.
  • or have service for storing mandatory User data and then having additional services for storing additional, non-mandatory data.
  • (EDITED) or to split User into logical components (ie smaller beans) Address, DateFrom… and use those in service methods.

Is there a pragmatical way to describe service method and its arguments in case like this? (in Java).

Best Answer

Don't use a Java Bean if you can

While Java Beans are extremely useful on many occasions, I would argue that it would be better for User not to be a bean in the context you describe. You can design this class so that its instances are always in a valid state.

In the following example, name is mandatory and address is optional.

class User {
    private String name;
    private String address;

    User(String name) {
        setName(name);
    }

    void setName(String name) {
        if (name == null)
            throw new IllegalArgumentException("[name] cannot be null");
        this.name = name;
    }
    String getName() {
        return name;
    }

    void setAddress(String address) {
        this.address = address;
    }
    String getAddress() {
        return address;
    }
}

As you can see, with that design, it is not possible at any point to have a User instance without a name. However, you can have instances with an address, as well as instances without one.

Also notice that in the code example above I am performing some basic validation. If needed, you can perform more complex validation than that. For example by making sure the name does not contain characters not likely to appear in your context.

Things to consider

There are a few things to consider to make sure such a class meets its goal. Only its immutable content can be shown to the outside world, for example. Consider another similar class that contains a list that must always contain at least one item. If that list is mutable and external code can access it (through a getter, for example), nothing stops it from removing objects from it until it is empty, putting your initial instance in a state that shouldn't be allowed. Of course, if you need to expose that data, consider only giving a copy of the list or have it immutable.

What if there are a lot of mandatory values ?

If it makes sense to group some of these values in classes of their own, and that in doing so the constructor becomes wieldable, go for it. Such classes should be designed with the aforementioned constraints in mind.

Alternatively, you can define a Builder to help in constructing the objects.

What if a Java Bean is needed in a specific part of my code?

That design may work well in most places these classes are used except at specific places where, because for example a framework needs it, you must use Java beans. When that happens, instead of using beans everywhere, you can define a bean that contains the same data with added constructor and method to take care of data conversion. For example...

class UserBean {
    private String name;
    private String address;

    UserBean() {
    }
    UserBean(User user) {
        this.name = user.getName();
        this.address = user.getAddress();
    }

    void setName(String name) {
        this.name = name;
    }
    String getName() {
        return name;
    }

    void setAddress(String address) {
        this.address = address;
    }
    String getAddress() {
        return address;
    }

    User toUser() {
        User ret = new User(name);
        ret.setAddress(address);
        return ret;
    }
}

That way it is easy to have a bean from the non-bean instance and vice-versa.

Conclusion

As you can see the problem you have can be solved while keeping its initial signature. It is possible that going this way results in more code to be written for a similar result (though I wouldn't vouch for it, since passing a necessarily valid object around means less validation needed by the code using it).

I have tried to keep it short (ahem...) and to use here a design close to your initial one (beans). However, it is more for the sake of the explanation rather than because I consider that the best design. With all the hassle of maybe needing builders and additional bean classes, I would tend to go the extra mile and make the class being passed around immutable. That is how I have designed a recent, short project and it turned out to work particularly well.

Related Topic