DEV Community

Cover image for 🧱 Lesson 2D -Implementing Application Services (Service Layer and DTOs).
Farrukh Rehman
Farrukh Rehman

Posted on

🧱 Lesson 2D -Implementing Application Services (Service Layer and DTOs).

Series: From Code to Cloud: Building a Production-Ready .NET Application
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead
LinkedIn: https://linkedin.com/in/farrukh-rehman
GitHub: https://github.com/farrukh1212cs

Source Code Backend : https://github.com/farrukh1212cs/ECommerce-Backend.git

Source Code Frontend : https://github.com/farrukh1212cs/ECommerce-Frontend.git

Introduction

In this lecture, we’ll focus on implementing the Application Layer —
this is the layer that connects our Domain logic (repositories) to the API layer.

It contains:

  • DTOs (Data Transfer Objects) → used to safely send/receive data between layers.
  • Services → contain business workflows using domain repositories.

The goal is to keep:

  • The Domain layer independent and pure (entities + repository interfaces).
  • The Infrastructure layer focused only on persistence (EF Core).
  • The Application layer responsible for business use cases.

Folder Structure Overview

ECommerce.Application/DTOs/ProductDto.cs

namespace ECommerce.Application.DTOs;

public class ProductDto
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int Stock { get; set; }
}


Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/DTOs/CustomerDto.cs

namespace ECommerce.Application.DTOs;

public class CustomerDto
{
    public Guid Id { get; set; }
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string PhoneNumber { get; set; } = string.Empty;

    public string FullName => $"{FirstName} {LastName}";
}

Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/DTOs/OrderDto.cs

using ECommerce.Domain.Enums;

namespace ECommerce.Application.DTOs;

public class OrderDto
{
    public Guid Id { get; set; }
    public Guid CustomerId { get; set; }
    public DateTime OrderDate { get; set; }
    public decimal TotalAmount { get; set; }
    public OrderStatus Status { get; set; }
    public List<OrderItemDto> Items { get; set; } = new();
}

Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/DTOs/OrderItemDto.cs

namespace ECommerce.Application.DTOs;

public class OrderItemDto
{
    public Guid Id { get; set; }
    public Guid OrderId { get; set; }
    public Guid ProductId { get; set; }
    public string ProductName { get; set; } = string.Empty;
    public decimal UnitPrice { get; set; }
    public int Quantity { get; set; }

    public decimal TotalPrice => UnitPrice * Quantity;
}
Enter fullscreen mode Exit fullscreen mode

Service Interfaces

We’ll now define service interfaces that describe the available operations for each entity.

ECommerce.Application/Services/Interfaces/IProductService.cs

using ECommerce.Application.DTOs;

namespace ECommerce.Application.Services.Interfaces;

public interface IProductService
{
    Task<IEnumerable<ProductDto>> GetAllAsync();
    Task<ProductDto?> GetByIdAsync(Guid id);
    Task<ProductDto> AddAsync(ProductDto dto);
    Task UpdateAsync(ProductDto dto);
    Task DeleteAsync(Guid id);
}


Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/Services/Interfaces/ICustomerService.cs

using ECommerce.Application.DTOs;

namespace ECommerce.Application.Services.Interfaces;

public interface ICustomerService
{
    Task<IEnumerable<CustomerDto>> GetAllAsync();
    Task<CustomerDto?> GetByIdAsync(Guid id);
    Task<CustomerDto> AddAsync(CustomerDto dto);
    Task UpdateAsync(CustomerDto dto);
    Task DeleteAsync(Guid id);
}


Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/Services/Interfaces/IOrderService.cs

using ECommerce.Application.DTOs;

namespace ECommerce.Application.Services.Interfaces;

public interface IOrderService
{
    Task<IEnumerable<OrderDto>> GetAllAsync();
    Task<OrderDto?> GetByIdAsync(Guid id);
    Task<OrderDto> AddAsync(OrderDto dto);
    Task UpdateAsync(OrderDto dto);
    Task DeleteAsync(Guid id);
}

Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/Services/Interfaces/IOrderItemService.cs

uusing ECommerce.Application.DTOs;

namespace ECommerce.Application.Services.Interfaces;

public interface IOrderItemService
{
    Task<IEnumerable<OrderItemDto>> GetByOrderIdAsync(Guid orderId);
    Task<IEnumerable<OrderItemDto>> GetAllAsync();
}


Enter fullscreen mode Exit fullscreen mode

Service Implementations
Next, we’ll implement concrete service classes that depend on repository interfaces from the Domain layer.

