8
\$\begingroup\$

I am working in a shop where we tie into multiple different vendors to share data. I am also tasked with "bringing the code base up to the 4.x framework". To start, I understand that Entity Framework is a Repository/UoW pattern and really does not gain anything by wrapping it in home-spun Repository/UoW patterns. While I will eventually add EF at a later date, I don't have it in the existing code base. Instead, I have:

  • ADO.NET
  • OLEDB
  • Web Service (SOAP)
  • Web Socket (and a whole bunch of ugly)

And that's what I've picked out so far...

So, I am trying to build a basic Repository and UoW from the bottom up that will, eventually, present the same way to the developer in the end. What I have so far are 3 projects:

  1. Me.Data // Has all the base interfaces and abstract classes
  2. Me.Data.Cars // Handles the data via a SOAP based web service
  3. Me.Data.Symitar // Handles the data via a web socket

Starting from the bottom:

NAMESPACE : Me.Data

IRepository

public interface IRepository<T, in TId>
{
    T Insert(T entity);
    T Update(T entity);
    T Delete(T entity);
    IList<T> Get(Expression<Func<T, bool>> predicate);
    IList<T> GetAll();
    T GetById(TId id);
}

IRepositoryWebService

public interface IRepositoryWebService<T, in TId> : IRepository<T, TId> where T : class
{
    T Get(T entity);
    IList<T> GetList(T entity);
}

RepositoryWebService

public abstract class RepositoryWebService<T, TId> : IRepositoryWebService<T, TId> where T : class
{
    protected IUnitOfWork UnitOfWork;

    protected RepositoryWebService(IUnitOfWork unitOfWork) { UnitOfWork = unitOfWork; }

    #region IRepositoryWebService<TSend, TReceive> Members
    public virtual T Delete(T entity) { throw new NotImplementedException("This web service does not have an delete/del endpoint for this entity!"); }
    public virtual IList<T> Get(Expression<Func<T, bool>> predicate) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
    public virtual T Get(T entity) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
    public virtual IList<T> GetAll() { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
    public virtual T GetById(TId id) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
    public virtual IList<T> GetList(T entity) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
    public virtual T Insert(T entity) { throw new NotImplementedException("This web service does not have an insert/put endpoint for this entity!"); }
    public virtual T Update(T entity) { throw new NotImplementedException("This web service does not have an update/post endpoint for this entity!"); }
    #endregion
}

RepositoryAdo

public abstract class RepositoryAdo<T, TId> : IRepository<T, TId> where T : class, IEntity<TId>
{
    protected IUnitOfWork WorkUnit;
    protected Table<T> DataTable;

    protected RepositoryAdo(DataContext dataContext) { DataTable = dataContext.GetTable<T>(); }

    #region IRepository<T> Members
    public virtual T Insert(T entity) { DataTable.InsertOnSubmit(entity); return null; }
    public virtual T Update(T entity) { DataTable.InsertOnSubmit(entity); return null; }
    public virtual T Delete(T entity) { DataTable.DeleteOnSubmit(entity); return null; }
    public virtual IList<T> Get(Expression<Func<T, bool>> predicate) { return DataTable.Where(predicate).ToList(); }
    public virtual IList<T> GetAll() { return DataTable.ToList(); }
    public virtual T GetById(TId id)
    {
        // Sidenote: the == operator throws NotSupported Exception!
        // 'The Mapping of Interface Member is not supported'
        // Use .Equals() instead
        return DataTable.Single(e => e.Id.Equals(id));
    }
    #endregion
}

IUnitOfWork

public interface IUnitOfWork : IDisposable
{
    void Commit();
    void Rollback();
}

NAMESPACE : Me.Data.Cars

AssetConditionReportRepository

public class AssetConditionReportRepository : RepositoryWebService<CarsWS_CRInfo, string>
{
    protected new readonly UnitOfWorkIbeam UnitOfWork;

    public AssetConditionReportRepository(UnitOfWorkIbeam unitOfWork) : base(unitOfWork) { UnitOfWork = unitOfWork; }

