Motivation
Repository pattern is a common mode during system development. In some masters' works: This pattern appeared in either the poeaa written by Martin Fowler or the DDD written by Eric Eban. Repository pattern mainly defines how to cut the dependency between the BLL layer and the Dal layer, so that the BLL layer does not depend on the actual implementation of the Dal layer. In addition, you can switch the Dal layer when you need to change the Dal target.
At the same time, learning repository pattern brings the concept of boundary into the architecture design. When designing the architecture, you can use repository pattern to encapsulate the architecture boundary. External Systems, modules, databases... In addition to the target architecture, the target architecture has higher cohesion and less coupling.
This article introduces the implementation of a repository pattern, which defines the responsibilities and interactions between objects to complete the functions and responsibilities that repository pattern should provide. Make a record for yourself and hope to help developers who need it.
Structure
Next, we will use the usercountservice, which is a computing service, as an example. This usercountservice calculates the number of all personnel in the system and the number of male personnel, which is provided for external systems. The repository pattern is used as the system boundary of the BLL layer and the dependency between the BLL and the Dal. The sample structure is as follows:
Major participants include:
User
-Data Objects used for system operation.
-Userid is the index value of this object.
Usercountservice
-Use userrepository to load the user.
-Use the loaded user to calculate various counts.
Userrepository
-Use iuserrepositoryprovider to load the user.
Iuserrepositoryprovider
-Interface for the user of the data object to access the system boundary.
-Only query is supported.
Sqluserrepositoryprovider
-Inherit from iuserrepositoryprovider.
-Create a user object for the database to be queried.
The following figure shows the interaction process between objects.
Practice
Model column download
For more information, see the sample program.
Click here to download the repositorysample
Fan Shi
First, create the repositorysample. BLL project, and create a Data Object User for system operation.
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; } }}
Create a boundary object that provides BLL layer data access, userrepository object, and iuserrepositoryprovider interface. (You can also directly create the iuserrepository interface as the boundary object for bll-layer data access. The reason for establishing the userrepository object and the iuserrepositoryprovider interface is that this combined boundary object is just a personal habit of architecture design. I prefer not to use interfaces for direct use within the architecture .)
namespace RepositorySample.BLL{ public class UserRepository { // 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(); } }}
namespace RepositorySample.BLL{ public interface IUserRepositoryProvider { // Methods IEnumerable<User> QueryAll(); }}
Create the last object of The bll layer, that is, the usercountservice, which truly provides the computing service.
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; } }}
Create the repositorysample. Dal project, and access the Dal object sqluserrepositoryprovider of the SQL database. (Because it is a simulation example, the database is not actually queried. Instead, it is directly created .)
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; } }}
The last thing left is to create a console project to use usercountservice. In this console project, usercountservice is generated and the number of people is printed. (Because it is a simulation example, the generation of usercountservice uses a direct creation method. The actual project can use various IOC frameworks to generate injection actions to avoid high coupling between the console project and the Dal project .)
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; } }}
Scenario
Then we use several scenarios to verify the reusability of the system. It also describes how to use the elasticity provided by repository pattern to meet various needs.
System data source replacement
When selling systems to customers, SQL Server cannot be installed in the enterprise environment of the customer. You must use other data storage media (for example, CSV files ).
In this case, you can write a new csvuserrepositoryprovider object to replace sqluserrepositoryprovider. The system queries user data in the CSV file through the new csvuserrepositoryprovider object. In this way, the system can replace the data source. The sample code is as follows:
First, create the Dal object csvuserrepositoryprovider to access the CSV file in the repositorysample. Dal project. (Because it is a simulation example, we will not analyze the file content. Instead, we will build it directly .)
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; } }}
Then, because the IOC framework is not used for generating injection, you must manually modify the generating injection.
static UserCountService CreateUserCountService(){ // UserRepositoryProvider CsvUserRepositoryProvider userRepositoryProvider = new CsvUserRepositoryProvider(); // UserRepository UserRepository userRepository = new UserRepository(userRepositoryProvider); // UserCountService UserCountService userCountService = new UserCountService(userRepository); // Return return userCountService;}
Finally, let's look at the running results and find that the calculated number of workers is calculated based on the data provided by csvuserrepositoryprovider.
Add data sources to the System
In addition, after the sales system gave the customer a while, the customer added an external user data source. This new user data source must be available with user data in the original system.
In this case, you can write a unionuserrepositoryprovider object to merge the new user data source and the old user data source according to the customer's requirements. The system can obtain the user data of the two data sources through the unionuserrepositoryprovider object. In this way, the system can add additional data sources. The sample code is as follows:
First, create a unionuserrepositoryprovider object in the repositorysample. Dal project. This object can combine the information provided by multiple iuserrepositoryproviders.
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; } }}
In addition, because the IOC framework is not used for generating injection, you must manually modify the generated injection.
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;}
Finally, let's look at the running results. We can find that the calculated number of workers is calculated by merging the data provided by csvuserrepositoryprovider and sqluserrepositoryprovider.
Cache added to the system
After using the system for a while, the system slows down when the data volume is large. After a variety of performance tools, it is found that analysis of CSV files is the bottleneck of the overall system performance.
In this case, you can write a cacheuserrepositoryprovider object to cache the data provided by csvuserrepositoryprovider according to the customer's requirements. The system will analyze the CSV file only when obtaining the data for the first time. In this way, the system can add the cache data source function to improve the system performance. The sample code is as follows:
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; } }}
Of course, because the IOC framework is not used in the sample to generate the injection, you need to manually modify the generated injection.
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;}
Postscript
The entire repository pattern is actually implemented, and the eye-catching developers will find that it has the same meaning as IOC. The difference between repository pattern and IOC mainly depends on the granularity during design. IOC looks at the dependency of cut from the aspect of program design. Repository pattern looks at cutting dependency from the architectural design aspect.
Adding the repository pattern design to the architecture design can provide the elasticity of the Dal layer for the system architecture. In addition to meeting the customer's needs, the success or failure of a system is also an important part of these additional non-functional requirements. I would like to think a little bit more about it. developers who want to maintain it in the future will appreciate it.