[Architecture Pattern] Entity Expansion
动机 :
一个软件系统的生命周期,必然面临到系统改版的问题。而在系统改版的时候,最常遇到的问题之一是,用户希望增加系统对象的数据字段(例如 : 用户数据增加相片)。常见的做法是把相关的功能,从把整个系统从UI到DB重整(重写?)一遍,让用户希望增加至系统的字段,在系统里实现。
这样的做法,笔者把它称作『修改式程序代码累积』。所谓的修改式程序代码累积是说,藉由修改经过验证、并且正常运作的程序代码与接口来扩充系统。理论上,程序代码经过修改之后,必须重新执行完整的测试。而接口经过修改,使用手册、教育训练等等,常常也必需做同步的更新。可以说是牵一发动全身。
笔者比较喜爱『增加式程序代码累积』。所谓的增加式程序代码累积是说,原有的程序代码、包含接口不做更动,而是增加额外的程序代码附加到系统内来扩充系统。 因为程序代码与接口都没有做过修改,就可以避免修改程序代码所产生的额外工作。但不可避免的,这样的系统在开发初期要花比较大的心力做设计。
本文介绍一个Entity Expansion模式。Entity Expansion模式主要是定义一组,数据对象(Entity)以及边界对象(Repository)的生成、结构、行为模式,用来扩展对象的属性数据。实作这个模式,可以为系统加入增加式程序代码累积的能力。
* 下列文章分别标注段落为01、02只是不想全部写成一个超长段落,不是示意项目必须拆解成两个。
基础平台 :
结构
参与者
Page Shell
-页面的壳层,可以透过设定数据,动态挂载系统页面的系统。
范例程序
Page Shell依照开发平台的不同,会有不同的选择。例如以ASP.NET的平台来说,可以直接套用ASP.NET的页面机制,然后再另外建立一个索引页面,用修改索引页面的方式来达成动态挂载系统页面的需求。
基础项目01 :
结构
参与者
UserManagePage
-User管理页面,提供新增、修改、删除、查询 User的功能。
-使用例如 Spring.Net、Provider Pattern来反射生成 IUserRepositoryFactory。
-使用生成的 IUserRepositoryFactory生成 IUserRepository。
-使用 IUserRepository新增、修改、删除、查询 User。
User
-系统运作使用的数据对象。
IUserRepositoryFactory
-生成 IUserRepository。
-依靠例如 Spring.Net、Provider Pattern来反射生成。
IUserRepository
-数据对象 User进出系统边界的接口。
-提供新增、修改、删除、查询等功能。
范例程序
namespace CLK.EntityExpansion { public class UserManagePage { // Methods public void ShowUser() { // GetData IUserRepositoryFactory userRepositoryFactory = null; // 使用例如Spring.Net、Provider Pattern来反射生成。(Base专案生成 DefaultUserRepositoryFactory、Ex专案生成 ExpandedUserRepositoryFactory) IUserRepository userRepository = userRepositoryFactory.CreateRepository(); IEnumerable<User> userCollection = userRepository.GetAll(); // Show this.ShowUser(userCollection); } private void ShowUser(IEnumerable<User> userCollection) { //..... } } public class User { // Constructor public User() { this.UserID = Guid.Empty; this.Name = string.Empty; this.Description = string.Empty; } protected User(User item) { #region Require if (item == null) throw new ArgumentNullException(); #endregion this.UserID = item.UserID; this.Name = item.Name; this.Description = item.Description; } // Properties public Guid UserID { get; set; } public string Name { get; set; } public string Description { get; set; } } public interface IUserRepositoryFactory { // Methods IUserRepository CreateRepository(); } public interface IUserRepository { // Methods void Add(User item); void Modify(User item); void Remove(Guid id); User GetByID(Guid id); IEnumerable<User> GetAll(); } }
基础项目02 :
结构
参与者
DefaultUserRepositoryFactory
-IUserRepositoryFactory的实作
-生成 IUserRepository - SqlUserRepository。
SqlUserRepository
-数据对象 User进出系统边界的接口,IUserRepository的实作。
-依靠 IUserRepositoryFactory生成。
-负责将数据对象User进出SQL数据库。
范例程序
namespace CLK.EntityExpansion { public class DefaultUserRepositoryFactory : IUserRepositoryFactory { // Methods public IUserRepository CreateRepository() { // Create IUserRepository userRepository = new SqlUserRepository(); // Return return userRepository; } } public class SqlUserRepository : IUserRepository { // Methods public void Add(User item) { // Sql操作... } public void Modify(User item) { // Sql操作... } public void Remove(Guid id) { // Sql操作... } public User GetByID(Guid id) { // Sql操作... return null; } public IEnumerable<User> GetAll() { // Sql操作... return null; } } }
扩展专案01 :
结构
参与者
ExUserManagePage
-ExUser管理页面,提供新增、修改、删除、查询 ExUser的功能。
-使用例如 Spring.Net、Provider Pattern来反射生成 IExUserRepositoryFactory。
-使用生成的 IExUserRepositoryFactory生成 IExUserRepository。
-使用 IExUserRepository新增、修改、删除、查询 ExUser。
ExUser
-系统运作使用的数据对象。
-继承User并且扩展自己的属性数据。
IUserRepositoryFactory
-生成 IExUserRepository。
-依靠例如 Spring.Net、Provider Pattern来反射生成。
IExUserRepository
-数据对象 ExUser进出系统边界的接口。
-提供新增、修改、删除、查询等功能。
范例程序
namespace CLK.EntityExpansion.Expanded { public class ExUserManagePage { // Methods public void ShowExUser() { // GetData IExUserRepositoryFactory userRepositoryFactory = null; // 使用例如Spring.Net、Provider Pattern来反射生成。(Base专案生成 DefaultExUserRepositoryFactory、Ex专案生成 ExpandedExUserRepositoryFactory) IExUserRepository userRepository = userRepositoryFactory.CreateRepository(); IEnumerable<ExUser> userCollection = userRepository.GetAll(); // Show this.ShowExUser(userCollection); } private void ShowExUser(IEnumerable<ExUser> exUserCollection) { //..... } } public class ExUser : User { // Constructor public ExUser() : base() { this.Photo = null; } public ExUser(User item) : base(item) { #region Require if (item == null) throw new ArgumentNullException(); #endregion this.Photo = null; } // Properties public byte[] Photo { get; set; } } public interface IExUserRepositoryFactory { // Methods IExUserRepository CreateRepository(); } public interface IExUserRepository { // Methods void Add(ExUser item); void Modify(ExUser item); void Remove(Guid id); ExUser GetByID(Guid id); IEnumerable<ExUser> GetAll(); } }
扩展专案02 :
结构
参与者
DefaultExUserRepositoryFactory
-IExUserRepositoryFactory的实作。
-生成 IUserRepository - SqlUserRepository。
-生成 IExUserSectionRepository - SqlExUserSectionRepository。
-生成 ExUserRepository,传入生成的IUserRepository、IExUserSectionRepository。
ExUserRepository
-数据对象 ExUser进出系统边界的接口,IExUserRepository的实作。
-依靠 DefaultExUserRepositoryFactory生成,接受传入的IUserRepository、IExUserSectionRepository。
-负责数据对象ExUser拆解为 User及ExUserSection。并将使用者对其新增修改删除动作拆解为,User对象进出IUserRepository、ExUserSection物件进出IExUserSectionRepository。
-负责数据对象User及ExUserSection组合为ExUser。并将使用者对其查询动作拆解为,User对象进出IUserRepository、ExUserSection物件进出IExUserSectionRepository。
ExUserSection
-系统运作使用的数据对象,存放ExUser扩充增加的属性数据。
IExUserSectionRepository
-数据对象 ExUserSection进出系统边界的接口。
-提供新增、修改、删除、查询等功能。
SqlExUserSectionRepository
-数据对象ExUserSection 进出系统边界的接口,IUserRepository的实作。
-负责将数据对象 ExUserSection进出SQL数据库。
ExpandedUserRepositoryFactory
-IUserRepositoryFactory的实作。
-生成 IUserRepository - SqlUserRepository。
-生成 IExUserSectionRepository - SqlExUserSectionRepository。
-生成 UserRepositoryDecorator传入生成的IUserRepository、IExUserSectionRepository。
UserRepositoryDecorator
-数据对象 User进出系统边界的接口,IUserRepository的实作。
-依靠 IUserRepositoryFactory生成。
-依靠 ExpandedUserRepositoryFactory生成,接受传入的IUserRepository、IExUserSectionRepository。
-负责将用户对数据对象User新增修改删除查询动作拆解为进出IUserRepository。
-负责数据对象User拆解为 UserId。并将使用者对其删除动作拆解为,ExUserSection对象自IExUserSectionRepository移除。
范例程序
namespace CLK.EntityExpansion.Expanded { public class ExUserSection { // Properties public Guid UserID { get; set; } public byte[] Photo { get; set; } } public interface IExUserSectionRepository { // Methods void Add(ExUserSection item); void Modify(ExUserSection item); void Remove(Guid id); ExUserSection GetByID(Guid id); } public class SqlExUserSectionRepository : IExUserSectionRepository { public void Add(ExUserSection item) { // Sql操作... } public void Modify(ExUserSection item) { // Sql操作... } public void Remove(Guid id) { // Sql操作... } public ExUserSection GetByID(Guid id) { // Sql操作... return null; } } }
namespace CLK.EntityExpansion.Expanded { public class DefaultExUserRepositoryFactory : IExUserRepositoryFactory { // Methods public IExUserRepository CreateRepository() { // Create IUserRepository userRepository = new SqlUserRepository(); IExUserSectionRepository exUserSectionRepository = new SqlExUserSectionRepository(); // Return return new ExUserRepository(userRepository, exUserSectionRepository); } } public class ExUserRepository : IExUserRepository { // Fields private readonly IUserRepository _userRepository = null; private readonly IExUserSectionRepository _exUserSectionRepository = null; // Constructor public ExUserRepository(IUserRepository userRepository, IExUserSectionRepository exUserSectionRepository) { #region Require if (userRepository == null) throw new ArgumentNullException(); if (exUserSectionRepository == null) throw new ArgumentNullException(); #endregion _userRepository = userRepository; _exUserSectionRepository = exUserSectionRepository; } // Methods public void Add(ExUser item) { #region Require if (item == null) throw new ArgumentNullException(); #endregion // User _userRepository.Add(item); // ExUserSection ExUserSection exUserSection = new ExUserSection(); exUserSection.UserID = item.UserID; exUserSection.Photo = item.Photo; _exUserSectionRepository.Add(exUserSection); } public void Modify(ExUser item) { #region Require if (item == null) throw new ArgumentNullException(); #endregion // User _userRepository.Modify(item); // ExUserSection ExUserSection exUserSection = new ExUserSection(); exUserSection.UserID = item.UserID; exUserSection.Photo = item.Photo; _exUserSectionRepository.Modify(exUserSection); } public void Remove(Guid id) { #region Require if (id == Guid.Empty) throw new ArgumentNullException(); #endregion // User _userRepository.Remove(id); // ExUserSection _exUserSectionRepository.Remove(id); } public ExUser GetByID(Guid id) { #region Require if (id == Guid.Empty) throw new ArgumentNullException(); #endregion // User User user = _userRepository.GetByID(id); if (user == null) return null; // ExUserSection ExUserSection exUserSection = _exUserSectionRepository.GetByID(id); if (exUserSection == null) return new ExUser(user); // ExUser ExUser exUser = new ExUser(user); exUser.Photo = exUserSection.Photo; return exUser; } public IEnumerable<ExUser> GetAll() { // Result List<ExUser> exUserList = new List<ExUser>(); // User foreach (User user in _userRepository.GetAll()) { // ExUserSection ExUserSection exUserSection = _exUserSectionRepository.GetByID(user.UserID); if (exUserSection == null) { exUserList.Add(new ExUser(user)); continue; } // ExUser ExUser exUser = new ExUser(user); exUser.Photo = exUserSection.Photo; exUserList.Add(exUser); } // Return return exUserList; } } }
namespace CLK.EntityExpansion.Expanded { public class ExpandedUserRepositoryFactory : IUserRepositoryFactory { // Methods public IUserRepository CreateRepository() { // Create IUserRepository userRepository = new SqlUserRepository(); IExUserSectionRepository exUserSectionRepository = new SqlExUserSectionRepository(); // Return return new UserRepositoryDecorator(userRepository, exUserSectionRepository); } } public class UserRepositoryDecorator : IUserRepository { // Fields private readonly IUserRepository _userRepository = null; private readonly IExUserSectionRepository _exUserSectionRepository = null; // Constructor public UserRepositoryDecorator(IUserRepository userRepository, IExUserSectionRepository exUserSectionRepository) { #region Require if (userRepository == null) throw new ArgumentNullException(); if (exUserSectionRepository == null) throw new ArgumentNullException(); #endregion _userRepository = userRepository; _exUserSectionRepository = exUserSectionRepository; } // Methods public void Add(User item) { #region Require if (item == null) throw new ArgumentNullException(); #endregion // User _userRepository.Add(item); } public void Modify(User item) { #region Require if (item == null) throw new ArgumentNullException(); #endregion // User _userRepository.Modify(item); } public void Remove(Guid id) { #region Require if (id == Guid.Empty) throw new ArgumentNullException(); #endregion // User _userRepository.Remove(id); // ExUserSection _exUserSectionRepository.Remove(id); } public User GetByID(Guid id) { #region Require if (id == Guid.Empty) throw new ArgumentNullException(); #endregion // User User user = _userRepository.GetByID(id); // Return return user; } public IEnumerable<User> GetAll() { // Result IEnumerable<User> userList = null; // User userList = _userRepository.GetAll(); // Return return userList; } } }
结语 :
本文范例简单的说就是,当基础项目与扩展项目都建立完毕。在原本基础项目的管理页面建立User对象,在扩展项目的ExUser管理页面也可以查询到新增的ExUser(内容为User数据,ExUser扩展的属性数据为默认值)。当在扩展项目删除ExUser时,基础项目内的User也会同时被删除。
本文介绍的Entity Expansion模式,看起来只是扩展了对象的属性数据,但其实可以看成,处理了强型别扩展对象进出系统边界的责任。以此模式为基础发展,理论上可以设计出能无限延伸的应用系统架构。
期許自己~
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。