Oct 26, 2018

|

by: james

|

Tags: API, RESTful API

|

Categories: Tech Tips

A Generic Approach to RESTful APIs

A Generic Approach to RESTful APIs

In the world of agile and incremental development, it’s pretty typical for stakeholders and developers alike to want to get to a functional minimum viable product as soon as possible.  This can often lead to a spirited debate about how to design a product that solves those Day One needs but can easily be scaled or extended to meet the needs of a growing user base.

To that end, we’ve leveraged a number of patterns that can help get to that initial release product quickly but doesn’t box us into a design that has to be scrapped when volume exceeds expectation.

Repository Pattern

If you have done any development that covers data access, chances are that you have come across the Repository pattern.  The idea behind the pattern is that data access is hidden behind one or more classes that abstract how an object is persisted or retrieved.

A common approach is to have a Repository per entity that needs to be persisted.  So, in a complex system, you might have hundreds of repositories that each implement methods to insert an entity, select an entity by key, update an entity, or delete it.

A common interface for this might look something like this:

    public interface IRepository<T> where T : new()
    {
        T Get(long id);
        IList<T> Get();
        long Insert(T entity);
        void Update(T entity);
        void Delete(long id);
    }

And a concrete implementation of that repository might look something like this:

    public class CustomerRepository : IRepository<Customer>
    {
        private string ConnectionString = ConfigurationManager.
            ConnectionStrings[“Example”].ConnectionString;
        public void Delete(long id)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                connection.Open();
                connection.Execute(“DELETE FROM Customer WHERE Id = @Id”, new { Id = id });
            }
        }
        public Customer Get(long id)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                connection.Open();
                return connection.Query<Customer>(“SELECT * FROM Customer WHERE Id = @Id”,
                    new { Id = id }).FirstOrDefault();
            }
        }
        public IList<Customer> Get()
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                connection.Open();
                return connection.Query<Customer>(“SELECT * FROM Customer WHERE Id = @Id”.
                    ToList();
            }
        }
        public long Insert(Customer entity)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                connection.Open();
                return connection.Query<long>(@”INSERT INTO Customer
                    (Name, Address, Phone) VALUES (@Name, @Address, @Phone);
                    SELECT last_insert_rowid()”, entity).First();
            }
        }
        public void Update(Customer entity)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                connection.Open();
                connection.Query<Customer>(@”UPDATE Customer
                    SET Name = @Name, Address = @Address, Phone = @Phone
                    WHERE Id = @Id”, entity);
            }
        }
    }

Clearly, something like this makes for a nice separation of business logic and data access.  This has a number of benefits: because CustomerRepository is coded behind the IRepository interface, it can be easily be refactored without downstream impact to client code and business logic can be tested against a test implementation that implements IRepository without needing to have database connectivity in those unit test cases.

Leveraging a Generic Repository

After implementing a few of these repositories, you may start to see a pattern, and the redundancy of these operations across an inordinate number of entities may get tiring.  Don’t fret — the Generic Repository Pattern is here!

As the name implies, a Generic Repository is a way to handle data access for all entities through a single class.  Instead of the concrete implementation of the CustomerRepository as above, we’d leverage a GenericRepository that implements the IRepository interface to handle all entities.

Here is an implementation of a GenericRepository that follows a few conventions – all tables have a primary key called Id and each Entity’s properties are named to match the column names in the analogous database table:

    public class GenericRepository<T> : IRepository<T> where T : new ()
    {
        private string ConnectionString = ConfigurationManager.ConnectionStrings[“Example”].ConnectionString;
        public void Delete(long id)
        {
            var t = new T();
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                connection.Open();
                connection.Execute(“DELETE FROM ” + typeof(T).Name + ” WHERE Id = @Id”, new { Id = id });
            }
        }
        public T Get(long id)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                var t = new T();
                connection.Open();
                return connection.Query<T>(“SELECT * FROM ” + typeof(T).Name + ” WHERE Id = @Id”,
                    new { Id = id }).FirstOrDefault();
            }
        }
        public IList<T> Get()
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                var t = new T();
                connection.Open();
                return connection.Query<T>(“SELECT * FROM ” + typeof(T).Name).ToList();
            }
        }
       public long Insert(T entity)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                var properties = entity.GetType().GetProperties().Where(x=>x.Name != “Id”).
                    Select(x => x.Name);
                connection.Open();
                return connection.Query<long>(“INSERT INTO ” + entity.GetType().Name + ” ” +
                    “(” + string.Join(“,”, properties) + “) VALUES (” +
                    string.Join(“,”, properties.Select(x=>”@” + x)) + “); ” +
                    “SELECT last_insert_rowid()”, entity).First();
            }
        }
        public void Update(T entity)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                var properties = entity.GetType().GetProperties().Where(x=>x.Name != “Id”).
                    Select(x => x.Name);
                connection.Open();
                connection.Query<T>(“UPDATE ” + entity.GetType().Name +
                    ” SET ” + string.Join(“,”, properties.Select(x=>x + ” = @” + x)) +
                    ” WHERE Id = @Id”, entity);
            }
        }
    }

