I've been trying for a while to write clean and testable code, with the help of DI and abstractions.
I found myself guilty of service-itis, creating 3 or 4 interfaces for something that clearly doesn't need it. So I decided to relax, and only abstract what really needs it.
I'm faced with a problem where I'm not sure what is the best way to solve.
Let me explain, the requirements are pretty straightforward.
The application must determine, when a user wants to sign up, if he's blacklisted, based on a couple of information about the user
The service providing the information about the possible blacklisting of the player is external, and not accessible from our development platforms.
OK, I see here a need for an interface to abstract the blacklisting service so I can test properly the actions that rely on the fact that a user may be blacklisted, so I come up with a cery basic interface:
public interface IUserBlacklistService
{
Boolean IsUserBlacklisted(IsUserBlacklistedRequest request);
}
public class IsUserBlacklistedRequest
{
public String FirstName
{
get;
set;
}
public String LastName
{
get;
set;
}
public DateTime BirthDate
{
get;
set;
}
public String BirthCity
{
get;
set;
}
public String BirthCountry
{
get;
set;
}
public String BirthDepartment
{
get;
set;
}
}
Now, the actual service I have to deal with works this way:
The blacklist is exposed as a DNS server, meaning I have to make a DNS query, asking for hashofsomeoftheuserinformation.somedomain.tld, something like firstnamelastnamebirthdate.somedomain.tld
If the provided information matches entries in the blacklist, the DNS server replies with TXT records containing:
- A set of information for French user
- Another set of information for other users
I then have to match this information with the information the user filled to know if he's blacklisted or not.
At this point, I'm thinking of creating the DnsUserBlacklistService
class implementing the IUserBlacklistService
, but also of creating another abstraction representing only the DNS query, let's call it IDnsService
, which will look like:
public interface IDnsService
{
IEnumerable<String> GetRecords(String queriedDomain);
}
This, in my mind, will allow me to safely test:
- That the queried domain is the right one, so I know I build the hash correctly from the user information
- That the matching between the returned TXT records and the user information is correct
So the class DnsUserBlacklistService
would look like this:
public class DnsUserBlacklistService: IUserBlacklistService
{
private readonly IDnsService _dnsService;
private readonly String _domain;
public DnsUserBlacklistService(IDnsService dnservice, String domain)
{
_dnsService = dnsService;
_domain = domain;
}
public Boolean IsUserBlacklisted(IsUserBlacklistedRequest request)
{
var subDomainToQuery = GetSubDomainToQuery(request);
var fullDomainToQuery = String.Format("{0}.{1}", subDomainToQuery, _domain);
var dnsRecords = _dnsService.GetRecords(fullDomainToQuery);
if (dnsRecords != null && dnsRecords.Any())
{
// Match between records and user information
// Note: I know I shouldn't write pseudo-code, but this part
// is really unrelevant to the question
}
// If no DNS records, assume the user is not blacklisted
return false;
}
private String GetSubDomainToQuery(IsUserBlacklistedRequest request)
{
return String.Concat(
request.FirstName,
request.LastName,
request.BirthDate.ToString("yyyyMMdd")
);
}
}
It seems to me that having everything in the DnsUserBlacklistService
class wouldn't allow me to test thoroughly the process. I could have some internal
methods being exposed to the test project with an [InternalsVisibleTo]
attribute, like the hash construction, but that doesn't abstract me from the actual DNS query, which is the one external service I don't own.
In the end, my question is: am I going too far with abstractions?
Maybe some thoughts on this particular example could help me decide in the future where to stop.