    public override CarsWS_CRInfo Get(CarsWS_CRInfo entity)
    {
        return GetById(entity.assetID, entity.caseID);
    }
    public CarsWS_CRInfo GetById(string assetId, string caseId)
    {
        var response = UnitOfWork.Service.GetAssetConditionReport(UnitOfWork.Credentials, assetId, caseId);
        return response;
    }
}

UnitOfWorkIbeam

public class UnitOfWorkIbeam : IUnitOfWork
{
    #region Properties
    internal AssetManagementServiceSoapClient Service { get; private set; }
    internal UserCredentialsHeader Credentials { get; private set; }
    internal bool IsAllowedToRun = false;
    internal string _uid = "";
    internal string _pwd = "";
    // need to hold on to the delegate so we can add/remove it as needed...
    private readonly RemoteCertificateValidationCallback _sslCheck = (sender, certificate, chain, sslPolicyErrors) => true;
    #endregion

    #region Constructor
    public UnitOfWorkIbeam() { SetupServerCertificateValidation(); }
    protected UnitOfWorkIbeam(string userName, string password) { SetupServerCertificateValidation(userName, password); }
    #endregion

    #region Implement IUnitOfWork
    public void Commit() { /* No commit on this web service */ }
    public void Rollback() { /* No rollback on this web service */ }
    #endregion

    #region Implement IDisposable
    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
    public void Dispose() { CleanUpServerCertificateValidation(); }
    #endregion

    #region Private Helpers
    internal void SetupServerCertificateValidation(string userName = null, string password = null)
    {
        // Setup delegate to ignore SSL cert check...
        ServicePointManager.ServerCertificateValidationCallback += _sslCheck; //((sender1, certificate, chain, sslPolicyErrors) => true);

        // Setup web service...
        Service = new AssetManagementServiceSoapClient();
        Credentials = new UserCredentialsHeader { userName = userName ?? Uid, password = password ?? Pwd };
        IsAllowedToRun = true;
    }
    internal void CleanUpServerCertificateValidation()
    {
        // Setup delegate to ignore SSL cert check...
        if (ServicePointManager.ServerCertificateValidationCallback != null)
            ServicePointManager.ServerCertificateValidationCallback -= _sslCheck;

        // Setup web service...
        Service = null;
        Credentials = null;
        IsAllowedToRun = false;
    }
    #endregion
}

NAMESPACE : Me.Data.Symitar

Repositories

I am just spinning up this project and don't have any repositories designed as of yet.

UnitOfWorkSymitar

public class UnitOfWorkSymitar : IUnitOfWork
{
    #region Properties
    internal IPHostEntry IpHostInfo;
    internal IPAddress IpAddress;
    internal IPEndPoint RemotEndPoint;
    internal Socket Sender;
    internal byte[] Bytes = new byte[1024];
    internal bool IsAllowedToRun = false;
    internal string SymServerIp = "192.168.1.1";
    internal int SymPort = 11000;
    #endregion

    #region Constructor
    public UnitOfWorkSymitar() { OpenSymitarConnection(); }
    protected UnitOfWorkSymitar(string serverIp, int? port) { OpenSymitarConnection(serverIp, port); }
    #endregion

    #region Implement IUnitOfWork
    public void Commit() { /* No commit on this socket connection */ }
    public void Rollback() { /* No rollback on this socket connection */ }
    #endregion

    #region Implement IDisposable
    public void Dispose() { CloseSymitarConnection(); }
    #endregion