ECommerce.Application/Services/Implementations/ProductService.cs

using ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Domain.Entities;
using ECommerce.Domain.Repositories;

namespace ECommerce.Application.Services.Implementations;

public class ProductService : IProductService
{
    private readonly IProductRepository _repository;

    public ProductService(IProductRepository repository)
    {
        _repository = repository;
    }

    public async Task<IEnumerable<ProductDto>> GetAllAsync()
    {
        var products = await _repository.GetAllAsync();
        return products.Select(p => new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            Description = p.Description,
            Price = p.Price,
            Stock = p.Stock
        });
    }

    public async Task<ProductDto?> GetByIdAsync(Guid id)
    {
        var p = await _repository.GetByIdAsync(id);
        if (p == null) return null;

        return new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            Description = p.Description,
            Price = p.Price,
            Stock = p.Stock
        };
    }

    public async Task<ProductDto> AddAsync(ProductDto dto)
    {
        var entity = new Product
        {
            Name = dto.Name,
            Description = dto.Description,
            Price = dto.Price,
            Stock = dto.Stock
        };

        await _repository.AddAsync(entity);
        dto.Id = entity.Id;
        return dto;
    }

    public async Task UpdateAsync(ProductDto dto)
    {
        var entity = await _repository.GetByIdAsync(dto.Id);
        if (entity == null) return;

        entity.Name = dto.Name;
        entity.Description = dto.Description;
        entity.Price = dto.Price;
        entity.Stock = dto.Stock;

        await _repository.UpdateAsync(entity);
    }

    public async Task DeleteAsync(Guid id) => await _repository.DeleteAsync(id);
}
Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/Services/Implementations/CustomerService.cs

using ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Domain.Entities;
using ECommerce.Domain.Repositories;

namespace ECommerce.Application.Services.Implementations;

public class CustomerService : ICustomerService
{
    private readonly ICustomerRepository _repository;

    public CustomerService(ICustomerRepository repository)
    {
        _repository = repository;
    }

    public async Task<IEnumerable<CustomerDto>> GetAllAsync()
    {
        var customers = await _repository.GetAllAsync();
        return customers.Select(c => new CustomerDto
        {
            Id = c.Id,
            FirstName = c.FirstName,
            LastName = c.LastName,
            Email = c.Email,
            PhoneNumber = c.PhoneNumber
        });
    }

    public async Task<CustomerDto?> GetByIdAsync(Guid id)
    {
        var c = await _repository.GetByIdAsync(id);
        if (c == null) return null;

        return new CustomerDto
        {
            Id = c.Id,
            FirstName = c.FirstName,
            LastName = c.LastName,
            Email = c.Email,
            PhoneNumber = c.PhoneNumber
        };
    }

    public async Task<CustomerDto> AddAsync(CustomerDto dto)
    {
        var entity = new Customer
        {
            FirstName = dto.FirstName,
            LastName = dto.LastName,
            Email = dto.Email,
            PhoneNumber = dto.PhoneNumber
        };

        await _repository.AddAsync(entity);
        dto.Id = entity.Id;
        return dto;
    }

    public async Task UpdateAsync(CustomerDto dto)
    {
        var entity = await _repository.GetByIdAsync(dto.Id);
        if (entity == null) return;

        entity.FirstName = dto.FirstName;
        entity.LastName = dto.LastName;
        entity.Email = dto.Email;
        entity.PhoneNumber = dto.PhoneNumber;

        await _repository.UpdateAsync(entity);
    }

    public async Task DeleteAsync(Guid id) => await _repository.DeleteAsync(id);
}


Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/Services/Implementations/OrderService.cs

using ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Domain.Entities;
using ECommerce.Domain.Repositories;

namespace ECommerce.Application.Services.Implementations;

public class OrderService : IOrderService
{
    private readonly IOrderRepository _repository;
    private readonly ICustomerRepository _customerRepository;
    private readonly IProductRepository _productRepository;

    public OrderService(
        IOrderRepository repository,
        ICustomerRepository customerRepository,
        IProductRepository productRepository)
    {
        _repository = repository;
        _customerRepository = customerRepository;
        _productRepository = productRepository;
    }

    public async Task<IEnumerable<OrderDto>> GetAllAsync()
    {
        var orders = await _repository.GetAllAsync();
        return orders.Select(o => new OrderDto
        {
            Id = o.Id,
            CustomerId = o.CustomerId,
            OrderDate = o.OrderDate,
            TotalAmount = o.TotalAmount,
            Status = o.Status,
            Items = o.Items.Select(i => new OrderItemDto
            {
                Id = i.Id,
                ProductId = i.ProductId,
                ProductName = i.ProductName,
                Quantity = i.Quantity,
                UnitPrice = i.UnitPrice
            }).ToList()
        });
    }

