What is Clean Architecture: Part 10 - Writing the Application Logic in the Request Handler

mohamed Tayel - Sep 3 - - Dev Community

Introduction

Welcome back to the "What is Clean Architecture" series! We've covered a lot of ground so far, exploring essential architectural principles and setting up our projects. Now, it's time to bring all of these concepts to life by implementing the actual business logic within our request handlers.

In this part, we'll walk through integrating MediatR and AutoMapper into our application, creating request and request handler classes, and finally, preparing everything to be registered in the IServiceCollection. This is where the rubber meets the road—transforming contracts into meaningful implementation code.

If you're not familiar with MediatR or AutoMapper or CQRS, I highly recommend checking out my previous articles on these topics:

With that foundation in place, let's dive into the implementation!

Step 1: Setting Up the Project Structure

Adding Feature Folders

Feature Folder

To maintain a clean and organized project structure, we'll start by creating feature-specific folders under the Application layer. This helps in grouping related classes together, making the project more maintainable.

  1. Create a folder named Features under the Application layer.

    • This folder will contain all the business logic related to different features in the application.
  2. Under the Features folder, create another folder named Events.

    • The Events folder will encapsulate all the logic related to the event management feature.

Events Folder

  1. Within the Events folder, create a subfolder named Queries.
    • This folder will contain all the query logic related to events.

Queries

Here's how the folder structure should look so far:

 Folder Structure

Step 2: Creating Query Folders

Within the Queries folder, we'll create specific folders for each query:

  1. Create a folder named GetEventDetail under Queries.
    • This folder will contain the query and handler classes for fetching event details.

GetEventDetail

  1. Create another folder named GetEventsList under Queries.
    • This folder will contain the query and handler classes for fetching a list of events.

GetEventsList

Your Queries folder structure should now look like this:

Step 2: Query Folders

Step 3: Creating the Mapping Profile

Next, we'll create a folder for our AutoMapper profiles:

  1. Create a folder named Profiles under the Application layer.

    • This folder will contain all AutoMapper profiles that define the mappings between domain entities and ViewModels.
  2. Create a class named MappingProfile.cs under the Profiles folder.

    • This class will define the mappings needed for our application.

Here's how the folder structure with the MappingProfile should look:

Step 3: Mapping Profile

Step 4: Implementing the Query and Handler Classes

Now that the folder structure is in place, let's create the query and handler classes:

  1. In the GetEventDetail folder, create the following classes:

    • GetEventDetailQuery.cs: This class will define the query for fetching event details.
    • GetEventDetailQueryHandler.cs: This class will handle the logic for the GetEventDetailQuery.
    • EventDetailVm.cs: This class will act as the ViewModel for event details.
    • CategoryDto.cs: This DTO will represent the category information within EventDetailVm.
  2. In the GetEventsList folder, create the following classes:

    • GetEventsListQuery.cs: This class will define the query for fetching a list of events.
    • GetEventsListQueryHandler.cs: This class will handle the logic for the GetEventsListQuery.
    • EventListVm.cs: This class will act as the ViewModel for the list of events.

Here’s how the structure should look with all classes in place:

Step 4: GetEventDetail Folder

Step 5: Adding Package References

With the folder and class structure in place, let's add the necessary packages:

  1. Add the MediatR and AutoMapper packages to your project via NuGet. You can do this via the NuGet Package Manager or by adding them directly to your .csproj file:
   <ItemGroup>
     <PackageReference Include="MediatR" Version="12.3.0" />
     <PackageReference Include="AutoMapper" Version="12.0.0" />
     <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
   </ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Here’s how the package references should look in your project file:

Step 5: Package References

Step 6: Implementing the Request Handler Logic

Now that our project structure and dependencies are set up, let's implement the business logic.

GetEventsListQuery and Handler

The GetEventsListQuery class represents a request to retrieve a list of events. Here’s how it looks:

