API Development Using Strategy Design Pattern with 3-Tier Architecture

In today's fast-paced software development landscape, creating well-structured and maintainable applications is crucial. One popular design pattern that can greatly enhance the organization and flexibility of your ASP.NET Core Web API projects is the Strategy Design Pattern. This pattern allows you to encapsulate and swap out algorithms or behaviors at runtime, making it an ideal choice for handling various CRUD (Create, Read, Update, Delete) operations on your data models. In this article, I will explore how to implement the Strategy Design Pattern within a 3-Tier Architecture in an ASP.NET Core Web API. You'll learn how to create a robust business logic layer, define concrete strategies for each CRUD operation, and seamlessly integrate them into your API controllers. By the end of this article, you'll have a comprehensive understanding of how to leverage this pattern for a more maintainable and scalable API.

To implement a complete CRUD (Create, Read, Update, Delete) API for a C# article using the Strategy Design Pattern in an ASP.NET Core Web API with a 3-tier architecture, you'll need to organize your application into distinct layers: Presentation (API), Business Logic, and Data Access. Here's a step-by-step guide to achieve this.

Create a new ASP.NET Core Web API project

Use Visual Studio or the command-line tool to create a new ASP.NET Core Web API project. Make sure you select the appropriate template for your project.

Define Your Layers

In a 3-tier architecture, you have three main layers.

  • Presentation (API) Layer: Responsible for handling incoming HTTP requests and returning responses.
  • Business Logic Layer: Contains the application's business logic, including the Strategy Design Pattern.
  • Data Access Layer: Manages data storage and retrieval.

Model and DTOs

Create your C# article model and Data Transfer Objects (DTOs) if needed. The model represents the core data structure, while DTOs are used for input and output in the API.

// Model
public class Article
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

// DTOs
public class ArticleDTO
{
    public string Title { get; set; }
    public string Content { get; set; }
}

Begin by organizing your project into namespaces and folders for a clear separation of concerns. Create a folder for your business logic layer and namespace accordingly.

Define the Interface

Within the business logic layer, define an interface that outlines the contract for CRUD operations. This interface should contain method signatures for creating, reading, updating, and deleting articles, as shown below.

// Sardar Mudassar Ali Khan
public interface IArticleService
{
    Article CreateArticle(ArticleDTO articleDto);
    Article GetArticle(int id);
    IEnumerable<Article> GetAllArticles();
    Article UpdateArticle(int id, ArticleDTO articleDto);
    void DeleteArticle(int id);
}

ArticleService Class

Next, implement a class that will serve as the core of your business logic layer. This class, typically named ArticleService, should implement the IArticleService interface. The concrete methods for each CRUD operation will be implemented here.

public class ArticleService: IArticleService
{
    private readonly IArticleRepository _articleRepository;

    public ArticleService(IArticleRepository articleRepository)
    {
        _articleRepository = articleRepository;
    }

    public Article CreateArticle(ArticleDTO articleDto)
    {
        var newArticle = new Article
        {
            Title = articleDto.Title,
            Content = articleDto.Content
        };

        var createdArticle = _articleRepository.Add(newArticle);

        return createdArticle;
    }

    public Article GetArticle(int id)
    {
        var article = _articleRepository.GetById(id);

        if (article == null)
        {
            throw new Exception("Article not found");
        }

        return article;
    }

    public IEnumerable<Article> GetAllArticles()
    {
        var articles = _articleRepository.GetAll();
        return articles;
    }

    public Article UpdateArticle(int id, ArticleDTO articleDto)
    {
        var existingArticle = _articleRepository.GetById(id);

        if (existingArticle == null)
        {
            throw new Exception("Article not found");
        }

        existingArticle.Title = articleDto.Title;
        existingArticle.Content = articleDto.Content;

        var updatedArticle = _articleRepository.Update(existingArticle);

        return updatedArticle;
    }

    public void DeleteArticle(int id)
    {
        var existingArticle = _articleRepository.GetById(id);

        if (existingArticle == null)
        {
            throw new Exception("Article not found");
        }

        _articleRepository.Delete(existingArticle);
    }
}

Create Strategy Classes

Implement concrete strategies for your CRUD operations. Each strategy should implement the IArticleService interface.

// Sardar Mudassar Ali Khan
public class CreateArticleStrategy : IArticleService
{
    private readonly IArticleRepository _articleRepository;

    public CreateArticleStrategy(IArticleRepository articleRepository)
    {
        _articleRepository = articleRepository;
    }

    public Article CreateArticle(ArticleDTO articleDto)
    {
        var newArticle = new Article
        {
            Title = articleDto.Title,
            Content = articleDto.Content
        };

        var createdArticle = _articleRepository.Add(newArticle);

        return createdArticle;
    }

    public Article GetArticle(int id)
    {
        throw new NotImplementedException("Get operation is not supported in CreateArticleStrategy");
    }

