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.
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_
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.
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.
Building a user management microservice (Part 4): Implementing REST API
If you like Java and Spring as much as I do, sign up for my newsletter.