jasontaylordev / NorthwindTraders

Northwind Traders is a sample application built using ASP.NET Core and Entity Framework Core.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Implementing CQRS in the infrastructure layer.

heyitsabby opened this issue · comments

How can I use CQRS in the infrastructure layer when I have to communicate with an external API? I'm using a wrapper library, SpotifyAPI-NET to simplify calling Spotify API.

In my Application layer, I have ISpotifyService:

using System.Collections.Generic;
using System.Threading.Tasks;
using LDR.Application.Common.Models;

namespace LDR.Application.Common.Interfaces
{
    public interface ISpotifyService
    {
        Task<ArtistsViewModel> FindArtists(string searchQuery);

        Task<Artist> GetArtist(string id);

        // More commands and queries below...
    }
}

The models above are in the Application layer, so I can reference them anywhere else.

And in the Infrastructure layer, I implement SpotifyService:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LDR.Application.Common.Interfaces;
using LDR.Application.Common.Models;
using SpotifyAPI.Web;
using SpotifyAPI.Web.Auth;
using SpotifyAPI.Web.Enums;
using SpotifyAPI.Web.Models;

namespace LDR.Infrastructure
{
    public class SpotifyService : ISpotifyService
    {
        private SpotifyWebAPI _spotify;
        private Token _token;

        private string _clientId = Environment.GetEnvironmentVariable("SpotifyClientId");
        private string _secretId = Environment.GetEnvironmentVariable("SpotifySecretId");

        public async Task<ArtistsViewModel> FindArtists(string searchQuery)
        {
            await Authenticate();

            var results = _spotify?.SearchItemsEscaped(searchQuery, SearchType.Artist);

            var artists = results?.Artists.Items
                .Select(artist => new Artist {Name = artist.Name, SpotifyId = artist.Id})
                .ToHashSet();

            return new ArtistsViewModel {Artists = artists};
        }

        public async Task<Artist> GetArtist(string id)
        {
            await Authenticate();

            var result = await _spotify.GetArtistAsync(id);

            return new Artist
            {
                Name = result.Name, 
                SpotifyId = result.Id,
                Popularity = result.Popularity,
                Genres = result.Genres
            };
        }

        private async Task Authenticate()
        {
            // If token is not null or is not expired, do nothing
            if (_token == null || _token.IsExpired())
            {
                var auth = new CredentialsAuth(_clientId, _secretId);

                _token = await auth.GetToken();

                _spotify = new SpotifyWebAPI {AccessToken = _token.AccessToken, TokenType = _token.TokenType};
            }
        }
    }
}

This setup works but I'd like to separate the queries similar to how it's done with CQRS + Mediatr in the app layer. The reason being that I have more commands + queries I have to implement and I want to add validation to each. I'd rather not have everything in one class.

With CQRS style, I can inject SpotifyService via DI into my Handler's constructor and use it as shown above. The problem comes when I have to reference the CommandHandler inside my controllers. Right now, trying to do that introduces a dependency on the Infrastructure layer, which I want to avoid.

Perhaps CQRS is not the best approach here, but I'm after something similar in terms of simplicity and organization. I'm not sure how else to separate my interface implementation into separate files.