Search code examples
ooparchitecturedomain-driven-designdata-access-layer

Just how limited should the responsibilities of a Repository be?


Some reading tells me my Repositories(1) should do nothing at all but direct CRUD operations through the configured (injected) data provider, but very often in small projects I find adding a whole service layer as well to be a bit much.

Example, my app deals with 'bank' branches and clients. Now, if my user 'opens' a branch for each work session, and each new client is assigned to that branch, I feel inclined to inject an AppContext singleton I've written, to track ambient app attribiutes such as the currently opened branch, into my ClientRepository and let that object assign the correct BranchID to the new client record.

I know this is not correct in the purest sense, but I am talking basic 2 tiers apps here, UI and data. The only business 'logic' is really done in querying the database. Are there any more suitable patterns I could be using here?

(1) The Repository Pattern


Solution

  • I personally agree that the Repository should be as "clean" as possible like you described in the first paragraph.

    From experience, I have "cheated" by also writing business logic code in the CRUD functions, which often required other repositories to be properties.

    Working in ASP.net, I took advantage of generics and event-handling.

    public class Repository<T, DC>
            where T : class
            where DC : DataContext, new()
    {
        public delegate void RecordInsertedHandler(object s, BasicEventArgs<T> e);
        public event RecordInsertedHandler RecordInserted;
    
        public delegate void RecordDeletedHandler(object s, BasicEventArgs<T> e);
        public event RecordDeletedHandler RecordDeleted;
    
        public delegate void RecordObtainedHandler(object s, BasicEventArgs<T> e);
        public event RecordObtainedHandler RecordObtained;
    
        public delegate void RecordUpdatedHandler(object s, BasicEventArgs<T> e);
        public event RecordUpdatedHandler RecordUpdated;
    
        protected DC dc;
    
        public Repository(string connectionString="")
        {
            dc = ((connectionString == null) || (connectionString == "")) ? new DC() :   DynamicTypes.Instantiate<DC>(connectionString);  // See code below for DynamicTypes:
    
        }
    
        // There are similar functions for other events not shown.
        protected void OnRecordInserted(BasicEventArgs<T> obj)
        {
            if (RecordInserted != null)
            {
                RecordInserted(this, obj);
            }
        }
    
        // Only the Insert is shown here.
        private void Insert(T obj)
        {
            dc.GetTable<T>().InsertOnSubmit(obj);
            dc.SubmitChanges();
    
            OnRecordInserted(new BasicEventArgs<T>(obj));
        }
    }
    

    The basic idea is that you would fire/invoke these events in the correct places of your regular CRUD functions. I presume that your repository will be a member of an ASP.net page or a Windows form. Those containers then would be the "event listeners" since they can manipulate the UI.

    Dynamic Types:

    public static class DynamicTypes
    {        
        public static T Instantiate<T>(params object[] args)
        {
            return (T)Activator.CreateInstance(typeof(T), args);
        }
    }
    

    BasicEventArgs:

    public class BasicEventArgs<T> : EventArgs
    {
        private T _Data;
        private string _Message;
    
        public BasicEventArgs(T data, string message="") : base()
        {
            _Data = data;
            _Message = message;
        }
    
        public T Data
        {
            get { return _Data; }
            set { _Data = value; }
        }
    
        public string Message
        {
            get { return _Message; }
            set { _Message = value; }
        }
    }
    

    Repositories:

    public class BranceshRepository : Repository<Branch, YourDataContext>
    {
         public BranchesRepository(string connectionString="") : base(connectionString)
         {
         }
    }
    
    public class ClientsRepository : Repository<Client, YourDataContext>
    {
         public ClientsRepository(string connectionString="") : base(connectionString)
         {
         }
    }
    

    Now, for example, we have an ASP.net page:

    public partial class MyPage : Page
    {
          protected ClientsRepository _ClientsRepository;
          protected BranchesRepository _BranchesRepository;
    
          protected void Page_Load(object s, EventArgs e);
          {
               _ClientsRepository = new ClientsRepository(...);
               _BranchesRepository = new BranchesRepository(...);
    
               _BranchesRepository.RecordInserted += new Repository<Branch,YourDataContext>.RecordInsertedHandler(OnBranchInserted);
               _ClientsRepository.RecordInserted += new RepositoryM<Client, YourDataContext>.RecordInsertedHandler(OnClientInserted);
          }
    
          protected void OnBranchInserted(object s, BasicEventArgs<Branch> e)
          {
                 /* e.Data is your newly-inserted branch with the newly-generated Id
                    from the database. You may save this branch to Session for
                    later use when your user inserts a new client.
                   */
          }
    
          protected void OnClientInserted(object s, BasicEventArgs<Client> e)
          {
                  Branch currentBranch = (Branch)Session["Branch"];
                  e.Data.BranchId = currentBranch.Id;
                  _ClientsRepository.Update(e.Data);                
          }
    
          // Control event handlers not shown, like CreateClient_BT_Click, for example.
    }
    

    This way, you do not need to inject a singleton in your ClientsRepository, and you have full access to your UI in these event handlers.