
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:
1public interface IWeatherClient
2{
3 [Get("/weather")]
4 Task<WeatherResponseModel> GetWeather();
5
6 [Get("/weather/{zipCode}")]
7 Task<WeatherResponseModel> GetWeather(string zipCode);
8}
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!
1public class WeatherProvider
2{
3 private readonly IWeatherClient _weatherClient;
4 private readonly WeatherOptions _weatherOptions;
5
6 public WeatherProvider(IWeatherClient weatherClient, IOptions<WeatherOptions> weatherOptions)
7 {
8 _weatherClient = weatherClient;
9 _weatherOptions = weatherOptions.Value;
10 }
11
12 public async Task<WeatherResponseModel> GetWeather()
13 {
14 // add authentication
15 // add optional handlers
16
17 // add try/catch error handling
18 return await _weatherClient.GetWeather();
19 }
20}
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”).
1public class CreateUserService
2{
3 private IUserAccountRepository _userAccountRepository;
4 private IUserProfileRepository _userProfileRepository;
5
6 public CreateUserService(IUserAccountRepository userAccountRepository, IUserProfileRepository userProfileRepository)
7 {
8 _userAccountRepository = userAccountRepository;
9 _userProfileRepository = userProfileRepository
10 }
11
12 public Task<UserAccountId> CreateUser(string userName, string email)
13 {
14 // database operation
15 // ..
16
17
18 return createdUser;
19 }
20}
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.

Comments