30-19 之 Domain Layer - Repository

接下来我们要来谈谈,应该不少人常听到的『 Repository 』这个东东,目前我先将他放在 3-Tier Layer 中的 DataSourceLayer 的部份,但是在书中是放在 :

Object-Relational Metadata Mapping Patterns

接下来我们来研究看看他到底是什麽。

什麽是 Repository 呢 ?

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

我个人的理解为 :

  • repository 介於 domain model 与 data mapper 之间。
  • 他存在的目的,可以想成,让 db 操作『 更接近 domain model 』

data mapping 就是单纯的资料库操作,所以他提供的方法名很接近资料库处理,例如 select、update 等。而 repository 倾向接近 domain 层,所以他的命名基本上是偏向 findByName、save 啥的。

我觉得这里可以想简单定义以为 repository 与 data mapper 的差别 :

  • data mapper : 单纯的资料库操作。
  • repository : data mapper 往上一层,更接近 domain 的东东,他是为了 domain 操作 db 而存在。

那这样我可以理解为什麽要在书中把他定义在『 Object-Relational Metadata Mapping Patterns 』层级了。因为严格来说它是为了让 domain layer 与 data source layer 中间在一个层级。

范例

// Domain Layer
class PersonDomain{
    id: string
    age: number
    name: string
    company: string
    constructor(id:string, age: number, name: string,company: string){
        this.id = id
        this.age = age
        this.name = name
        this.company = company
    }
    isAdult(): boolean{
        return this.age >= 18
    }
    isVIP(): boolean{
        return ['HAHOW','GOOGLE','KKBOX'].includes(this.company)
    }
}

// DataSource Layer

function _mockExecuteSelectSql(sql){
    return [
        {
            id: '1',
            name: 'mark',
            age: 18,
            company: 'HAHOW'
        }
    ] 
}

interface IPersonRepository{
    findById(id: string): PersonDomain
    findByCompany(company: string): PersonDomain[]
    save(person: PersonDomain)
}

class PersonRepository implements IPersonRepository {
    personMapper: IPersonMapper 
    constructor(personMapper: IPersonMapper){
        this.personMapper = personMapper
    }
    findById(id: string): PersonDomain{
        const result = this.personMapper.selectByQuery({
            id 
        })
        return result[0]
    }
    findByCompany(company: string): PersonDomain[]{
        const result = this.personMapper.selectByQuery({
            company 
        })
        return result 
    }
    save(person: PersonDomain): void{
        this.personMapper.insert(person)
    }
}

interface IPersonMapper{
    selectByQuery(query: any): PersonDomain[]
    insert(person: PersonDomain): void
}

class PersonMapper implements IPersonMapper {
    selectByQuery(query: any): PersonDomain[]{
        console.log('Connect to db for find')
        const querystring = query
        const resultSet: any = _mockExecuteSelectSql(querystring)
        const result = this.doLoad(resultSet)
        return result
    }
    insert(person: PersonDomain): void{

    }
    private doLoad(resultSet: any): PersonDomain[]{
        const result = []
        for (const data of resultSet) {
            result.push(new PersonDomain(data.id, data.age, data.name, data.company))
        }
        return result
    }
}

// App Layer
const personMapper: IPersonMapper = new PersonMapper()
const personRepository: IPersonRepository = new PersonRepository(personMapper)
const personsInCompany: PersonDomain[] = personRepository.findByCompany('HAHOW') 
if(personsInCompany.length >= 10) throw Error('公司人数已达限制')

const newPerson: PersonDomain = new PersonDomain(null, 18, 'Mark', 'HAHOW')
if(!newPerson.isAdult()) throw Error('要成年才能注册喔')
personRepository.save(newPerson)

小总结

这个知识点可以用来解释什麽现象

之前我一直在想,如果 domain 层有很多需要为了业务,而产生的一些 query,例如 SelectByNameAndAgeAndCompany 这种的,那是不是要在 data mapper 那『 为了业务而写一个方法支援它呢 ? 』。

那这样不就 domain model 与 dataMapper 耦合在一起了,理解了 repository 才知道这个层级就是为了解决这个问题。

这个知识点可以和以前的什麽知识连结呢 ?

上一篇文章中我们有问了一个问题如下 :

Repository 就是指 DataMapper 吗 ?

不是,它比较接近 domain 层,它会将一些 domain 复杂的 query 资料库操作,在 repository 里处理,例如一些 n+1 的 i/o 操作,应该也会在这里进行优化。

我要如何运用这个知识点 ?

  • 之後在设计 domain 与 db 操作之间,可以加上一层 repository 层,然後建立业务上所需要的一些查询方法,而不用在 dataMapper 那为了业务,而新增方法。
  • 记好 repository 接近业务,所以命名时不要以 db 的操作来命名。

参考资料


<<:  Swift 新手-物联网与 iOS App的整合运用

>>:  Day-23 AVL Tree

分布式可观测性 Structured Log

对於Log, 在Log采集服务, 希望是采取Structured Log的结构来输出. 常见的结构有...

Day26 用python写UI-聊聊Text(三)

今天的程序码也超长的,因为范例有结合昨天的一起呈现,所以就越加越长了~ ♠♣今天的文章大纲♥♦ 储存...

Day-30 跳页传值

上一篇介绍完如何跳页, 但是页面与页面间又该如何传递资料呢? 因此本篇的主题是跳页传值。 传值的语法...

Day 05 : 来点不一样的 Two Sum

今天来稍微改变一下 Two Sum 这题题目 原本的题目要回传nums中的index,我们来把他改成...

tkinter 实现台湾类股抽签程序

# -*- coding: utf-8 -*- import tkinter as tk impor...