There are fans of the Generic Repository Pattern, and others will tell you that striving to implement the same contract generically across all your application objects and handling their data storage demands universally is too great a generalization and it can’t pay enough attention to the specifics of how a specific use case may need to store, filter, or access data.

I’ve never understood the argument against using a Generic Repository, namely because you can always implement a specific repository overriding some or all of the exposed methods and extending the class with new methods.  In fact, in our example, this implementation of a GenericRepository above can sit side-by-side with a derived implementation of the GenericRepository that serves Customer entities. So, if you have specific needs for Customer – perhaps you need to implement a different filter or search, or need to provide a different data store, or you are using a different ORM, you can implement that directly in this new CustomerRepository.

Making a Generic RESTful API

Because RESTful APIs often leverage HTTP methods GET, PUT, POST, and DELETE and map them to analogous data access CRUD operations, building a Generic API and coupling that with a Generic Repository provides a basic framework that can be an easy way to bootstrap a project and get moving toward a minimum viable product.

While we understand that you may not want every business object to map directly to a database entity and expose direct data access CRUD operations to it by way of this framework, it certainly is something that needs to be done a good amount of time.  Of course, just like what we mentioned regarding the Generic Repository pattern, you can always implement something more detailed and override the default behavior where the use cases dictate.

Here is an example of what a GenericController might look like:

    public class GenericController<T> : ApiController where T : new ()
    {
        private GenericRepository<T> genericRepository_ = new GenericRepository<T>();
        public virtual IHttpActionResult Delete(long id)
        {
            var deleted = genericRepository_.Get(id);
            if (deleted == null)
            {
                return NotFound();
            }
            genericRepository_.Delete(id);
            return Ok(deleted);
        }
        public virtual IHttpActionResult Get(long id)
        {
            var model = genericRepository_.Get(id);
            if (model == null)
            {
                return NotFound();
            }
            return Ok(model);
        }
        public virtual IHttpActionResult Get()
        {
            return Ok(genericRepository_.Get());
        }
        public virtual IHttpActionResult Post(T model)
        {
            var id = genericRepository_.Insert(model);
            var updatedModel = model as dynamic;
            updatedModel.Id = id;
            return Created<T>(model.GetType().Name + ‘/’ + id.ToString(), updatedModel);
        }
        public virtual IHttpActionResult Put(T model)
        {
            genericRepository_.Update(model);
            return Ok(model);
        }
    }

As you can see, the code is very straightforward and essentially maps the GET, PUT, POST, and DELETE methods over to their analogous repository methods.

To access this functionality, the only thing you need to do is create a controller specific to the model that you want to follow the generic pattern.  For a Customer, that might look like this:

    public class CustomerController : GenericController<Customer>
    {
    }

We’ve actually created a Customer model that is a one to one mapping to the Customer entity.  That affords us the capability of altering that model from the underlying data access object.  There isn’t any explicit mapping needed here in the generic implementation so long as you follow the same naming convention for properties on each side.

Of course, because the Controller methods are virtual, you can choose to supply your own implementations in those cases that your needs are more complex.

Hopefully, this allows you to get started quickly and gets you to a point where you are spending your cycles on the hard core bits of your project rather than on some of the type of code that can be very redundant.