译见|构建用户管理微服务(三):实现和测试存储库

本期的译见,将带您继续前行:详细介绍一个完整的基于 JPA 的用户存储库实现,一个 JPA 的支撑模型和一些测试用例。

Misha&Boring 译  道客船长

译见

在上期的《译见|构建用户管理微服务(二):实现领域模型》中,有着相当数量的涉及到实现领域模型的编码,它们构成了用户注册过程中所需要的全部逻辑。在第三部分中,作者将带你继续前行:详细介绍一个完整的基于 JPA 的用户存储库实现,一个 JPA 的支撑模型和一些测试用例。

 

使用 XML 来映射简单的 JAVA 对象

仅看到用户存储库,也许你就能想到在对它添加基于 JPA 的实现时会遇到什么困难。

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); 

}

 

但是, 正如我在第一部分提到的, 我们将使用 DDD (域驱动设计), 因此, 在模型中就不能使用特定框架的依赖关系云 (包括 JPA 的注解) ,剩下的唯一可行性方法是用 XML 进行映射。如果我没有记错的话,自2010年以来,我再也没有接触过任何一个 orm.xml 的文件 , 这也就是我为什么开始怀念它的原因。

接下来我们看看XML文件中User的映射情况,以下是 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.springuni.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>

 

域驱动设计是一种持久化无关的方法,因此坚持设计一个没有具体目标数据结构的模型可能很有挑战性。当然, 它也存在优势, 即可对现实世界中的问题直接进行建模, 而不存在只能以某种方式使用某种技术栈之类的副作用。

public class User implements Entity<Long, User> {  

private Long id;  
private String screenName;  ...  

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

}

一般来说,一组简单的字符串或枚举值就能对用户的权限(或特权)进行建模了。

使用像 MongoDB 这样的文档数据库能够轻松自然地维护这个模型,如下所示。(顺便一提, 我还计划在本系列的后续内容中添加一个基于 Mongo 的存储库实现)

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

然而, 在关系模型中, 权限的概念必须作为用户的子关系进行处理。但是在现实世界中, 这仅仅只是一套权限规则。我们需要如何弥合这样的差距呢?

在 JPA 2.0 中可以引入 ElementCollection 来进行操作,它的用法类似于 OneToMany。在这种情况下, 已经配置好的 JPA 提供的程序 (Hibernate) 将自动生成必要的子关系。

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 包含了一个完整的基于 JPA 的 UserRepository 实现。其目标是, 每个模块都应该只拥有那些对它们的操作来说绝对必要的依赖关系,而这些关系只需要依赖 JPA API 便可以实现。

springuni-commons-jpa 是一个支撑模块, 它能够使用预先配置好的 HikariCP 和 Hibernate 的组合作为实体管理器, 而不必关心其他细节。 它的特色是 AbstractJpaConfiguration, 类似于 Spring Boot 的 HibernateJpaAutoConfiguration。

然而我没有使用后者的原因是 Spring Boot 的自动配置需要一定的初始化。因为谷歌应用引擎标准环境是我的目标平台之一,因此能否快速地启动是至关重要的。

单元测试存储库

虽然有人可能会说, 对于存储库没必要进行过多的测试, 尤其是在使用 Spring Data 的 存储库接口的时候。但是我认为测试代码可以避免运行时存在的一些问题,例如错误的实体映射或错误的 JPQL 查询。

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

  @Autowired
  UserRepository userRepository

  User user

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

  ...

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

  ...

}

这个测试用例启动了一个具有嵌入式 H2 数据库的实体管理器。H2 非常适合于测试, 因为它支持许多众所周知的数据库 (如 MySQL) 的兼容模式,可以模拟你的真实数据库。

下期预告:构建用户管理微服务(四):实现 REST 控制器

原文链接:https://www.springuni.com/user-management-microservice-part-3

DaoCloud 公司简介:「DaoCloud 道客」云原生领域的创新领导者,成立于 2014 年底,拥有自主知识产权的核心技术,致力于打造开放的云原生操作系统为企业数字化转型赋能。产品能力覆盖云原生应用的开发、交付、运维全生命周期,并提供公有云、私有云和混合云等多种交付方式。成立迄今,公司已在金融科技、先进制造、智能汽车、零售网点、城市大脑等多个领域深耕,标杆客户包括交通银行、浦发银行、上汽集团、东风汽车、海尔集团、屈臣氏、金拱门(麦当劳)等。目前,公司已完成了 D 轮超亿元融资,被誉为科技领域准独角兽企业。公司在北京、武汉、深圳、成都设立多家分公司及合资公司,总员工人数超过 400 人,是上海市高新技术企业、上海市“科技小巨人”企业和上海市“专精特新”企业,并入选了科创板培育企业名单。

未经允许不得转载:DaoCloud道客博客 » 译见|构建用户管理微服务(三):实现和测试存储库

申请试用