Skip to content
Aaron Hanusa edited this page Nov 26, 2015 · 76 revisions

IDataProxy is the actor within the Peasy Framework that is responsible for data storage and retrieval. An implementation of the Repository Pattern, IDataProxy serves as an abstraction layer for data stores that encompass (but not limited to) the following:

Possible implementations

  • Relational Databases - SQL Server, MySQL, Oracle, etc.
  • Document (NoSQL) Databases - RavenDB, MongoDB, VelocityDB, etc.
  • Services - HTTP, SOAP, etc.
  • Cache Stores - ASP.NET & Azure caching, Redis, etc.
  • Queues - MSMQ, RabbitMQ, etc.
  • File System
  • In-memory data stores for testing

Abstracting the data store layer allows you to swap data proxy implementations within your service classes to deliver solutions that are scalable and testable, and to support a multitude of deployment scenarios, all while being able to have them subjected to the command execution pipeline.

Public methods

Synchronously returns all values from a data source in the form of an enumerable list IEnumerable<T>, where T is a DTO that implements IDomainObject<TKey>, and is especially useful for lookup data.

Asynchronously returns all values from a data source in the form of an enumerable list IEnumerable<T>, where T is a DTO that implements IDomainObject<TKey>, and is especially useful for lookup data.

Accepts the id of the entity that you want to query and synchronously returns a DTO that implements IDomainObject<TKey>.

Accepts the id of the entity that you want to query and asynchronously returns a DTO that implements IDomainObject<TKey>.

Accepts a DTO that implements IDomainObject<TKey> and synchronously inserts it into the data store. This method should return a new DTO instance with updated state.

Accepts a DTO that implements IDomainObject<TKey> and asynchronously inserts it into the data store. This method should return a new DTO instance with updated state.

Accepts a DTO that implements IDomainObject<TKey> and synchronously updates it in the data store. This method should return a new DTO instance with updated state.

Accepts a DTO that implements IDomainObject<TKey> and asynchronously updates it in the data store. This method should return a new DTO instance with updated state.

Accepts the id of the entity that you want deleted and synchronously deletes it from the data store.

Accepts the id of the entity that you want deleted and asynchronously deletes it from the data store.

Implementing IDataProxy

IDataProxy implements ISupportCRUD and ISupportCRUDAsync. When implementing IDataProxy, you must provide all of the method signatures defined by the interface. However, you can choose to provide an actual implementation for any or all of these methods.

For example, you might only want to expose synchronous functionality that retrieves lookup values, but it may not make sense to provide insert, update, or delete functionality. Further, you may want to only support synchronous behavior if your data store does not provide the means to communicate with it asynchronously.

If this seems like a code smell, you are free to implement ISupportCRUD or ISupportCRUDAsync directly. If that's not granular enough, each of these interfaces is defined by other interfaces that provide CRUD functionality at the most granular level. Note that if you choose this route you will have to create your own service base, service command, and/or command implementations.

Sample implementation using Entity Framework

The following code sample shows what an implementation of IDataProxy serving as a customer data store might look like using Entity Framework 6.0 or higher. Please note that this code might be written more efficiently and has been scaled back for brevity and serves only as an example.

Also note that because this example uses Entity Framework 6.0, we take advantage of the asynchronous support in the async methods. Versions prior to 6.0 do not provide async support, therefore when using any prior version of Entity Framework, you might not want to provide async functionality.

Lastly, this example uses Automapper(Mapper.Map) to perform mapping logic against the customer DTOs and Entity Framework Models.

public class CustomerRepository : IDataProxy<Customer, int> 
{
    public IEnumerable<Customer> GetAll()
    {
        using (var context = new EFContext())
        {
            var data = context.tCustomers
                              .Select(Mapper.Map<tCustomer, Customer>)
                              .ToArray();
            return data;
        }
    }

    public Customer GetByID(int id)
    {
        using (var context = new EFContext())
        {
            var data = context.tCustomers
                              .Where(b => b.CustomerID == id)
                              .ToList()
                              .Select(Mapper.Map<tCustomer, Customer>)
                              .FirstOrDefault();
            return data;
        }
    }

    public Customer Insert(Customer entity)
    {
        using (var context = new EFContext())
        {
            var data = Mapper.Map(entity, new tCustomer());
            context.Set<tCustomer>().Add(data);
            context.SaveChanges();
            entity.ID = data.CustomerID;
            return entity;
        }
    }

    public Customer Update(Customer entity)
    {
        using (var context = new EFContext())
        {
            var data = Mapper.Map(entity, new tCustomer());
            context.Set<tCustomer>().Attach(data);
            context.Entry<tCustomer>(data).State = EntityState.Modified;
            context.SaveChanges();
            entity = Mapper.Map(data, entity);
            return entity;
        }
    }

