Skip to content
Igor Soares edited this page Jan 15, 2025 · 8 revisions

Patterns

  • Repository

  • Singleton

Project example: Users/Infrastructure/RabbitMQConnection.cs

Reference: Refactoring Guru Singleton C#

Ps. The class was instantiated as Lazy in order to have a delayed loading since it is not used everywhere in the system. In addition, Lazy supports multi-threading by default, meaning that even in an environment with several threads there will only be one instance of the class.

  • The Asynchronous Initialization Pattern

Project example: Clean Architecture/Users/Infrastructure/RabbitMQConnection.cs

Reference: Async OOP 2: Constructors

  • Fail Fast

Project example: Users/Application/UseCases/User/ReadUser.cs

   // ReadUser.cs

   internal sealed class ReadUser(IUserRepository userRepository) : IReadUser
   {
       private readonly IUserRepository _userRepository = userRepository;

       public async Task<(UserException?, User?)> ExecuteAsync(Guid userId)
       {
           try
           {
               return (null, await _userRepository.GetById(userId));
           }
           catch (Exception ex)
           {
               return (new UserException("An error occurred while getting a user.", ex), null);
           }
       }
   }

Reference: Try/Catch Considered Harmful

  • DTO

  • Specification Pattern

Project example: Projects/Application/Validations/Project/ValidateUpdateProjectAttributes.cs

Reference: Specification Design Pattern

Ps. The pattern wasn't followed in its entirety by creating a separate class, as this implementation would have added too much complexity to the simplicity of the class. But its idea of having separate validations was kept.

  • Return Early

Project example: Users/Infrastructure/Repositories/UserRepository.cs

  public async Task<User?> Delete(Guid userId)
  {
      var user = await _context.Users.FindAsync(userId);

      if (user == null) return null;

      _context.Users.Remove(user);
      await _context.SaveChangesAsync();

      return user;

  }
  • Short-circuiting to avoid if-else

Project example: Projects/Application/Validations/Project/ValidateUpdateProjectAttributes.cs

  // ValidateUpdateProjectAttributes.cs

  bool hasProjectDto = context.ActionArguments.TryGetValue("projectDTO", out var projectDtoObject);
  bool isProjectUpdateDto = projectDtoObject is ProjectUpdateDTO;
  bool isValidProjectDto = hasProjectDto && isProjectUpdateDto;
  • Outbox Pattern

Project example: Users/Domain/Entities/OutboxMessage.cs

// Entity of DB Users
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;

namespace Users.Domain.Entities
{
    [Index(nameof(OutboxMessageId))]
    public class OutboxMessage
    {
        [Key]
        public Guid OutboxMessageId { get; init; } = Guid.NewGuid();

        [Required(ErrorMessage = "OutboxMessage payload is required")]
        public required string Payload { get; set; }
        public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
        public bool Processed { get; set; } 
    }
}

Services and Architecture

  • Projects
    • It is designed using Clean Architecture.
  • Users
    • It is designed using Clean Architecture.

System Design

diagram-export-1-15-2025-7_52_28-PM

Communication

diagram-export-1-15-2025-7_31_54-PM

  • Each service has its own database.
  • The Outbox standard was implemented to ensure consistency between the data of the Projects and Users services.
  • If there is an error when saving data in the Users database, it will perform a rollback.
  • The occasional inconsistency (of milliseconds) of the data is acceptable in this scenario, which is why queues are used.

Performance

  • Monitoring with Application Insights
  • Rule to not return null values in response body
  • Message Broker with RabbitMQ
  • Asynchronous Programming

Reference: https://renatogroffe.medium.com/dicas-de-performance-para-apis-rest-no-asp-net-core-f2f3c66042c8

Queues

Rules: Retry 3 times to connection and to read/get datas in queue, durable queues

  • sendUserIdToProject
  • sendUserIdToProjectDLQ