Skip to content
Aaron Hanusa edited this page Feb 6, 2021 · 76 revisions

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

Possible implementations

  • Relational Databases - SQL Server, SQLite, 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

GetAllAsync

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.

GetByIDAsync

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

InsertAsync

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.

UpdateAsync

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.

DeleteAsync

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

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. Please note that this code might be written more efficiently and has been scaled back for brevity and serves only as an example.

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

public class CustomerRepository : IDataProxy<Customer, int>
{
    private readonly MyDbContext _context;
    private readonly IMapper _mapper;

    public CustomerRepository(MyDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<IEnumerable<Customer>> GetAllAsync()
    {
        var result = await _context.Set<Customer>().ToArrayAsync();
        return _mapper.Map<IEnumerable<Customer>>(result);
    }

    public async Task<Customer> GetByIDAsync(Guid id)
    {
        var result = await _context.Set<Customer>().FirstOrDefaultAsync(e => e.ID == id);
        return _mapper.Map<Customer>(result);
    }

    public async Task<Customer> InsertAsync(Customer entity)
    {
        var insertData = _mapper.Map<Customer>(entity);
        _context.Set<Customer>().Add(insertData);
        await _context.SaveChangesAsync();
        return _mapper.Map<Customer>(insertData);
    }

    public async Task<Customer> UpdateAsync(Customer entity)
    {
        var updateData = _mapper.Map<Customer>(entity);
        _context.Attach(updateData).State = EntityState.Modified;
        await _context.SaveChangesAsync();
        return _mapper.Map<Customer>(updateData);
    }

    public async Task DeleteAsync(Guid id)
    {
        var entity = new Customer();
        entity.ID = id;
        _context.Attach(entity).State = EntityState.Deleted;
        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.

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.