精华内容
下载资源
问答
  • repository

    2021-01-17 13:26:41
    全部仓库贮藏室博物馆Each XML document becomes a data repository that can be queried just like any other database.每个xml文档成了数据仓库,就像其他数据库一样能被查询.期刊摘选With this approach, a ...

    全部

    仓库

    贮藏室

    博物馆

    Each XML document becomes a data repository that can be queried just like any other database.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    每个xml文档成了数据仓库,就像其他数据库一样能被查询.

    期刊摘选

    With this approach, a repository path is sufficient to describe a location.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    这种方式下, 库路径就可以充分的表述位置了.

    期刊摘选

    Objects cannot be added to the Repository . It Is being edited by % s.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    对象不能被添加到仓库. 它在在被%s编辑.

    期刊摘选

    Submitting her changes to the repository is the operation we call " checkin "

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    提交她的变更到配置库中的操作就是我们所说的 “ 签入 ”

    期刊摘选

    The repository database on disk has barely increased in size.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    库只是在磁盘空间上增加了点.

    期刊摘选

    My diary is the repository of all my hopes and plans.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    我在日记中记载着我的一切希望和计画.

    辞典例句

    Subversion and Vault are examples of tools which use binary file deltas for repository storage.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    Subversion和Vault是使用了二进制文件增量的存储库的工具实例.

    期刊摘选

    For this reason the hydrogeological study is one of the main contents in repository siting.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    因此,水文地质工作是处置库选址中的主要内容之一.

    期刊摘选

    The provider does not support Schema Rowsets, so its metadata cannot be scanned into the Repository.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    提供程序不支持架构行集, 因此无法将它的元数据扫描到知识库中.

    期刊摘选

    I recommend that each transaction you check into the repository should correspond to one task.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    我建议每个你签入到配置库中的事务都映射到一个任务.

    期刊摘选

    Web offers an abundant and valuable information repository.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    Web提供了一个极其丰富而有价值的信息资源库.

    期刊摘选

    The cached relationship collection does not match the repository database.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    高速缓存关系集合与知识库数据库不匹配.

    期刊摘选

    We treat our repository with great respect.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    我们对我们的库要非常的尊敬.

    期刊摘选

    The warehouse was chiefly a repository for empty boxes.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    这仓库主要是放空箱子的贮藏室.

    期刊摘选

    A repository must store every version of every file.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    一个库必须存储任何文件的任何版本.

    期刊摘选

    Start a separate repository only in situations where the contents of the two are completely unrelated.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    建立一个分离的库仅仅是在两个项内容完全不相关的情况下.

    期刊摘选

    Harbin, capital of China's northernmost province Heilongjiang a repository of some amazing old Russian architecture.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    哈尔滨, 中国最北的省黑龙江的省会,以一些令人称奇的俄罗斯老建筑的陈列而著称.

    期刊摘选

    The design tooling web is a repository of knowledge about computation for designers.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    设计工具使用网络是,一个针对设计者使用运算于设计的知识宝库.

    期刊摘选

    They decided to reposition their furniture in a recommended repository in Brooklyn.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    他们决定将其家具改为存放在位于布鲁克林区的一个建议存储库中.

    期刊摘选

    Part of that is building a knowledge repository.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    其中一部分就是建立知识仓库.

    期刊摘选

    For a repository of sample conceptual problem questions, Project Galileo ( n . d . ) is particularly useful.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    伽利略计划特别适用于样品概念问题的存放.

    期刊摘选

    The book is a repository of curious information.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    这书里有许多奇事.

    辞典例句

    The lost manuscript was found in a repository in France.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    丢失的手稿在法国的一个存放处被找到了.

    《简明英汉词典》

    Direct linking to the repository URLs is prohibited, please only link to this post.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    直接链接到库的网址是禁止, 请连结到这个职位.

    期刊摘选

    However, most programming tasks require us to make multiple repository changes.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    然后, 大多数的开发工作需要我们进行多重的修改库.

    期刊摘选

    Users can access content conveniently by Internet protocol and publish content by XSL stencil repository.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    建立了良好的内容存储机制,能通过互联网协议方便地直接读取、创建内容;能对内容实现基于语义的搜索:并且可以使用模板库发布内容.

    期刊摘选

    She was a repository of all his secrets.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    她是他完全信赖的人.

    《现代英汉综合大词典》

    My father is a repository of family history.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    我的父亲对家族史无所不知。

    《牛津高阶英汉双解词典》

    The repository of all important knowledge in a small town was the chief barman of the local pub.

    jJmGwUBqEgek0CINuv0EYRIMGYaCfm4GJptR0kK+Nxh6G5Ovy07RmMTSi7tAwkimU1V8yBRUSWK7iaQAAAABJRU5ErkJggg==

    小镇上最无所不知的人就是当地酒吧里的服务员领班。

    柯林斯例句

    展开全文
  • DDD之Repository模式

    2021-05-02 17:18:17
    先定义一个基础的 Repository 基础接口类,以及一些Marker接口类: public interface Repository, ID extends Identifier> { /** * 将一个Aggregate附属到一个Repository,让它变为可追踪。 * Change-Tracking在...

    一,传统架构下的实体模型

    传统的应用架构,几乎就是根据需求设计数据库的表,根据表建立实体,对应着实体的就是DAO,Service,Controller,也就是传统的MVC三层架构。

    回顾下我们平时写的代码,里面有着很多的xxxUtils工具类,很多的参数校验逻辑与业务逻辑混杂在一起,很多的实体类直接与数据库进行一对一映射。

    好处很明显,在业务初期,开发起来很容易,相对比较简单,流水线式编码,但是,一旦后期需求变更,业务改造,数据库表发生变化,可能给我们带来毁灭性的负担。所谓牵一发而动全身,前面欠下了技术债,后面很难受,想补救工作量巨大,不补救,系统难以升级,难以扩展,灵活性急剧下降。

    对于第三方(包括但不限于数据库)的强依赖,导致我们在做业务扩展的时候,顾虑重重,缺少了一往无前的动力。

    但是截至目前,包括我所参与开发的项目,依然是采用这种模式,为什么?

    • 数据库思维:从有了数据库的那一天起,开发人员的思考方式就逐渐从“写业务逻辑“转变为了”写数据库逻辑”,也就是我们经常说的在写CRUD代码。
    • 所谓简单:贫血模型的优势在于“简单”,仅仅是对数据库表的字段映射,所以可以从前到后用统一格式串通。这里简单打了引号,是因为它只是表面上的简单,实际上当未来有模型变更时,你会发现其实并不简单,每次变更都是非常复杂的事情。
    • 脚本思维:很多常见的代码都属于“脚本”或“胶水代码”,也就是流程式代码。脚本代码的好处就是比较容易理解,但长久来看缺乏健壮性,维护成本会越来越高。

    两个概念,你是否明确?

    • 数据模型:也就是和数据库一一映射的类
    • 业务模型/领域模型:业务逻辑中,相关连的数据如何联动

    真实代码结构中,Data Model和 Domain Model实际上会分别在不同的层里,Data Model只存在于数据层,而Domain Model在领域层,而链接了这两层的关键对象,就是Repository。

    二,Repository的作用

    在传统的MVC三层架构中,我们操作数据库的层,一般叫做DAO,或者Mapper层。

    由于他与数据库直接耦合,导致了强依赖性。更可怕的是,由于我们都是在Service层直接注入Mapper层,导致了这种强依赖的传递,也就是整个应用体系开始变得越加依赖数据库DB。

    举一个例子

    public Interface UserDao{
        public List<User> selectUserByIds(List<Integer> ids);
    }
    
    public class UserService{
        
        @Resource
        private UserDao userDao;
    
        public List<User> getUserList(List<Integer> ids){
            return userDao.selectUserByIds(ids);
        }
        
    }
    

    这个代码,咋一看,简单明了,没有任何问题。但是假如现在由于数据量的增长和访问数量的增加,我需要引入缓存的逻辑,假如有十个地方调用了这个DAO中的方法,我需要在这十个地方都修改成:

    public class UserService{
        
        @Resource
        private UserDao userDao;
        @Resource
        private RedisTemplate redisTemplate;
    
        public List<User> getUserList(List<Integer> ids){
            List<User> users=redisTemplate.opsForValue().get(key);
            if(users!=null){
                return users;
            }else{
                List<User> userList=userDao.selectUserByIds(ids);
                redisTemplate.opsForValue().set(key,userList);
                return userList;
            }
            
        }
    
    }
    

    所以,需要一个逻辑,能够隔离业务逻辑与DB之间的传递强耦合关系,让我们的应用更加灵活,健壮,这个就是Repository的价值。

    三,模型对象代码规范

    1.什么是DO,DTO,Entity?

    • Data Object:DO的字段类型和名称应该和数据库物理表格的字段类型和名称一一对应,这样我们不需要去跑到数据库上去查一个字段的类型和名称。
    • Entity:实体对象是我们正常业务应该用的业务模型,它的字段和方法应该和业务语言保持一致,和持久化方式无关。也就是说,Entity和DO很可能有着完全不一样的字段命名和字段类型,甚至嵌套关系。Entity的生命周期应该仅存在于内存中,不需要可序列化和可持久化。
    • DTO:主要作为Application层的入参和出参,比如CQRS里的Command、Query、Event,以及Request、Response等都属于DTO的范畴。DTO的价值在于适配不同的业务场景的入参和出参,避免让业务对象变成一个万能大对象。

    2.对象之间的关系

    在实际开发中DO、Entity和DTO不一定是1:1:1的关系。一些常见的非1:1关系如下:

    复杂的实体拆分成多张数据库的表:常见的原因,字段多,查询性能差,需要将非检索、大字段等单独存为一张表,提升基础信息表的检索效率。

    当然,除了一些数据查询频繁,聚合性非常强的表。

    拆分的实体:我接触过的,订单,商品,购物车。

    3.模型所在模块和转化器

    由于现在从一个对象变为3+个对象,对象间需要通过转化器(Converter/Mapper)来互相转化。而这三种对象在代码中所在的位置也不一样,简单总结如下:

    在这里插入图片描述

    DTO Assembler:在Application层,Entity到DTO的转化器有一个标准的名称叫DTO Assembler。Martin Fowler在P of EAA一书里对于DTO 和 Assembler的描述:Data Transfer Object。DTO Assembler的核心作用就是将1个或多个相关联的Entity转化为1个或多个DTO。

    Data Converter:在Infrastructure层,Entity到DO的转化器没有一个标准名称,但是为了区分Data Mapper,我们叫这种转化器Data Converter。这里要注意Data Mapper通常情况下指的是DAO,比如Mybatis的Mapper。Data Mapper的出处也在P of EAA一书里:Data Mapper

    如果是手写一个Assembler,通常我们会去实现2种类型的方法,如下;Data Converter的逻辑和此类似,略过。

    public class DtoAssembler {    
        // 通过各种实体,生成DTO    
        public OrderDTO toDTO(Order order, Item item) {        
            OrderDTO dto = new OrderDTO();        
            dto.setId(order.getId());        
            dto.setItemTitle(item.getTitle()); // 从多个对象里取值,且字段名称不一样      
            dto.setDetailAddress(order.getAddress.getDetail());
            // 可以读取复杂嵌套字段        
            // 省略N行        
            return dto;    
        }
        // 通过DTO,生成实体    
        public Item toEntity(ItemDTO itemDTO) {        
            
            Item entity = new Item();        
            entity.setId(itemDTO.getId());        // 省略N行        
            return entity;    
        }
    } 
    

    我们能看出来通过抽象出一个Assembler/Converter对象,我们能把复杂的转化逻辑都收敛到一个对象中,并且可以很好的单元测试。这个也很好的收敛了常见代码里的转化逻辑。

    在调用方使用时是非常方便的:

    public class Application { 
        
        private DtoAssembler assembler;    
        private OrderRepository orderRepository;    
        private ItemRepository itemRepository;
        
        public OrderDTO getOrderDetail(Long orderId) {        
            Order order = orderRepository.find(orderId);        
            Item item = itemRepository.find(order.getItemId());        
            return assembler.toDTO(order, item); // 原来的很多复杂转化逻辑都收敛到一行代码了    
        }
    }
    

    4.模型规范总结

    在这里插入图片描述

    从使用复杂度角度来看,区分了DO、Entity、DTO带来了代码量的膨胀(从1个变成了3+2+N个)。但是在实际复杂业务场景下,通过功能来区分模型带来的价值是功能性的单一和可测试、可预期,最终反而是逻辑复杂性的降低。

    四,Repository代码规范

    1.接口规范

    • 接口名称不应该使用底层实现的语法:我们常见的insert、select、update、delete都属于SQL语法,使用这几个词相当于和DB底层实现做了绑定。相反,我们应该把 Repository 当成一个中性的类 似Collection 的接口,使用语法如 find、save、remove。在这里特别需要指出的是区分 insert/add 和 update 本身也是一种和底层强绑定的逻辑,一些储存如缓存实际上不存在insert和update的差异,在这个 case 里,使用中性的 save 接口,然后在具体实现上根据情况调用 DAO 的 insert 或 update 接口。

    • 出参入参不应该使用底层数据格式:需要记得的是 Repository 操作的是 Entity 对象(实际上应该是Aggregate Root),而不应该直接操作底层的 DO 。更近一步,Repository 接口实际上应该存在于Domain层,根本看不到 DO 的实现。这个也是为了避免底层实现逻辑渗透到业务代码中的强保障。

    • 应该避免所谓的“通用”Repository模式:很多 ORM 框架都提供一个“通用”的Repository接口,然后框架通过注解自动实现接口,比较典型的例子是Spring Data、Entity Framework等,这种框架的好处是在简单场景下很容易通过配置实现,但是坏处是基本上无扩展的可能性(比如加定制缓存逻辑),在未来有可能还是会被推翻重做。当然,这里避免通用不代表不能有基础接口和通用的帮助类。

    先定义一个基础的 Repository 基础接口类,以及一些Marker接口类:

    public interface Repository<T extends Aggregate<ID>, ID extends Identifier> {
        /**     
        * 将一个Aggregate附属到一个Repository,让它变为可追踪。     
        * Change-Tracking在下文会讲,非必须     
        */    
        void attach(@NotNull T aggregate);
        /**     
        * 解除一个Aggregate的追踪     
        * Change-Tracking在下文会讲,非必须     
        */    
        void detach(@NotNull T aggregate);
        /**     
        * 通过ID寻找Aggregate。     
        * 找到的Aggregate自动是可追踪的     
        */    
        T find(@NotNull ID id);
        /**     
        * 将一个Aggregate从Repository移除     
        * 操作后的aggregate对象自动取消追踪     
        */    
        void remove(@NotNull T aggregate);
        /**     
        * 保存一个Aggregate     
        * 保存后自动重置追踪条件     
        */    
        void save(@NotNull T aggregate);
    }
    // 聚合根的Marker接口
    public interface Aggregate<ID extends Identifier> extends Entity<ID> {
    }
    // 实体类的Marker接口
    public interface Entity<ID extends Identifier> extends Identifiable<ID> {
    }
    public interface Identifiable<ID extends Identifier> {    
        ID getId();
    }
    // ID类型DP的Marker接口
    public interface Identifier extends Serializable {
    }
     
    

    业务自己的接口只需要在基础接口上进行扩展,举个订单的例子:

    // 代码在Domain层
    public interface OrderRepository extends Repository<Order, OrderId> {
        // 自定义Count接口,在这里OrderQuery是一个自定义的DTO    
        Long count(OrderQuery query);
        // 自定义分页查询接口    
        Page<Order> query(OrderQuery query);
        // 自定义有多个条件的查询接口    
        Order findInStore(OrderId id, StoreId storeId);
    }
    

    每个业务需要根据自己的业务场景来定义各种查询逻辑。

    这里需要再次强调的是Repository的接口是在Domain层,但是实现类是在Infrastructure层。

    2.Repository基础实现

    先举个Repository的最简单实现的例子。注意OrderRepositoryImpl在Infrastructure层:

    // 代码在Infrastructure层
    @Repository // Spring的注解
    public class OrderRepositoryImpl implements OrderRepository {    
        private final OrderDAO dao; // 具体的DAO接口    
        private final OrderDataConverter converter; // 转化器
        public OrderRepositoryImpl(OrderDAO dao) {        
            this.dao = dao;        
            this.converter = OrderDataConverter.INSTANCE;    
        }
        @Override    
        public Order find(OrderId orderId) {        
            OrderDO orderDO = dao.findById(orderId.getValue());        
            return converter.fromData(orderDO);    
        }
        @Override    
        public void remove(Order aggregate) {        
            OrderDO orderDO = converter.toData(aggregate);        
            dao.delete(orderDO);    
        }
        @Override    
        public void save(Order aggregate) {        
            if (aggregate.getId() != null && aggregate.getId().getValue() > 0) {            // update            
                OrderDO orderDO = converter.toData(aggregate);            
                dao.update(orderDO);        
            } else {            // insert            
                OrderDO orderDO = converter.toData(aggregate);            
                dao.insert(orderDO);            
                aggregate.setId(converter.fromData(orderDO).getId());        
            }    
        }
        @Override    
        public Page<Order> query(OrderQuery query) {        
            List<OrderDO> orderDOS = dao.queryPaged(query);        
            long count = dao.count(query);        
            List<Order> result = orderDOS.stream().map(converter::fromData).collect(Collectors.toList());        
            return Page.with(result, query, count);    
        }
        @Override    
        public Order findInStore(OrderId id, StoreId storeId) {        
            OrderDO orderDO = dao.findInStore(id.getValue(), storeId.getValue());        
            return converter.fromData(orderDO);    
        }
    }
    

    从上面的实现能看出来一些套路:所有的Entity/Aggregate会被转化为DO,然后根据业务场景,调用相应的DAO方法进行操作,事后如果需要则把DO转换回Entity。代码基本很简单,唯一需要注意的是save方法,需要根据Aggregate的ID是否存在且大于0来判断一个Aggregate是否需要更新还是插入。

    3.Repository复杂实现

    针对单一Entity的Repository实现一般比较简单,但是当涉及到多Entity的Aggregate Root时,就会比较麻烦,最主要的原因是在一次操作中,并不是所有Aggregate里的Entity都需要变更,但是如果用简单的写法,会导致大量的无用DB操作。

    举一个常见的例子,在主子订单的场景下,一个主订单Order会包含多个子订单LineItem,假设有个改某个子订单价格的操作,会同时改变主订单价格,但是对其他子订单无影响:
    在这里插入图片描述

    如果用一个非常naive的实现来完成,会导致多出来两个无用的更新操作,如下:

    public class OrderRepositoryImpl extends implements OrderRepository {    
        private OrderDAO orderDAO;    
        private LineItemDAO lineItemDAO;    
        private OrderDataConverter orderConverter;    
        private LineItemDataConverter lineItemConverter;
        // 其他逻辑省略
        @Override    
        public void save(Order aggregate) {        
            if (aggregate.getId() != null && aggregate.getId().getValue() > 0) {            // 每次都将Order和所有LineItem全量更新 
                OrderDO orderDO = orderConverter.toData(aggregate);           
                orderDAO.update(orderDO);            
                for (LineItem lineItem: aggregate.getLineItems()) {                
                    save(lineItem);            
                }        
            } else {            
                // 插入逻辑省略       
            }    
        }
        private void save(LineItem lineItem) {        
            if (lineItem.getId() != null && lineItem.getId().getValue() > 0) {            
                LineItemDO lineItemDO = lineItemConverter.toData(lineItem);            
                lineItemDAO.update(lineItemDO);        
            } else {            
                LineItemDO lineItemDO = lineItemConverter.toData(lineItem);            
                lineItemDAO.insert(lineItemDO);            
                lineItem.setId(lineItemConverter.fromData(lineItemDO).getId());        
            }    
        }
    }
    

    在这个情况下,会导致4个UPDATE操作,但实际上只需要2个。在绝大部分情况下,这个成本不高,可以接受,但是在极端情况下(当非Aggregate Root的Entity非常多时),会导致大量的无用写操作。

    五,Repository迁移路径

    在我们日常的代码中,使用Repository模式是一个很简单,但是又能得到很多收益的事情。最大的收益就是可以彻底和底层实现解耦,让上层业务可以快速自发展。

    我们假设现有的传统代码包含了以下几个类(还是用订单举例):

    • OrderDO
    • OrderDAO

    可以通过以下几个步骤逐渐的实现Repository模式:

    • 生成Order实体类,初期字段可以和OrderDO保持一致

    • 生成OrderDataConverter,通过MapStruct基本上2行代码就能完成

    • 写单元测试,确保Order和OrderDO之间的转化100%正确

    • 生成OrderRepository接口和实现,通过单测确保OrderRepository的正确性

    • 将原有代码里使用了OrderDO的地方改为Order

    • 将原有代码里使用了OrderDAO的地方都改为用OrderRepository

    • 通过单测确保业务逻辑的一致性。

    展开全文
  • Repository模式

    2021-04-23 13:16:54
    [TOC]# 简介Repository设计模式相当于在控制器层和model层中加了一层,这层是仓库的意思,存放model层中的一些数据![](https://box.kancloud.cn/3bec61da15ecac77370dbeb84102a4b2_1476x478.jpg)# 使用![]...

    [TOC]

    # 简介

    Repository设计模式相当于在控制器层和model层中加了一层,这层是仓库的意思,存放model层中的一些数据

    ![](https://box.kancloud.cn/3bec61da15ecac77370dbeb84102a4b2_1476x478.jpg)

    # 使用

    ![](https://box.kancloud.cn/0bda17b76532c7d366d2da462a7471d1_480x1030.jpg)

    建立Repository目录来存放不同的业务逻辑

    在Contracts中存放接口文件,Eloquent中存放具体的实现方法

    TestRepository.php

    ~~~

    namespace App\Repository\Test\Eloquent;

    use App\Repository\Test\Contracts\TestRepositoryInterface;

    class TestRepository implements TestRepositoryInterface

    {

    public function test()

    {

    return 'this is test repository';

    }

    }

    ~~~

    TestController.php

    ~~~

    namespace App\Http\Controllers;

    use App\Http\Requests;

    use Illuminate\Http\Request;

    class TestController extends Controller

    {

    public function test()

    {

    $test = app('Test');

    $testInfo = $test->test();

    echo $testInfo;

    }

    }

    ~~~

    编写服务提供者

    执行`php artisan make:provider RepositoryServiceProvider `

    编写你自己的服务提供者

    `app\Providers\RepositoryServiceProvider.php`注册Test仓库

    ~~~

    public function register()

    {

    $this->registerTestRepository();

    }

    public function provides()

    {

    $test = ['Test'];

    return array_merge($test);

    }

    private function registerTestRepository()

    {

    $this->app->singleton('Test', 'App\Repository\Test\Eloquent\TestRepository');

    }

    ~~~

    在app.php的providers数组中添加我们的服务提供者

    ~~~

    'providers' => [

    App\Providers\RepositoryServiceProvider::class,

    ]

    ~~~

    # 总结

    通过这种方式我们可以将不同的业务逻辑分别创建一个仓库用来存放具体的方法,而在controller层只管调用不用去管具体的实现,也减少了controller层总对数据库的操作,使代码更加规范简洁。

    展开全文
  • 但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的,也是我本人之前有一些疑问的地方就是Repository。我之前觉得IRepository和三层里面的IDAL很像,为什么要整出这么个...

    概述

    上一篇我们算是粗略的介绍了一下DDD,我们提到了实体、值类型和领域服务,也稍微讲到了DDD中的分层结构。但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的,也是我本人之前有一些疑问的地方就是Repository。我之前觉得IRepository和三层里面的IDAL很像,为什么要整出这么个东西来;有人说用EF的话就不需要Repository了;IRepository是鸡肋等等。 我觉得这些问题都很好,我自己也觉得有问题,带着这些问题我们就来看一看Repository在DDD中到底起着一个什么样的角色,它为什么存在?有一句真理不是说“存在即合理”么? 那我们就要找到它存在的理由,去更好的理解它,或者说我们能不能针对不同的需求去改造它呢?注:本文讨论的是Repository在DDD中的应用,与EF该不该用Repoistory不是同一个话题。

    领域驱动系列

    目录

    EF与Repository

    在上一篇《初探领域驱动设计(1)为复杂业务而生》中,我们已经实现了一个用户注册的例子,但是并不完整。我们还没有具体的实现Repository,即使是在测试的时候我们使用的也是一个Mock。那么今天,我们就来实现一个EntityFramework的Repository。有人说EF没有必要套一个Repository,我是同意的。但是不同的场景,不同的使用方法,我们下面再具体讲。我们在上一篇中已经提到了IRepository的接口定义,下面是我们的简单实现:

    // EFRepository.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Data2 {3 public class EfRepository : IRepository whereT : BaseEntity4 {5 privateDbContext _context;6

    7 publicEfRepository(DbContext context)8 {9 if (context == null)10 {11 throw new ArgumentNullException("context");12 }13 _context =context;14 }15

    16 publicT GetById(Guid id)17 {18 return _context.Set().Find(id);19 }20

    21 public boolInsert(T entity)22 {23 _context.Set().Add(entity);24 _context.SaveChanges();25 return true;26 }27 public boolUpdate(T entity)28 {29 _context.Set().Attach(entity);30 _context.Entry(entity).State =EntityState.Modified;31 _context.SaveChanges();32 return true;33 }34

    35 public boolDelete(T entity)36 {37 _context.Set().Remove(entity);38 _context.SaveChanges();39 return true;40 }41

    42

    43 public IEnumerable Get(Expression>predicate)44 {45 return _context.Set().Where(predicate).ToList();46 }47 }48 }

    View Code

    // 应用层UserService.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 public classUserService : IUserService2 {3 private IRepository_userRepository;4

    5 public UserService(IRepositoryuserRepository)6 {7 _userRepository =userRepository;8 }9

    10 public User Register(string email, string name, stringpassword)11 {12 var domainUserService = newDomain.UserService(_userRepository);13 var user =domainUserService.Register(email, name, password);14 returnuser;15 }16 }

    View Code

    // 领域层UserService.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Domain2 {3 public classUserService4 {5 private IRepository_userRepository;6

    7 public UserService(IRepositoryuserRepsoitory)8 {9 _userRepository =userRepsoitory;10 }11

    12 public virtual User Register(string email, string name, stringpassword)13 {14 if (_userRepository.Get().Any(u => u.Email ==email))15 {16 throw new ArgumentException("The email is already taken");17 }18

    19 var user = newUser20 {21 Id =Guid.NewGuid(),22 Email =email,23 Name =name,24 Password =password25 };26

    27 user.CreateShoppingCart();28 _userRepository.Insert(user);29 returnuser;30 }31 }32 }

    View Code

    9d9f2f140f90a73a5acf3c0f8b6b75e6.png  

    上面领域层UserService中的代码和我们上一篇中的代码是一样的,netfocus兄提出来一个问题“是不是把user对象加入到repository中就算完成注册了?” 现在看来,如果代码这样写,好像就已经完成了注册的功能。 但是如果真这样写,我又觉得问题更大,也就是为什么我会在上篇的未必留下那个问题,“Domain -> Repository -> Database” 和“BLL -> Dal -> Database” 有区别么?撇开这个问题不说,看看我们上面的EfRepository有没有什么问题? 好用么?现在好像没有办法使用事务啊!带着这个问题我们来看看Unit Of Work能怎么帮我们。

    Unit Of Work 与 Repository

    我们EfRepository的实现中,每一次Insert/Update/Delete操作被执行之后,变更就会立即同步到数据库中去。第一,我们没有为多个操作添加一个事务的能力;第二,这会为我们带来性能上的损失。而Unit Of Work模式正好解决了我们的问题,下面是Martin Fowler 对于该模式的解释:

    “A Unit of Work keep track of everything you do during a business transaction that can affect the database. When you’re done, it figures out everything that need to be done to alter the database as a result of your work.”

    Unit of Work负责跟踪所有业务事务过程中数据库的变更。当事务完成之后,它找出需要处理的变更,并更新数据库。

    正如我们大家一直讨论的那样,在EF中,DBContext它本身就已经是一个Unit Of Work的模式,因为上面说的功能它都有。那我们有必要自己再给它包上一层吗?我的答案是肯定的,这个和我们为Repository建立接口是一样的,EF中的IDbSet就是一个Repository模式,但是他们都是EF里面的东西,如果哪天我们换成NHibernate了,我们不可能为了这一个接口和基类把EF这个dll也加进来是么? 我们要做的并不多,因为DbContext.SaveChanges它本身就是有事务的,所以我们只需要创建一个带有SaveChanges的接口就可以了。

    // IUnitOfWork.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Core.Data2 {3 public interfaceIUnitOfWork : IDisposable4 {5 intSaveChanges();6 }7 }

    View Code

    接着就是让我们的Context,继承DbContex和我们上面的接口。

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Data2 {3 public classRepositoryAndEfContext : DbContext, IUnitOfWork4 {5 publicRepositoryAndEfContext() { }6

    7 public RepositoryAndEfContext(stringnameOrConnectionString)8 : base(nameOrConnectionString)9 {10 Configuration.LazyLoadingEnabled = true;11 }12

    13 protected override voidOnModelCreating(DbModelBuilder modelBuilder)14 {15 var typesToRegister =Assembly.GetExecutingAssembly().GetTypes()16 .Where(type => !String.IsNullOrEmpty(type.Namespace))17 .Where(type => type.BaseType != null

    18 &&type.BaseType.IsGenericType19 && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));20

    21 foreach (var type intypesToRegister)22 {23 dynamic configurationInstance =Activator.CreateInstance(type);24 modelBuilder.Configurations.Add(configurationInstance);25 }26 //...or do it manually below. For example,27 //modelBuilder.Configurations.Add(new LanguageMap());

    28

    29 base.OnModelCreating(modelBuilder);30 }31 }32 }

    View Code

    哦,对了,别忘了把Repository里面的SaveChanges方法去掉。

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Data2 {3 public class EfRepository : IRepository whereT : BaseEntity4 {5 privateDbContext _context;6

    7 publicEfRepository(IUnitOfWork uow)8 {9 if (uow == null)10 {11 throw new ArgumentNullException("uow");12 }13 _context = uow asDbContext;14 }15

    16 publicT GetById(Guid id)17 {18 return _context.Set().Find(id);19 }20

    21 public boolInsert(T entity)22 {23 _context.Set().Add(entity);24 return true;25 }26

    27 public boolUpdate(T entity)28 {29 _context.Set().Attach(entity);30 _context.Entry(entity).State =EntityState.Modified;31 return true;32 }33

    34 public boolDelete(T entity)35 {36 _context.Set().Remove(entity);37 return true;38 }39

    40

    41 public IEnumerable Get(Expression>predicate)42 {43 return _context.Set().Where(predicate).ToList();44 }45 }46 }

    View Code

    那么我们应用层的UserService就可以这样写了。

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    namespaceRepositoryAndEf.Service

    {public classUserService : IUserService

    {private IRepository_userRepository;private IUnitOfWork _uow =EngineContext.Current.Resolve();public UserService(IRepositoryuserRepository)

    {

    _userRepository=userRepository;

    }public User Register(string email, string name, stringpassword)

    {var domainUserService = newDomain.UserService(_userRepository);var user =domainUserService.Register(email, name, password);//在调用SaveChnages()之前,做其它的更新操作//它们会一起在同一个事务中执行。

    _uow.SaveChanges();returnuser;

    }

    }

    }

    View Code

    如果光看这段代码有没有觉得很奇怪?没有任何对_userRepository的操作,就做了SaveChanges,因为我们在领域服务里面就已经把新创建的用户实体放到那个userRepository中去了。我想这个问题@田园的蟋蟀纠结过很久:) ,也就是领域服务那里面持有repository的引用,它可以自己将要更新的实体添加到repository中,但是如果对于一些不涉及到领域服务的操作,那这一点就需要在应用层来做了,比如添加商品到购物车的操作。

    // 应用层ShoppingCartService.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Service2 {3 public classShoppingCartService : IShoppingCartService4 {5 private IRepository_shoppingCartRepository;6 private IRepository_productRepository;7 privateIUnitOfWork _uow;8

    9 publicShoppingCartService(IUnitOfWork uow,10 IRepositoryshoppingCartRepository,11 IRepositoryproductRepository)12 {13 _uow =uow;14 _shoppingCartRepository =shoppingCartRepository;15 _productRepository =productRepository;16 }17

    18 publicShoppingCart AddToCart(Guid cartId,19 Guid productId,20 intquantity)21 {22 var cart =_shoppingCartRepository.GetById(cartId);23 var product =_productRepository.GetById(productId);24 cart.AddItem(product, quantity);25

    26 _shoppingCartRepository.Update(cart);27 _uow.SaveChanges();28 returncart;29 }30 }31 }

    View Code

    f62180b2674330ca1a6110dd2fc9d99a.png  这就是属于职责定义不明确的问题,特别是上面注册用户的例子。应用层也有_userRepository,并且领域服务还给我返回了一个user的实体,那我是把它加到这个_userRepository中呢还是不加好呢?

    我觉得我们应该有这样的一个定义,在领域层那里不使用repository的更新类操作(即Insert/Update/Delete),只使用查询类操作即(GetById,或者是Get)。把所有的更新类操作都放到应用层,这样由应用层去决定什么时候把实体更新到repository,以及什么时候去提交到数据库中。那我们就彻底与持久层,甚至领域实体生命期管理的功能撇开有关系了,从此用更OO的方式专注于业务。

    后面我们要做的更改就是把_userRepository.Insert(user)从我们User的领域服务中移除掉,并且在应用层的Register方法中加入这句话。 我想到这里,也算是回答了我自己的问题: IRepository正如它的名字一样,它就像一个容器,允许我们把东西放进去或者取出来,它离真正的数据库还有一步之遥,并且通过Unit Of Work,把对事务以及持久化的控制都交到了外面。而不是像DAL那样直接就反映到数据库中去了。除此之外呢?IRepository解除了领域层对基础设施层的依懒,这个也是大家经常提到了Repository的优点之一。但是未必这一点一定非得需要IRepository,把IDAL接口移个位置同样也可以实现,不信您看看洋葱架构。

    洋葱架构与IRepository

    洋葱架构很早就有,只不过08年的时候Jeffery给它取了个名字,让它成为了一个模式。说起来好像很高大上,但是希望大家不要被这些名字所迷惑,所正如Jeffery所说,在这种设计有了一个名字之后,更方便大家去讨论和传播以及使用这种模式。 并且洋葱架构也是一种多层架构,所以会出现“传统” 的多层架构 和“现代”的多层架构。 我更是认为,所谓的洋葱架构只是作出了一点点思想层面上的转变,仅此而已。 究竟是哪一点思想上的转变,可以让它成为一种模式呢? 依懒关系!

    1bdb88f34fd25f7efed714849a5e460f.png  Jeffery说在传统的多层架构中,上层对下层有着较强的依懒关系,UI没了BLL就没法工作,BLL少了DAL也无法正常运行。当然他说这句话的时候是08年,并且他的确是在前面加了“传统” 两个字。 我们很难找到到底是什么时候,这种传统的多层架构演变成了“现代” 的多层架构,但是我们能知道的是在08年7月以后我们对于多层架构又有了一个新的名词。即便如此,它的转变却是非常简单的 —— 也就是把IDAL接口从DAL层分离出去。

    如果把IDAL接口定义在DataAccess层,第一是造成了BLL对DataAccess的依懒;第二是造成了IDAL的责任不明确。如果说小A负责开发BLL,小C负责开发DAL,他们是不是需要协调该怎么样去定义IDAL接口? 是DAL为BLL服务,还是BLL的最终目地是把自己移交给DAL? 在最开始的时候,大家对IDAL的定义是为了支持不同的访问层设计,大家想的都是现在我们用SQL,将来有可能会有MySql。所以IDAL放在哪里也就无所谓了,为了方便就直接和实现一起放在DAL吧。

    把IDAL接口从DAL移出去之后会发生什么 ?

    f1aa5ddefed302a4bfce02dc3b31fb83.png  在把IDAL接口移到BLL层之后,箭头的方向就变了。现在一切都是以BLL为中心,BLL也不需要依懒于任何其它层了,作为独立的一块,我们可以更容易的进行单元测试,重构等。另外也明确了IDAL是为BLL服务的,也就是解决了我们上面提到的第二个问题。

    这个一个很简单的转变就是洋葱架构的主要思想,如果你还不能很好的领悟洋葱架构和传统多层架构之间的区别,希望下面这张图能用最直接,最简单的方式告诉你。

    传统多层架构与现代(洋葱架构)多层架构的区别

    634024f2eb2c14fdc08ee049a7ea99db.png

    你要是愿意,把IDAL直接放到Bll里面也是可以的。当Jeffery给这种架构起名叫“洋葱架构”再往前推4年,DDD问世的时候已经包含了这种思想。IRepository属于领域层而非基础架构层中的数据访问模块,就直接避免了领域层对基础设施层的依懒,或者说不定这种思想也是从DDD引申出来的,所以你会发现很多人现在依然用DAL。但是并没有什么问题,因为在这种新的多层架构下,扩展性和可维护性同样也可以被保持的很好。

    重新定义IRepository

    现在,我们再回过头去看Repository。它的两大职责:

    对领域实体的生命周期进行管理(从数据库重建,以及持久化到数据库)  ——被推迟到了应用层

    解除领域层对基础设施的依懒

    在第一点生效后,所有更新类的操作都推迟到应用层去执行。那IRepository中的那些更新类方法放在领域层是不是就多余了呢? 毕竟我们现在只需要用到查询的功能。我们可以单独建一个IQuery的接口给领域层使用。

    // IQuery.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Core.Data2 {3 public interface IQuery

    4 {5 T GetById(Guid id);6 IQueryable Table { get; }7 }8 }

    View Code

    // IRepository.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Core.Data2 {3 public partial interface IRepository:4 IQuery whereT : BaseEntity5 {6 boolInsert(T entity);7 boolUpdate(T entity);8 boolDelete(T entity);9 }10 }

    View Code

    我们直接让IRepository继承了IQuery,IQuery就相当于IRepository的一个功能子集,只提供读的功能。 而在EfRepository中,我们只要暴露DbSet.AsQueryAble()就可以了。

    // EfRepository IQuery的实体部分

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 publicT GetById(Guid id)2 {3 return _context.Set().Find(id);4 }5

    6 public IQueryableTable7 {8 get

    9 {10 return _context.Set().AsQueryable();11 }12 }

    View Code

    // 领域层 UserService.cs

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 namespaceRepositoryAndEf.Domain2 {3 public classUserService4 {5 private IQuery_userQuery;6

    7 public UserService(IQueryuserQuery)8 {9 _userQuery =userQuery;10 }11

    12 public virtual User Register(string email, string name, stringpassword)13 {14 if (_userQuery.Table.Any(u => u.Email ==email))15 {16 throw new ArgumentException("The email is already taken");17 }18

    19 var user = newUser20 {21 Id =Guid.NewGuid(),22 Email =email,23 Name =name,24 Password =password25 };26

    27 user.CreateShoppingCart();28 returnuser;29 }30 }31 }

    View Code

    // 客户端调用应用层Service代码

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 var uow = new RepositoryAndEfContext("ConnStr");2 var userRepository = new EfRepository(uow);3 var userService = newUserService(uow, userRepository);4 var newUser =userService.Register(5 "hellojesseliu@outlook.com",6 "Jesse Liu",7 "jesseliu");

    View Code

    现在,恐怕你再想在领域模型里面去使用Repository的更新类操作也不行了吧。 Table作为IQueryable返回,那我们想怎么查就随意了。因为是IQueryable,所以也是只会返回我们所查询的内容,和直接用EF查询是一个道理。下面是我们_userQuery.Table.Any()所生成的SQL语句。

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 exec sp_executesql N'SELECT2 CASE WHEN ( EXISTS (SELECT3 1 AS [C1]4 FROM [dbo].[Users] AS [Extent1]5 WHERE ([Extent1].[Email] = @p__linq__0) OR (([Extent1].[Email] IS NULL) AND (@p__linq__0 IS NULL))6 )) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]7 FROM ( SELECT 1 AS X ) AS [SingleRowTable1]',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'hellojesseliu@outlook.com'

    View Code

    可有可无的Repository

    我们把IRepository移出领域层之后,再加上我们对洋葱架构的理解。我们就可以知道Repository在应用层已经可以被替换成别的东西,IDAL也可以啊:)。当然有人也许会建议直接拿EF来用多好,其实我不建议这样去做,考虑到以后把EF换掉的可能性。并且我们加这样一个接口真的不会碍着我们什么事。如果有人觉得在读取数据的时候加一个Repository在中间,少掉了很多EF提供的功能,觉得很不爽,倒是可以试试像我们的IQuery接口一样直接对DbSet来查询。我们甚至可以学习CQRS架构,将“读”的服务完全分离开,我们就可以单独针对“读”来独立设计。

    但是Repository给我们带来的优点,这些优点也是我们不能轻易丢掉它的原因:

    提供一个简单的模型,来获取持久对象并管理期生命周期

    把应用和领域设计从持久技术、多种数据库策略解耦出来

    容易被替换成哑实现(Mock)以便我们在测试中使用

    如果你的项目属于短期的项目,或者说你不用考虑更换数据访问层,那么你就可以忽略第一和第二个优点。而第三个优点,借助于一些测试框架我们也可以实现,所以如果你不想用Repository,那就不用,前提条件是你所做的项目允许你这样做,并且你也能够找到好的替代方案来弥补Repository的优势。比如说对洋葱架构中的IDAL再进行一些改造等等。关于更多单元测试的话题,我们将在下一篇中一起来探讨。如果大家对Repository有什么其它的看法,也欢迎一起参与讨论。

    展开全文
  • 为什么要用 Repository 实体模型 vs. 贫血模型 Entity(实体)这个词在计算机领域的最初应用可能是来自于Peter Chen在1976年的“The Entity-Relationship Model - Toward a Unified View of Data"(ER模型),用来...
  • @Repository 注解

    2021-03-22 12:31:59
    官网引用引用spring的官方文档中的一段描述:在Spring2.0之前的版本中,在Spring2.5版本中,引入...而@Repository,@Service,@Controller就是针对不同的使用场景所采取的特定功能化的注解组件。因此,当你的一个类被@...
  • 对于JPA自带的repository有时候会觉得不太满意,可以写一些自己的工具进去,以下对jpa的repository进行自定义扩展 1、进行扩展之后的Repository层 其中MyRepository为自定义的工具 package ...
  • BaseRepository 用法指南

    2021-08-07 13:50:33
    8-7 BaseRepository API 本文将通过讲解+测试来一一列举该JPA的用法与特点。并在结尾附上API。 一、使用流程 在理解使用的每一步意义之前,我们先来了解一下整体使用过程。 本章节将通过描述对Article类的查询来...
  • 把数据存储到es中,有两种方式一种是ElasticsearchRepository 接口,另一种是ElasticsearchTemplate接口,今天我们主要分析ElasticsearchRepository接口。 一、ElasticsearchRepository 原理分析: 1、首先...
  • 基于ElasticsearchRepository进行简单封装封装用到的2个自定义类repository层service层service实现类使用时注意 封装用到的2个自定义类 public class PageQuery implements Serializable { private static final ...
  • Repository 模式为了保持代码的整洁性和可读性,使用Repository Pattern 是非常有用的。事实上,我们也不必仅仅为了使用这个特别的设计模式去使用Laravel,然而在下面的场景下,我们将使用OOP的框架Laravel 去展示...
  • 1. JpaRepository简单查询 基本查询也分为两种,一种是spring data默认已经实现,一种是根据查询的方法来自动解析成SQL。 预先生成方法 spring data jpa 默认预先生成了一些基本的CURD的方法,例如:增、删、...
  • 两者在分布式、微服务架构中使用率极高,本文将用实例介绍如何在Springboot中整合MongoDB的两种方法:MongoRepository和MongoTemplate。 1.MongoRepository 以Spring Date为中心的方法,基于所有Spring数据项目中...
  • 它可以同时配置多个仓库或叫资源库(repository),就是存放更新和依存的软件包的地方。在安装Red Hat Enterprise Linux过程中,默认是不安装所有软件包的,等到我们需要时再手动安装。通常有两种方法:1.通过rpm...
  • 在Linux系统的维护中,Linux软件包之间的依赖性是一件令人十分头痛的事情。比如你要安装软件包A,但是安装的时候提示你在安装A前得先安装软件包B...yum工具能根据repository(软件仓库)中rpm包的各种header信息,自动...
  • 介绍think-repository 是为 thinkphp 6.0.* 提供的存储库用于抽象数据层,使我们的应用程序更灵活地进行维护。你懂的ThinkPHP>= thinkphp 6.0.*安装教程Composercomposer require fanxd/think-repository dev-...
  • import org.springframework.stereotype.Repository; //导入依赖的package包/类private Map findMyBatisBeanDefinitions() {String[] enumsLocations = attributes.getStringArray(Constant.ENUMS_LOCATIONS_...
  • 基于EFcore 实现基本的数据库操作 0.代码关系与结构如图所示: 存在两个数据表,分别是UserInfo表、...创建RepositoryBase:IRepository 实现基本数据库操作 c.分别创建IUserInfoRepository、IContactRepository d
  • How To enable the EPEL Repository on RHEL 8 / CentOS 8 Linux [Info] Checking the EPEL repository... [Error] Install EPEL repository failed, please check it. [root@ip-172-31-41-9 opt]# rpm -ql epel-...
  • C:\ProfessionalSoftware\MavenRepository\org\apache\maven\maven-repository-metadata\3.0\maven-repository-metadata-3.0.jar;C:\ProfessionalSoftware\MavenRepository\org\apache\maven\maven-model-builder\...
  • 与HibernateRepository类似,通过继承MongoRepository接口,我们可以非常方便地实现对一个对象的增删改查,要使用Repository的功能,先继承MongoRepositoryTD>接口,其中T为仓库保存的bean类,TD为该bean的唯一...
  • android-repository

    2021-06-02 15:35:25
    android-repositoryThis is a set of scripts for creating a mirror of Android Repository where SDK Manager downloads SDK packages.Tested onLinux Mint Maya(13, Ubuntu 10.04)/Rebecca(17.1, Ubuntu 14.04),O...
  • 最近项目有用到mongodb,也是经历了从不会到满百度的查资料,对mongodb有了些许的理解,项目里面总想着偷懒,不想使用template的类去拼写,就找了spring封装好的mongorepository进行查询,大体跟spring-data-jpa...
  • MongoDB与spring集成,操作mongo非常方便主要有两种方式:一、使用MongoTemplate二、使用Spring Data Mongodb的MongoRepository两者的区别就是第一种得自己写CURD语句,第二种非常方便基本不用自己写CURD语句我之前...
  • 在Docker的使用过程中,我们经常说Image,Container,Repository, 他们之间具体是一个什么的关系,又是如何使用的,这里基于自己的理解整理一下。 1 Image 镜像 1.1 说明 Image(镜像):是一个只读的特殊的...
  • Debian让用户可以通过一个名为add-apt-repository的应用程序,添加和使用PPA软件库,不过Kali Linux在其默认的程序包列表中并不含有该应用程序。就Kali而言,由于这是个特殊用途的应用程序,已进行了某些改动,以便...
  • 个人理解:Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository是仓库管理员,...
  • Android Support Repository 下载后的本地路径如下 /extras/android/m2repository/com/android/support/ 里面的文件夹类似如下 接着找到你要追加的library 例如:要使用FloatingActionButton,对应的lib就是 ...
  • //other stuff } public interface BatchRepository extends MongoRepository,String> { //this does not work //Batch findOneOrderByCreatedDesc(); } Andriy Simonov: Notice that the method name slightly ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 632,749
精华内容 253,099
关键字:

repository