Recently I joined a new project team. We implement a microservice architecture using Spring Boot and several other cutting edge technology. While most team members are experienced programming in Java and Java EE, some of them are new to the Spring Framework and most are not quite familiar with how to structure Spring Beans properly.
In this post I will briefly explain how to implement a „good“ Bean (read Java class) and why following these simple rules will make your life easier. For this we’ll take a look at a basic Java class and then dissect it to understand what details make it a good Bean.
Interfaces 101
Just before we start dismantling a real Spring Bean, let’s have a look at the interface which the Bean implements.
package de.flaviait.blog.beans; /** * A service to manage {@link User}s. * * @author WaldemarBiller */ public interface UserService { /** * Persists the given {@link User} into the datastore. * * @param user the user to persist, must not be {@literal null} * @return the persisted user or {@literal null} * @throws UserExistsException in case when a user with same normalized name already exists */ User create(User user) throws UserExistsException; /** * Updates the given {@link User} in the datastore * * @param user the user to update, must not be {@literal null} * @return the persisted user or {@literal null} */ User update(User user); /** * Removes the given user from the datastore * * @param user the user to remove, must not be {@literal null} * @return {@literal true} in case the {@link User} does not exists anymore, {@literal false} otherwise */ boolean delete(User user); }
As usual the interface starts with a package declaration. No magic; please just avoid underscores in names.
It’s good style to provide a brief JavaDoc comment which states the general purpose of the interface. Don’t explain the world! The interface name should fit that purpose as well. An established naming pattern is to use an entity name and the kind of Bean type (in terms of Domain Driven Design, which knows Services, Repositories etc). If this doesn’t fit an adjective might be the right thing.
Each method the interface defines should also have a JavaDoc comment. The description usually starts with a verb.
For each parameter give a little description and tell the preconditions the parameter has to fulfill e.g. an integer being positive.
If the method throws a checked exception describe under which circumstances it is supposed to happen. Finally describe every possible kind of value that can be returned.
Following design by contract this JavaDoc is the contract for any implementing class. Implementors are not allowed to break it!
Back to Beans
Now let’s take that look at the example class.
package de.flaviait.blog.beans; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; /** * Default implementation of the {@link UserService}. * This class uses {@link UserRepository} to store {@link User}s. * * @author WaldemarBiller */ @Service class UserServiceImpl implements UserService { /* constants ******************************************************************************************************/ private static final Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class); /* dependencies ***************************************************************************************************/ private final UserRepository repository; /* constructors ***************************************************************************************************/ /* * @param repository the user repository, must not be {@literal null} */ @Autowired UserServiceImpl(UserRepository repository) { Assert.notNull(repository) this.repository = repository; } /* methods ********************************************************************************************************/ @Override public User create(User user) throws UserExistsException { Assert.notNull(user, "user must not be null"); LOG.info("Looking up count of users with normalized name '{}", user.getNormalizedName()); if (repository.countByNormalizedName(user.getNormalizedName()) > 0) { throw new UserExistsException(user.getNormalizedName()); } LOG.info("Persisting user '{}'", user.getNormalizedName()); return repository.save(user); } @Override public User update(User user) { Assert.notNull(user, "user must not be null"); LOG.info("Persisting user '{}'", user.getNormalizedName()); return repository.save(user); } @Override public boolean delete(User user) { Assert.notNull(user, "user must not be null"); Integer id = user.getId(); LOG.info("Removing user '{}'", user.getNormalizedName()); repository.delete(user); return !repository.exists(id); } }
Like almost every Java Class file this Spring Bean starts with a package declaration followed by the imports, and also sports a JavaDoc comment. Here you state what makes this implementation special and discriminates it from alternatives.
It’s good practice to divide the class into sections. A good order might be: inner classes, constants, members, dependencies, constructors, property methods, methods. But that’s just my preference.
Each class should have a logger if any necessary. Please declare it private, static and final! That way it has to be instantiated only once per class while still allowing to filter the logs properly.
Dependency Injection
Spring and Java EE are all about Dependency Injection.
The dependencies section features every dependency of the Bean. If a dependency is mandatory it should also be declared final. Please avoid field injection, as it harms you when you try to test the Spring Bean. Instead of this the constructor is annotated with @Autowired and every mandatory dependency is provided as a parameter. If your parameter list exceeds 5 items you should rethink your class structure and divide the Bean into several parts. Any optional dependency can be set via a setter, which has to be annotated with @Autowired, too.
Assert Parameters
Just like the method declarations in the interface the constructor defines a contract with preconditions. All preconditions can be checked for instance using Spring’s Assert helper class. You could also use Guava’s Preconditions class or some such, as long as it throws an IllegalArgumentException or some domain driven derivative if a precondition is violated. Please make sure to check the preconditions before any calculation!
The methods don’t need to have a JavaDoc comment as long as they just follow the contract which was defined in the interface. Keep it DRY!
That’s it. Spring Beans made easy.
The origin if the title picture is https://commons.wikimedia.org/wiki/File:Roasted_coffee_beans.jpg. It is in the public domain.