Abstractions are of immense importance in software development. They allow us to compartmentalise code and enable ‘pluggable’ software systems. However, we must carefully design our abstractions if they are to live up to these lofty expectations.
In particular, it’s essential for us to carefully consider our abstractions and ensure they don’t leak implementation details.
Consider the following interface, ICustomerRepository:
Interface ICustomerRepository represents an abstraction for a customer database or other persistence mechanism. You might also have noticed that a developer created it from a concrete SQL implementation. Here is ICustomerRepository again with highlights indicating its ancestry:
There are several hints of ICustomerRepository‘s origin:
GetCustomer() returns a SQL model,
AddSqlCustomer() has ‘Sql’ in its name,
UpdateCustomer() takes a SQL model parameter.
ICustomerRepository is leaking SQL-ness implementation details like a sieve. But why is this a problem? Well, what if we wanted to replace our SQL Server customer database with a MongoDb one? Could we implement a Mongo DB version of ICustomerRepository?
Here is what that might look like:
public class MongoDbCustomerRepository : ICustomerRepository { public AbcFinance.SqlServer.Customer GetCustomer() { /* Mongo DB implementation */ } public void AddSqlCustomer(BusinessLogic.Customer customer) { /* Mongo DB implementation */ } public void UpdateCustomer(Guid customerId, AbcFinance.SqlServer.Customer customer) { /* Mongo DB implementation */ } }
Looks weird, right?! We are using SQL types with a non-SQL Mongo DB database! One of the methods has ‘Sql’ in the name, but it’s not a SQL implementation. No, this will not do at all.
We aspire to build interfaces that are not leaking implementation details. Superior abstractions use neutral naming and models. We want to design an abstraction so that we could develop multiple implementations for it.
Let’s try again with a revised version of ICustomerRepository:
That’s better. The methods have implementation-agnostic names. Customer is a neutral business logic model, devoid of implementation specifics, not a SQL model.
This improved ICustomerRepository plays nicely with both a SQL Server and a Mongo DB implementation:
public class SqlServerCustomerRepository : ICustomerRepository { public BusinessLogic.Customer GetCustomer() { /* SQL Server implementation */ } public void AddCustomer(BusinessLogic.Customer customer) { /* SQL Server implementation */ } public void UpdateCustomer(Guid customerId, BusinessLogic.Customer customer) { /* SQL Server implementation */ } }
public class MongoDbCustomerRepository : ICustomerRepository { public BusinessLogic.Customer GetCustomer() { /* Mongo DB implementation */ } public void AddCustomer(BusinessLogic.Customer customer) { /* Mongo DB implementation */ } public void UpdateCustomer(Guid customerId, BusinessLogic.Customer customer) { /* Mongo DB implementation */ } }
SqlServerCustomerRepository and MongoDbCustomerRepository work well with ICustomerRepository. Both implementations hide their database-specific details deep inside their methods.
Let’s Avoid Leaky Abstractions and design implementation-neutral abstractions instead.
Avoid Leaky Abstractions
/by Olaf ThielkeAvoid Leaky Abstractions
It’s critical to create the ‘right’ kind of Abstractions—while it’s easy to develop awkward ‘leaky’ Abstractions.
Yesterday we covered off why Interfaces are Abstractions—a primer for today’s topic.
Abstractions are of immense importance in software development. They allow us to compartmentalise code and enable ‘pluggable’ software systems. However, we must carefully design our abstractions if they are to live up to these lofty expectations.
In particular, it’s essential for us to carefully consider our abstractions and ensure they don’t leak implementation details.
Consider the following interface,
ICustomerRepository
:Interface
ICustomerRepository
represents an abstraction for a customer database or other persistence mechanism. You might also have noticed that a developer created it from a concrete SQL implementation. Here isICustomerRepository
again with highlights indicating its ancestry:There are several hints of
ICustomerRepository
‘s origin:GetCustomer()
returns a SQL model,AddSqlCustomer()
has ‘Sql’ in its name,UpdateCustomer()
takes a SQL model parameter.ICustomerRepository
is leaking SQL-ness implementation details like a sieve. But why is this a problem? Well, what if we wanted to replace our SQL Server customer database with a MongoDb one? Could we implement a Mongo DB version ofICustomerRepository
?Here is what that might look like:
Looks weird, right?! We are using SQL types with a non-SQL Mongo DB database! One of the methods has ‘Sql’ in the name, but it’s not a SQL implementation. No, this will not do at all.
We aspire to build interfaces that are not leaking implementation details. Superior abstractions use neutral naming and models. We want to design an abstraction so that we could develop multiple implementations for it.
Let’s try again with a revised version of
ICustomerRepository
:That’s better. The methods have implementation-agnostic names. Customer is a neutral business logic model, devoid of implementation specifics, not a SQL model.
This improved
ICustomerRepository
plays nicely with both a SQL Server and a Mongo DB implementation:SqlServerCustomerRepository
andMongoDbCustomerRepository
work well withICustomerRepository
. Both implementations hide their database-specific details deep inside their methods.Let’s Avoid Leaky Abstractions and design implementation-neutral abstractions instead.