    public async Task<OrderDto?> GetByIdAsync(Guid id)
    {
        var o = await _repository.GetByIdAsync(id);
        if (o == null) return null;

        return new OrderDto
        {
            Id = o.Id,
            CustomerId = o.CustomerId,
            OrderDate = o.OrderDate,
            TotalAmount = o.TotalAmount,
            Status = o.Status,
            Items = o.Items.Select(i => new OrderItemDto
            {
                Id = i.Id,
                ProductId = i.ProductId,
                ProductName = i.ProductName,
                Quantity = i.Quantity,
                UnitPrice = i.UnitPrice
            }).ToList()
        };
    }

    public async Task<OrderDto> AddAsync(OrderDto dto)
    {
        var entity = new Order
        {
            CustomerId = dto.CustomerId,
            OrderDate = dto.OrderDate
        };

        foreach (var itemDto in dto.Items)
        {
            var product = await _productRepository.GetByIdAsync(itemDto.ProductId);
            if (product == null) continue;

            entity.Items.Add(new OrderItem
            {
                ProductId = product.Id,
                ProductName = product.Name,
                Quantity = itemDto.Quantity,
                UnitPrice = product.Price
            });
        }

        entity.CalculateTotal();
        await _repository.AddAsync(entity);

        dto.Id = entity.Id;
        dto.TotalAmount = entity.TotalAmount;
        return dto;
    }

    public async Task UpdateAsync(OrderDto dto)
    {
        var order = await _repository.GetByIdAsync(dto.Id);
        if (order == null) return;

        order.Status = dto.Status;
        await _repository.UpdateAsync(order);
    }

    public async Task DeleteAsync(Guid id) => await _repository.DeleteAsync(id);
}


Enter fullscreen mode Exit fullscreen mode

ECommerce.Application/Services/Implementations/OrderItemService.cs

using ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Domain.Repositories;

namespace ECommerce.Application.Services.Implementations;

public class OrderItemService : IOrderItemService
{
    private readonly IOrderItemRepository _repository;

    public OrderItemService(IOrderItemRepository repository)
    {
        _repository = repository;
    }

    public async Task<IEnumerable<OrderItemDto>> GetByOrderIdAsync(Guid orderId)
    {
        var items = await _repository.GetByOrderIdAsync(orderId);
        return items.Select(i => new OrderItemDto
        {
            Id = i.Id,
            OrderId = i.OrderId,
            ProductId = i.ProductId,
            ProductName = i.ProductName,
            Quantity = i.Quantity,
            UnitPrice = i.UnitPrice
        });
    }

    public async Task<IEnumerable<OrderItemDto>> GetAllAsync()
    {
        var items = await _repository.GetAllAsync();
        return items.Select(i => new OrderItemDto
        {
            Id = i.Id,
            OrderId = i.OrderId,
            ProductId = i.ProductId,
            ProductName = i.ProductName,
            Quantity = i.Quantity,
            UnitPrice = i.UnitPrice
        });
    }
}


Enter fullscreen mode Exit fullscreen mode

Updated Program.cs

using ECommerce.Application.Services.Implementations;
using ECommerce.Application.Services.Interfaces;
using ECommerce.Domain.Repositories;
using ECommerce.Infrastructure.Repositories;

var builder = WebApplication.CreateBuilder(args);

// ------------------------------------------------------
// Add Controllers
// ------------------------------------------------------
builder.Services.AddControllers();


// ------------------------------------------------------
//  Repository Registrations
// ------------------------------------------------------
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<ICustomerRepository, CustomerRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderItemRepository, OrderItemRepository>();


// ------------------------------------------------------
//  Service Registrations (Application Layer)
// ------------------------------------------------------
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<ICustomerService, CustomerService>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IOrderItemService, OrderItemService>();


// ------------------------------------------------------
//  Swagger
// ------------------------------------------------------
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();


var app = builder.Build();

// ------------------------------------------------------
// Middleware Pipeline
// ------------------------------------------------------
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
app.MapControllers();

app.Run();

Enter fullscreen mode Exit fullscreen mode

Next Lecture Preview
Lecture 2E : Implementing Controllers (API Layer)

Expose RESTful endpoints for Product, Customer, Order, and OrderItem using the services we built in the Application Layer.

Top comments (0)