.NET Naming Best Practises: Services vs. Providers

.NET Naming Best Practises: Services vs. Providers

.NET Naming Best Practises: Services vs. Providers

In many code bases there are Services and Providers - and often they do the same thing structurally; but what is the idea of a Service- and Provider-Classes?

Provider

Provider-Classes usually represent an abstraction of an external dependency, e.g. an external API, File Syste, external System etc. They are usually very specific and have a very specific interface.
The most important point: they do not contain any application logic! They are only there to encapsulate the external dependency.

Example: Weather API

A weather API initially has a Client; this client defines which endpoints this API makes available. Depending on this, clients are static - they are passed the HTTP client as a parameter or not; e.g. if you use Refit.

Since I am a big fan of Refit and recommend it for such scenarios, this example is also based on Refit:

public interface IWeatherClient
{
    [Get("/weather")]
    Task<WeatherResponseModel> GetWeather();

    [Get("/weather/{zipCode}")]
    Task<WeatherResponseModel> GetWeather(string zipCode);
}

A provider is now responsible for handling the overhead that a client needs - e.g. caching, authentication, or options and very importantly: an abstraction of potential exceptions!

public class WeatherProvider
{
    private readonly IWeatherClient _weatherClient;
    private readonly WeatherOptions _weatherOptions;

    public WeatherProvider(IWeatherClient weatherClient, IOptions<WeatherOptions> weatherOptions)
    {
        _weatherClient = weatherClient;
        _weatherOptions = weatherOptions.Value;
    }

    public async Task<WeatherResponseModel> GetWeather()
    {
        // add authentication
        // add optional handlers

        // add try/catch error handling
        return await _weatherClient.GetWeather();
    }
}

As Providers do not contain any application logic, but are only responsible for abstracting external dependencies, it is very often the case that providers can be registered as singletons in your DI. In most cases, Provider does not interact with any other services, providers or event systems.

Services

While providers are primarily responsible for technical abstraction, services are generally the opposite: Services contain the application logic. Services are either implemented as large, unfortunately often monolithic logic modules (e.g. "UserService"), or smaller as so-called use case services aka handlers (e.g. "CreateUserService / CreateUserHandler").

public class CreateUserService
{
    private IUserAccountRepository _userAccountRepository;
    private IUserProfileRepository _userProfileRepository;

    public CreateUserService(IUserAccountRepository userAccountRepository, IUserProfileRepository userProfileRepository)
    {
        _userAccountRepository = userAccountRepository;
        _userProfileRepository = userProfileRepository
    }

    public Task<UserAccountId> CreateUser(string userName, string email)
    {
        // database operation
        // ..


        return createdUser;
    }
}

Services usually interact with other components of an application (database, various providers, event system) and are therefore registered as scoped in the DI system.

Conclusion

There is a very clear separation between what providers are responsible for and what task services fulfill - you just have to implement it in your own architecture. If other code bases do not follow these basic rules, this is no excuse for your own architectural mistakes.