//EventListVm
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsList
{
    public class EventListVm
    {
        public Guid EventId { get; set; }
        public string Name { get; set; } = string.Empty;
        public DateTime Date { get; set; }
        public string ImageUrl { get; set; } = string.Empty;
    }
}
// GetEventsListQuery.cs
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries
{
    public class GetEventsListQuery:IRequest<List<EventListVm>>
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

And here’s the handler that processes this query:

// GetEventsListQueryHandler.cs

namespace GloboTicket.TicketManagement.Application.Features.Events.Queries
{
    public class GetEventsListQueryHandler : IRequestHandler<GetEventsListQuery, List<EventListVm>>
    {
        private readonly IAsyncRepository<Event> _eventRepository;
        private readonly IMapper _mapper;

        public GetEventsListQueryHandler(IMapper mapper, IAsyncRepository<Event> eventRepository)
        {
            _mapper = mapper;
            _eventRepository = eventRepository;
        }

        public async Task<List<EventListVm>> Handle(GetEventsListQuery request, CancellationToken cancellationToken)
        {
            var allEvents = (await _eventRepository.ListAllAsync()).OrderBy(x => x.Date);
            return _mapper.Map<List<EventListVm>>(allEvents);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

GetEventDetailQuery and Handler

Similarly, here’s the GetEventDetailQuery class for fetching event details:

//CategoryDto.cs
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventDetail
{
    public class CategoryDto
    {
        public Guid CategoryId { get; set; }

        public string Name { get; set; }
    }
}
//EventDetailVm.cs
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventDetail
{
    public class EventDetailVm
    {
        public Guid EventId { get; set; }

        public string Name { get; set; }

        public int Price { get; set; }

        public string Artist { get; set; }

        public DateTime Date { get; set; }

        public string Description { get; set; }

        public string ImageUrl { get; set; }

        public Guid CategoryId { get; set; }

        public CategoryDto Category { get; set; }
    }
}
// GetEventDetailQuery.cs

public class GetEventDetailQuery : IRequest<EventDetailVm>
{
    public Guid Id { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

And the handler for processing this query:

// GetEventDetailQueryHandler.cs

public class GetEventDetailQueryHandler : IRequestHandler<GetEventDetailQuery, EventDetailVm>
{
    private readonly IAsyncRepository<Event> _eventRepository;
    private readonly IAsyncRepository<Category> _categoryRepository;
    private readonly IMapper _mapper;

    public GetEventDetailQueryHandler(IAsyncRepository<Event> eventRepository, IAsyncRepository<Category> categoryRepository, IMapper mapper)
    {
        _eventRepository = eventRepository;
        _categoryRepository = categoryRepository;
        _mapper = mapper;
    }

    public async Task<EventDetailVm> Handle(GetEventDetailQuery request, CancellationToken cancellationToken)
    {
        var eventEntity = await _eventRepository.GetByIdAsync(request.Id);
        var eventDetailVm = _mapper.Map<EventDetailVm>(eventEntity);

        var categoryEntity = await _categoryRepository.GetByIdAsync(eventEntity.CategoryId);
        eventDetailVm.Category = _mapper.Map<CategoryDto>(categoryEntity);

        return eventDetailVm;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Configuring AutoMapper Profiles

Finally, we need to configure AutoMapper to handle the mappings between our entities and ViewModels. Here’s the MappingProfile class that defines these mappings:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Event, EventListVm>().ReverseMap();
        CreateMap<Event, EventDetailVm>().ReverseMap();
        CreateMap<Category, CategoryDto>().ReverseMap();
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

By following these steps, you've successfully organized your project structure, created the necessary queries and handlers, and configured AutoMapper profiles for a clean and maintainable application.

Stay tuned for the next part of this series, where we’ll explore how to integrate and test these components in a real-world application!

For the complete source code, you can visit the GitHub repository: https://github.com/mohamedtayel1980/clean-architecture

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player