动机
Repository Pattern是一个在开发系统时,很常用的一个模式。在一些大师的著作:不管是在Martin Fowler所写的PoEAA或者是Eric Eban著作的DDD里,都有出现这个Pattern的身影。Repository Pattern最主要是定义如何切割BLL层跟DAL层之间的相依性,让BLL层不用依赖于DAL层的实做。并且在有需要更换DAL目标的时候,可以有抽换DAL层的能力。
同时学习Repository Pattern,也为架构设计带入了边界的概念。在设计架构的时候,可以套用Repository Pattern来做为架构边界的封装。将外部的系统、模块、数据库…等等,隔离在目标架构之外,让目标架构有更高的内聚以及较少的耦合。
本篇文章介绍一个Repository Pattern的实做,这个实做定义对象之间的职责跟互动,用来完成Repository Pattern应该提供的功能及职责。为自己做个纪录,也希望能帮助到有需要的开发人员。
结构
接下来采用一个计算人员数量的服务UserCountService,当作范例的内容。这个UserCountService,计算系统内所有人员的数量、男性人员的数量,提供给外部系统使用。并且实做Repository Pattern来当作BLL层的系统边界、切割BLL与DAL之间的相依。范例的结构如下:
主要的参与者有:
User
-系统运作使用的数据对象。
-UserID是这个对象的索引值。
UserCountService
-使用UserRepository加载User。
-使用加载的User计算各种Count。
UserRepository
-使用IUserRepositoryProvider加载User。
IUserRepositoryProvider
-数据对象 User进出系统边界的接口。
-只提供查询功能。
SqlUserRepositoryProvider
-继承IUserRepositoryProvider。
-实做查询数据库产生User对象。
透过下面的图片说明,可以了解相关对象之间的互动流程。
实做
范列下载
实做说明请参照范例程序内容。
RepositorySample点此下载
范列实做
首先建立RepositorySample.BLL项目,并且建立一个系统运作使用的数据对象User。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
namespace
RepositorySample.BLL
{
public
class
User
{
// Constructor
public
User(Guid userID)
{
#region Require
if
(userID == Guid.Empty)
throw
new
ArgumentNullException();
#endregion
}
// Properties
public
Guid UserID {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
bool
IsMen {
get
;
set
; }
}
}
|
接着建立提供BLL层数据进出的边界对象,UserRepository对象及IUserRepositoryProvider接口。(这边要特别说明的是,也可以直接建立IUserRepository接口当作BLL层数据进出的边界对象。之所以建立UserRepository对象及IUserRepositoryProvider接口,这种组合式的边界对象,只是架构设计的个人习惯,笔者比较不喜欢使用接口让架构内部直接使用。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
namespace
RepositorySample.BLL
{
public
class
UserRepository : IUserRepositoryProvider
{
// Fields
private
readonly
IUserRepositoryProvider _userRepositoryProvider =
null
;
// Constructor
public
UserRepository(IUserRepositoryProvider userRepositoryProvider)
{
#region Require
if
(userRepositoryProvider ==
null
)
throw
new
ArgumentNullException();
#endregion
_userRepositoryProvider = userRepositoryProvider;
}
// Methods
public
IEnumerable<User> QueryAll()
{
return
_userRepositoryProvider.QueryAll();
}
}
}
|
1
2
3
4
5
6
7
8
|
namespace
RepositorySample.BLL
{
public
interface
IUserRepositoryProvider
{
// Methods
IEnumerable<User> QueryAll();
}
}
|
接着建立BLL层最后一个对象,也就是真正提供计算人员数量服务的对象UserCountService。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
namespace
RepositorySample.BLL
{
public
class
UserCountService
{
// Fields
private
readonly
UserRepository _userRepository =
null
;
// Constructor
public
UserCountService(UserRepository userRepository)
{
#region Require
if
(userRepository ==
null
)
throw
new
ArgumentNullException();
#endregion
_userRepository = userRepository;
}
// Methods
public
int
GetAllCount()
{
return
_userRepository.QueryAll().Count();
}
public
int
GetMenCount()
{
int
menCount = 0;
foreach
(User user
in
_userRepository.QueryAll())
{
if
(user.IsMen ==
true
)
{
menCount++;
}
}
return
menCount;
}
}
}
|
再来建立RepositorySample.DAL项目,以及存取Sql数据库的DAL对象SqlUserRepositoryProvider。(因为是仿真范例就不实做查询数据库,改用直接建立的方式来示意。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
namespace
RepositorySample.DAL
{
public
class
SqlUserRepositoryProvider : IUserRepositoryProvider
{
// Methods
public
IEnumerable<User> QueryAll()
{
User user =
null
;
List<User> userList =
new
List<User>();
user =
new
User(Guid.NewGuid());
user.Name =
"Clark"
;
user.IsMen =
true
;
userList.Add(user);
user =
new
User(Guid.NewGuid());
user.Name =
"Jane"
;
user.IsMen =
false
;
userList.Add(user);
return
userList;
}
}
}
|
最后剩下的就是建立一个Console项目,来使用UserCountService。这个Console项目里,会生成UserCountService,并且打印的人员数量。(因为是仿真范例,UserCountService的生成,采用直接建立的方式来示意。实际项目可以采用各种IoC Framework来做生成注入的动作,避免Console项目与DAL项目有高度的耦合。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
namespace
RepositorySample
{
class
Program
{
static
void
Main(
string
[] args)
{
// UserCountService
UserCountService userCountService = CreateUserCountService();
// Print
Console.WriteLine(
"All Count : "
+ userCountService.GetAllCount());
Console.WriteLine(
"Men Count : "
+ userCountService.GetMenCount());
// End
Console.ReadLine();
}
static
UserCountService CreateUserCountService()
{
// UserRepositoryProvider
SqlUserRepositoryProvider userRepositoryProvider =
new
SqlUserRepositoryProvider();
// UserRepository
UserRepository userRepository =
new
UserRepository(userRepositoryProvider);
// UserCountService
UserCountService userCountService =
new
UserCountService(userRepository);
// Return
return
userCountService;
}
}
}
|
情景
接着采用几个情景,来验证系统的重用性。并且说明如何利用Repository Pattern提供的弹性,来满足各种的需求。
系统更换数据源
卖系统给客户的时候,客户的企业环境内不允许装设SQL Server,必须要采用别种数据存放媒介(例如:CSV檔)。
这时可以依照客户的需求,写一个新的CsvUserRepositoryProvider对象,用来取代SqlUserRepositoryProvider,系统透过这个新的CsvUserRepositoryProvider对象来查询CSV档案内的User数据。透过这样的方式,系统就可以更换数据源。范例的程序代码如下:
首先在RepositorySample.DAL项目内,建立存取CSV档案的DAL对象CsvUserRepositoryProvider。(因为是仿真范例就不实做剖析档案内容,改用直接建立的方式来示意。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
namespace
RepositorySample.DAL
{
public
class
CsvUserRepositoryProvider : IUserRepositoryProvider
{
// Methods
public
IEnumerable<User> QueryAll()
{
User user =
null
;
List<User> userList =
new
List<User>();
user =
new
User(Guid.NewGuid());
user.Name =
"Jeff"
;
user.IsMen =
true
;
userList.Add(user);
return
userList;
}
}
}
|
接着因为范例没有采用IoC Framework来做生成注入,所以必须手动修改生成注入这个工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
static
UserCountService CreateUserCountService()
{
// UserRepositoryProvider
CsvUserRepositoryProvider userRepositoryProvider =
new
CsvUserRepositoryProvider();
// UserRepository
UserRepository userRepository =
new
UserRepository(userRepositoryProvider);
// UserCountService
UserCountService userCountService =
new
UserCountService(userRepository);
// Return
return
userCountService;
}
|
最后看看运行结果,可以发现计算出来的人员数量,是以CsvUserRepositoryProvider提供的资料来做计算。
系统增加数据源
另外在卖系统给客户过了一阵子之后,客户增加了一个外部的使用者数据源。这个新的使用者数据源,必须要可以跟原本系统内的用户数据一起使用。
这时可以依照客户的需求,写一个UnionUserRepositoryProvider对象,用来合并新使用者数据源与旧用户数据源,系统透过这个UnionUserRepositoryProvider对象就可以取得两种数据源的User数据。透过这样的方式,系统就可以加入额外的数据源。范例的程序代码如下:
首先在RepositorySample.DAL项目内,建立一个UnionUserRepositoryProvider对象。这个对象可以合并多个IUserRepositoryProvider提供的资料。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
namespace
RepositorySample.DAL
{
public
class
UnionUserRepositoryProvider : IUserRepositoryProvider
{
// Fields
private
readonly
List< IUserRepositoryProvider> _userRepositoryProviderList =
null
;
// Constructor
public
UnionUserRepositoryProvider(List<IUserRepositoryProvider> userRepositoryProviderList)
{
#region Require
if
(userRepositoryProviderList ==
null
)
throw
new
ArgumentNullException();
#endregion
_userRepositoryProviderList = userRepositoryProviderList;
}
// Methods
public
IEnumerable<User> QueryAll()
{
List<User> userList =
new
List<User>();
foreach
(IUserRepositoryProvider userRepositoryProvider
in
_userRepositoryProviderList)
{
foreach
(User user
in
userRepositoryProvider.QueryAll())
{
userList.Add(user);
}
}
return
userList;
}
}
}
|
另外因为范例没有采用IoC Framework来做生成注入,所以必须手动修改生成注入这个工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static
UserCountService CreateUserCountService()
{
// UserRepositoryProvider
List<IUserRepositoryProvider> userRepositoryProviderList =
new
List<IUserRepositoryProvider>();
userRepositoryProviderList.Add(
new
CsvUserRepositoryProvider());
userRepositoryProviderList.Add(
new
SqlUserRepositoryProvider());
UnionUserRepositoryProvider userRepositoryProvider =
new
UnionUserRepositoryProvider(userRepositoryProviderList);
// UserRepository
UserRepository userRepository =
new
UserRepository(userRepositoryProvider);
// UserCountService
UserCountService userCountService =
new
UserCountService(userRepository);
// Return
return
userCountService;
}
|
最后看看运行结果,可以发现计算出来的人员数量,是以合并CsvUserRepositoryProvider、SqlUserRepositoryProvider提供的数据来做计算。
系统加入快取功能
接着客户使用系统一阵子之后,发现数据较多的时候,系统就会变慢。经过各种效能工具的检查,发现是剖析CSV文件是整个系统效能的瓶颈。
这时可以依照客户的需求,写一个CacheUserRepositoryProvider对象,用来快取CsvUserRepositoryProvider提供的数据,系统只有在第一次取数据的时候,才会去剖析CSV档案。透过这样的方式,系统就可以加入快取数据源的功能,提高系统的效能。范例的程序代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
namespace
RepositorySample.DAL
{
public
class
CacheUserRepositoryProvider : IUserRepositoryProvider
{
// Fields
private
readonly
IUserRepositoryProvider _userRepositoryProvider =
null
;
private
IEnumerable<User> _cache =
null
;
// Constructor
public
CacheUserRepositoryProvider(IUserRepositoryProvider userRepositoryProvider)
{
#region Require
if
(userRepositoryProvider ==
null
)
throw
new
ArgumentNullException();
#endregion
_userRepositoryProvider = userRepositoryProvider;
}
// Methods
public
IEnumerable<User> QueryAll()
{
if
(_cache ==
null
)
{
_cache = _userRepositoryProvider.QueryAll();
}
return
_cache;
}
}
}
|
当然啦,因为范例没有采用IoC Framework来做生成注入,所以手动修改生成注入这个工作还是要做。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
static
UserCountService CreateUserCountService()
{
// UserRepositoryProvider
CsvUserRepositoryProvider csvUserRepositoryProvider =
new
CsvUserRepositoryProvider();
CacheUserRepositoryProvider userRepositoryProvider =
new
CacheUserRepositoryProvider(csvUserRepositoryProvider);
// UserRepository
UserRepository userRepository =
new
UserRepository(userRepositoryProvider);
// UserCountService
UserCountService userCountService =
new
UserCountService(userRepository);
// Return
return
userCountService;
}
|
后记
整个Repository Pattern实做看来下,眼尖的开发人员会发现,它跟IoC有异曲同工的意味。而Repository Pattern跟IoC的差异,主要是取决于设计时的颗粒度。IoC是从程序设计面,去看待切割相依性这件事情。而Repository Pattern则是从架构设计面,去看待切割相依性这件事情。
在架构设计里加入Repository Pattern的设计,可以为系统架构提供了抽换DAL层的弹性。一个系统的成败,除了最基本的满足客户需求之外,这些额外的非功能需求也是很重要的一环。动手的时候多想一点点,未来维护的开发人员,会感激你的。
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。