    public void Delete(int id)
    {
        using (var context = new EFContext())
        {
            var Customer = new tCustomer() { CustomerID = id };
            context.Set<tCustomer>().Attach(Customer);
            context.Set<tCustomer>().Remove(Customer);
            context.SaveChanges();
        }
    }

    public async Task<IEnumerable<Customer>> GetAllAsync()
    {
        using (var context = new EFContext())
        {
            var data = await context.tCustomers.ToListAsync();
            return data.Select(Mapper.Map<tCustomer, Customer>).ToArray();
        }
    }

    public async Task<Customer> GetByIDAsync(int id)
    {
        using (var context = new EFContext())
        {
            var data = await context.tCustomers
                                    .Where(b => b.CustomerID == id)
                                    .ToListAsync();

            return data.Select(Mapper.Map<tCustomer, Customer>).FirstOrDefault();
        }
    }

    public async Task<Customer> InsertAsync(Customer entity)
    {
        using (var context = new EFContext())
        {
            var data = Mapper.Map(entity, new tCustomer());
            context.Set<tCustomer>().Add(data);
            await context.SaveChangesAsync();
            entity.ID = data.CustomerID;
            return entity;
        }
    }

    public async Task<Customer> UpdateAsync(Customer entity)
    {
        using (var context = new EFContext())
        {
            var data = Mapper.Map(entity, new tCustomer());
            context.Set<tCustomer>().Attach(data);
            context.Entry<tCustomer>(data).State = EntityState.Modified;
            await context.SaveChangesAsync();
            entity = Mapper.Map(data, entity);
            return entity;
        }
    }

    public async Task DeleteAsync(int id)
    {
        using (var context = new EFContext())
        {
            var Customer = new tCustomer() { CustomerID = id };
            context.Set<tCustomer>().Attach(Customer);
            context.Set<tCustomer>().Remove(Customer);
            await context.SaveChangesAsync();
        }
    }
}

Swappable Data Proxies

Because service classes have a dependency upon the IDataProxy abstraction, this means that data proxies can be swapped out and replaced with different implementations. The ability to swap data proxies provides the following benefits:

In many production systems, in-process client applications such as WPF and Windows Forms are often configured to communicate directly with a database. This configuration can lead to bottlenecks and poor performance over time as more clients are added to the environment. Peasy allows you to easily remedy this type of situation by swapping a data proxy that communicates directly with a database with one that scales more efficiently.

For example, instead of injecting data proxies that communicate directly with the database, you could inject data proxies that communicate with a cache or queue. For retrieval, the cache might return cached data, and for storage, you might update the cache or a queue and have a windows service monitoring it for changes. The windows service would then be responsible for data storage manipulation against a persistent data store (database, file system, etc).

Another possible solution would be to expose CRUD operations via HTTP services, and inject a data proxy capable of performing CRUD actions against your services into your service class. A side benefit of having clients use HTTP services is that you could gain the benefits of HTTP Caching, almost for free, which can provide scalability gains.

Because service classes rely on IDataProxy for data store abstraction, introducing solutions that offer scalability gains becomes almost trivial.

Multiple deployment scenarios

Because data proxies are swappable, you are able to reconfigure data storage without having to refactor your code. Let's take a scenario where an organization has deployed a WPF application that directly communicates with a database. As time passes, the organization receives third party pressure to expose the database to the outside world via web services (HTTP, SOAP, etc.).

After the web services are created, it is decided that the WPF application should dogfood the web services and no longer communicate directly with the database. Consuming service classes that abstract data stores via IDataProxy would easily allow swapping out the database data proxies with proxies that communicate with the web services.

Testability

Unit testing should be fast. Really fast. Databases are slow and come with a slew of issues when testing your code via automated unit tests. This is another scenario where swappable data proxies provide great benefit. By creating mock data proxies, you can easily inject them into your service classes and focus on testing initialization logic, validation and business rule logic, and command logic.

Available concrete implementations

This is a place holder for what will soon be a list of available concrete data store libraries and nuget packages. These libraries will contain base class implementations that abstract communications with varying data stores (Databases, Queues, Caches, Web Services (HTTP and SOAP), Mocks, etc.) utilizing different technologies (Entity Framework, MSMQ, HTTPClient (System.Net.Http), etc.)

No IQueryable support?

Simply put, IQueryable is a difficult interface to implement. Because data proxy implementations can come in so many flavors, IEnumerable methods were chosen to ease the pain of data proxy implementations.

Of course you are always free to extend your data proxies and service classes to expose IQueryable methods.

Clone this wiki locally