    #region Private Helpers
    private string OpenSymitarConnection(string serverIp = null, int? port = null)
    {
        try
        {
            IpHostInfo = Dns.GetHostEntry(serverIp ?? SymServerIp);
            IpAddress = IpHostInfo.AddressList[0];
            RemotEndPoint = new IPEndPoint(IpAddress, port ?? SymPort);
            Sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Sender.Connect(RemotEndPoint);
        }
        catch (Exception ex)
        {
            return DetermineError(ex);
        }

        IsAllowedToRun = true;
        return "Success!";
    }
    private string SendMessage(string request)
    {
        try
        {
            var msg = Encoding.ASCII.GetBytes(request + "\n");
            var bytesSent = Sender.Send(msg);
            var bytesRec = Sender.Receive(Bytes);
            var response = Encoding.ASCII.GetString(Bytes, 0, bytesRec);
            response = response.Replace("\n", "");
            return response;
        }
        catch (Exception ex)
        {
            return DetermineError(ex);
        }
    }
    private string CloseSymitarConnection()
    {
        try
        {
            Sender.Shutdown(SocketShutdown.Both);
            Sender.Close();
        }
        catch (Exception ex)
        {
            return DetermineError(ex);
        }
        finally
        {
            IsAllowedToRun = false;
        }
        return "Success!";
    }
    private static string DetermineError(Exception ex)
    {
        if (ex.GetType() == typeof(ArgumentNullException))
            return "Missing Arguments: " + Environment.NewLine + ex.GetFullMessage();
        if (ex.GetType() == typeof(SocketException))
            return "Socket Error: " + Environment.NewLine + ex.GetFullMessage();
        return "Unexpected Error: " + Environment.NewLine + ex.GetFullMessage();
    }
    #endregion
}

Use of patterns in code

public CaseNote PutData(int userId, string caseId, long status, CaseNoteType noteType, string note)
{
    // Map our objects to CARS objects...
    ...
    // package up the web request...
    ...
    // execute the web request...
    CarsWS_CaseNote response = null;
    using (var uow = new UnitOfWorkIbeam())
    {
        var caseNotesRepository = new CaseNotesRepository(uow);
        response = caseNotesRepository.Insert(userId, caseId, status, noteType, note);
    }

    // Map the CARS objects back to our objects...
    ...
    // Return our objects...
    return myCaseNote;
}

As you can see, each UnitOfWork (so far) handles the under pinnings of connection, authentication, setup and tear down. I used IDisposable to ensure that the tear down happens everytime the UnitOfWork goes out of scope. Now, some of the things I am not happy with:

  • Commit/Rollback : Hitting a 3rd party service seems to be an all or nothing proposition. The call can fail, but if something downstream fails I have no way to roll back the earlier calls.
  • GetById(TId id) : I went with this because I have found instances where ID is int, string and/or guid. However, it does not handle compound keys! It also seems to clutter up the Repository creation.

All of that said, does anyone see anything I am doing wrong or could do better? I am rewriting almost everything from the ground up and NOW is the time to make sure I have things RIGHT (or right enough to support the future).

I appreciate any feedback ya'll may have - good and bad.

\$\endgroup\$
1
\$\begingroup\$

Given all your requirements and sources of data, it makes a lot of sense to use Domain Driven Design (DDD) + Command Query Responsibility Segregation (CQRS)+ Event Sourcing (ES). (Quite a mouthful). A good reference is here. An illustration can be found here. This will allow you to move all the fault-recovery logic away from Repository to a different abstraction (Single-Responsiblity-Principle).

To support composite keys and different types of keys for the Id, you can make GetById() a generic method as follows:

T GetById<TKeyType>(TKeyType id)

That will allow interface implementations to support composite keys and multiple key types. If you do not want to create classes just for Composite keys, you can use Tuple<int,string> to specify a composite key that is an int and a string.

Also strongly recommend that you modify your implementation code to use a Inversion-Of-Control (IoC) container such as NInject and replace all creation statements. For things like the UnitOfWorkIbeam that have a scope, use a factory (again use Func<UnitOfWorkIbeam> if you do not want a new type) to hold the dependency. An IoC container allows you to disconnect dependencies and allows you to change at one place which implementation is used.

\$\endgroup\$
  • \$\begingroup\$ Love the Tuple<>! Thanks. Looking at all the rest of your suggestions. Trying not to get to deep into patterns upon patterns as my developers are junior and don't have experience with all of the suggestions. Would rather take them one step at a time. \$\endgroup\$ – Keith Barrows Nov 11 '14 at 21:30
  • \$\begingroup\$ I would at minimum request you to look at IoC. Helps a ton with maintainability and it is easy to beat into junior developers. \$\endgroup\$ – KRam Nov 12 '14 at 22:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.