    public IEnumerable<Article> GetAllArticles()
    {
        throw new NotImplementedException("GetAll operation is not supported in CreateArticleStrategy");
    }

    public Article UpdateArticle(int id, ArticleDTO articleDto)
    {
        throw new NotImplementedException("Update operation is not supported in CreateArticleStrategy");
    }

    public void DeleteArticle(int id)
    {
        throw new NotImplementedException("Delete operation is not supported in CreateArticleStrategy");
    }
}

public class GetArticleStrategy : IArticleService
{
    private readonly IArticleRepository _articleRepository;

    public GetArticleStrategy(IArticleRepository articleRepository)
    {
        _articleRepository = articleRepository;
    }

    public Article CreateArticle(ArticleDTO articleDto)
    {
        throw new NotImplementedException("Create operation is not supported in GetArticleStrategy");
    }

    public Article GetArticle(int id)
    {
        var article = _articleRepository.GetById(id);

        if (article == null)
        {
            throw new Exception("Article not found");
        }

        return article;
    }

    public IEnumerable<Article> GetAllArticles()
    {
        var articles = _articleRepository.GetAll();
        return articles;
    }

    public Article UpdateArticle(int id, ArticleDTO articleDto)
    {
        throw new NotImplementedException("Update operation is not supported in GetArticleStrategy");
    }

    public void DeleteArticle(int id)
    {
        throw new NotImplementedException("Delete operation is not supported in GetArticleStrategy");
    }
}

API Controller

In the Presentation Layer, create a controller that will handle incoming HTTP requests and delegate to the appropriate strategy based on the request type (e.g., POST, GET, PUT, DELETE).

[Route("api/articles")]
[ApiController]
public class ArticleController : ControllerBase
{
    private readonly IArticleService _articleService;

    public ArticleController(IArticleService articleService)
    {
        _articleService = articleService;
    }

    [HttpPost]
    public ActionResult<Article> CreateArticle(ArticleDTO articleDto)
    {
        var createdArticle = _articleService.CreateArticle(articleDto);
        return Ok(createdArticle);
    }

    [HttpGet("{id}")]
    public ActionResult<Article> GetArticle(int id)
    {
        var article = _articleService.GetArticle(id);
        if (article == null)
            return NotFound();
        return Ok(article);
    }

    [HttpGet]
    public ActionResult<IEnumerable<Article>> GetAllArticles()
    {
        var articles = _articleService.GetAllArticles();
        return Ok(articles);
    }

    [HttpPut("{id}")]
    public ActionResult<Article> UpdateArticle(int id, ArticleDTO articleDto)
    {
        var updatedArticle = _articleService.UpdateArticle(id, articleDto);
        if (updatedArticle == null)
            return NotFound();
        return Ok(updatedArticle);
    }

    [HttpDelete("{id}")]
    public IActionResult DeleteArticle(int id)
    {
        _articleService.DeleteArticle(id);
        return NoContent();
    }
}

Dependency Injection

Configure dependency injection in the Startup.cs file to inject the appropriate strategy into the controller.

services.AddScoped<IArticleService, CreateArticleStrategy>();

Data Access Layer

Implement the data access layer for CRUD operations using Entity Framework Core, Dapper, or any other data access technology.

Database Setup

Configure your database connection in the appsettings.json file and create a database schema that corresponds to your Article model.

Testing

Test your API by using tools like Postman or by writing unit tests for your controller and business logic.

That's a high-level overview of how to implement a CRUD API for a C# article using the Strategy Design Pattern in an ASP.NET Core Web API with a 3-tier architecture. Be sure to fill in the details of your CRUD methods, handle exceptions, and implement validation as needed.

Conclusion

In this implementation of an ASP.NET Core Web API with a 3-tier architecture using the Strategy Design Pattern, we've created a structured and modular application for managing articles. Here's a brief conclusion of the key points.

  1. Architecture: We've followed a 3-tier architecture, including the Presentation (API) Layer, Business Logic Layer, and Data Access Layer.
  2. Strategy Design Pattern: We applied the Strategy Design Pattern to handle CRUD operations by creating multiple strategies, each implementing the IArticleService interface.
  3. Business Logic Layer: The ArticleService class and the various strategy classes, such as CreateArticleStrategy and GetArticleStrategy, encapsulate the business logic for different operations.
  4. Dependency Injection: We've used dependency injection to inject the appropriate services and repositories into the business logic classes.
  5. Data Access: The actual data access logic, such as interacting with a database through Entity Framework Core or another ORM, should be implemented in the IArticleRepository and injected into the business logic classes.
  6. Error Handling: We've introduced basic error handling through exceptions, but you should enhance error handling and validation based on your project's requirements.

By following this architectural approach and the Strategy Design Pattern, you can achieve a flexible, maintainable, and scalable API for managing articles while adhering to best practices in software development. Remember that in a real-world application, you'd also focus on security, validation, logging, and other critical aspects to ensure the robustness and reliability of your system.


Similar Articles