Skip to main content
2 of 4
added 1826 characters in body

How far should I go with abstraction?

Note: This post has been copied from this StackOverflow topic Edit: Made the code more real so the question can be replied to. Sorry, everyone.

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: IDnsUserBlacklistService
{
    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.

I hope I was clear, thanks for reading if you made it to the end ;-)