User management microservice (Part 3): Implementing and testing repositories

April 18, 2017 · 5 min read – Last updated on August 10, 2020

In the previous part there was fair amount coding involved over the course of implementing the domain model, which comprises all the logic a user registration process needs. In this third post, I went ahead and added a concrete JPA-based implementation of UserRepository, a JPA support module and some test cases.

1. Using XML for mapping Plain Old Java Objects

By just looking at UserRepository, probably you would think of any difficulties for adding a JPA-based implementation to it.

public interface UserRepository {

  void delete(Long userId) throws NoSuchUserException;

  Optional<User> findById(Long id);
  Optional<User> findByEmail(String email);
  Optional<User> findByScreenName(String screenName);

  User save(User user);

}

However, I mentioned in the first part, that we’d be using DDD (Domain-Driven Design) and as a consequence, no framework specific dependency (including JPA annotations) cloud be used in the model. The only viable option which remains is doing the mapping in XML. I think I haven’t touch an orm.xml file since 2010, if I correctly recall, so I started to craft it with nostalgia.

This is how User‘s mapping looks like in XML, an excerpt from user-orm.xml.

<entity class="com.springuni.auth.domain.model.user.User" cacheable="true" metadata-complete="true">
<table name="user_"/>
<named-query name="findByIdQuery">
  <query>
    <![CDATA[
      select u from User u
      where u.id = :userId
      and u.deleted = false
    ]]>
  </query>
</named-query>
<named-query name="findByEmailQuery">
  <query>
    <![CDATA[
      select u from User u
      where u.contactData.email = :email
      and u.deleted = false
    ]]>
  </query>
</named-query>
<named-query name="findByScreenNameQuery">
  <query>
    <![CDATA[
      select u from User u
      where u.screenName = :screenName
      and u.deleted = false
    ]]>
  </query>
</named-query>
<entity-listeners>
  <entity-listener class="com.craftingjava.commons.jpa.IdentityGeneratorListener"/>
</entity-listeners>
<attributes>
  <id name="id"/>
  <basic name="timezone">
    <enumerated>STRING</enumerated>
  </basic>
  <basic name="locale"/>
  <basic name="confirmed"/>
  <basic name="locked"/>
  <basic name="deleted"/>
  <one-to-many name="confirmationTokens" fetch="LAZY" mapped-by="owner" orphan-removal="true">
    <cascade>
      <cascade-persist/>
      <cascade-merge/>
    </cascade>
  </one-to-many>
  <element-collection name="authorities">
    <collection-table name="authority">
      <join-column name="user_id"/>
    </collection-table>
  </element-collection>
  <embedded name="auditData"/>
  <embedded name="contactData"/>
  <embedded name="password"/>
  <!-- Do not map email directly through its getter/setter -->
  <transient name="email"/>
</attributes>
</entity>

DDD is a persistence ignorant approach, hence persisting a model which wasn’t designed with the target data store in mind might be challenging. Of course, it also has got that advantage that a real-world problem gets modeled directly and not just as a side effect of using a certain technology stack in a certain way.

public class User implements Entity<Long, User> {

  private Long id;
  private String screenName;

  ...

  private Set<String> authorities = new LinkedHashSet<>();

}

The natural way how authorities (~privileges) of a User can be modeled is a simple set of strings or enumerated values.

Using a document database like MongoDB would make this model so easy and natural to persist. It might look like this. (BTW, I plan to add a Mongo-based repository implementation later in this series as well)

{
   "id":123456789,
   "screenName":"test",
   ...
   "authorities":[
      "USER",
      "ADMIN"
   ]
}

With relational modelling however, the concept of Authority has to be handled as a child relation to User. Yet,
in the real world this is just a set of authorities. How do we bridge that gap?

ElementCollection which was introduces in JPA 2.0 comes to the rescue. It’s similar to OneToMany in its semantics. In this case the configured JPA provider (Hibernate) generated the necessary child relation automatically.

create table authority (
  user_id bigint not null,
  authorities varchar(255)
)

alter table authority 
  add constraint FKoia3663r5o44m6knaplucgsxn 
  foreign key (user_id) references user_
  

2. New modules in the project

springuni-auth-user-jpa is the one I’ve been talking about so far, which contains a concrete JPA-based implementation of UserRepository.
The objective was that every module should have merely those dependencies which absolutely necessary to their operation. This one only needs to depend on the JPA API.

springuni-commons-jpa is a support module for being able to use a pre-configured combination of HikariCP and Hibernate as the entity manager without having to care about the details. It’s featuring
AbstractJpaConfiguration , which is similar to Spring Boot’s HibernateJpaAutoConfiguration.

The reason why I didn’t use the latter was that Spring Boot’s auto-configuration takes some to initialize. As one of my target platform’s is Google App Engine Standard Environment, fast start-up time is critical.

3. Unit testing repositories

Altought someone might argue that there’s not much to test on a repository, especially if you’re using Spring Data’s Repository interfaces , yet I think that a couple of runtime issues can be avoided by doing that. For example wrong entity mapping or wrong JPQL queries.

@RunWith(SpringJUnit4ClassRunner)
@ContextConfiguration(classes = [UserJpaTestConfiguration])
@Transactional
@Rollback
class UserJpaRepositoryTest {

  @Autowired
  UserRepository userRepository

  User user

  @Before
  void before() {
    user = new User(1, "test", "test@craftingjava.com")
    user.addConfirmationToken(ConfirmationTokenType.EMAIL, 10)
    userRepository.save(user)
  }

  ...

  @Test
  void testFindById() {
    Optional<User> userOptional = userRepository.findById(user.id)
    assertTrue(userOptional.isPresent())
  }

  ...

}

This test case starts up a Hibernate-powered entity manager with an embedded H2 database. H2 is very good for testing, because it supports compatibility modes with bunch of well-known databases, such as MySQL. This way it can mimic your real DB.

4. Next in this series

Building a user management microservice (Part 4): Implementing REST API