Implementing User Microservice Domain Layer

Implementing User Microservice Domain Layer:

The Domain Layer is the heart of the User Microservice, encapsulating the core business logic, rules, and models that define how the user-related operations function. It is framework-agnostic, meaning it operates independently of any technical concerns like databases or APIs, ensuring that the business model remains reusable, testable, and adaptable.

In this layer, we define entities (e.g., User, Address, RefreshToken, Client) that represent real-world business objects, as well as repository interfaces (IUserRepository) that define how these entities are accessed and manipulated.

This separation of concerns ensures:

  • Business rules are centralized and not mixed with technical implementation.
  • The model can adapt to future technology changes without rewriting core logic.
  • A clean architecture structure where the what is separated from the how.

Key responsibilities include:

  • Representing user data and relationships.
  • Defining contracts for persistence operations.
  • Managing domain rules like token validity, account activation, and address handling.
Installing the Required Packages

In the Domain Project, please install the following package so that we can use the EF Core-related attributes directly on entities.

  • Install-Package Microsoft.EntityFrameworkCore

First, create 2 folders at the project root directory named Entities and Repositories.

Entities/User.cs

Create a class file named User.cs within the Entities folder of your UserService.Domain project and copy and paste the following code. The User entity represents the core user profile in the domain model, encapsulating all essential user information, including unique ID, username, email, phone number, full name, profile photo URL, email confirmation and activation status, timestamps for creation and last login, and a collection of associated addresses.

using Microsoft.EntityFrameworkCore;
namespace UserService.Domain.Entities
{
    [Index(nameof(Email), Name = "Index_Email_Unique", IsUnique = true)]
    public class User
    {
        public Guid Id { get; set; }
        public string? UserName { get; set; } = null!;
        public string? Email { get; set; } = null!;
        public bool IsEmailConfirmed { get; set; }
        public bool IsActive { get; set; }
        public string? PhoneNumber { get; set; }
        public string? FullName { get; set; }
        public string? ProfilePhotoUrl { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime? LastLoginAt { get; set; }
        public List<Address> Addresses { get; set; } = new();
    }
}
Entities/Address.cs

Create a class file named Address.cs within the Entities folder of your UserService.Domain project and copy and paste the following code. The Address entity models the physical address information linked to a user, with properties for a unique ID, the owning UserId, address lines, city, state, postal code, country, and boolean flags to indicate default shipping and billing addresses. This structure allows each user to manage multiple addresses, supporting e-commerce shipping/billing requirements.

namespace UserService.Domain.Entities
{
    public class Address
    {
        public Guid Id { get; set; }
        public Guid UserId { get; set; }
        public string AddressLine1 { get; set; } = null!;
        public string? AddressLine2 { get; set; }
        public string City { get; set; } = null!;
        public string State { get; set; } = null!;
        public string PostalCode { get; set; } = null!;
        public string Country { get; set; } = null!;
        public bool IsDefaultShipping { get; set; }
        public bool IsDefaultBilling { get; set; }
    }
}
Entities/Client.cs

Create a class file named Client.cs within the Entities folder of your UserService.Domain project and copy and paste the following code. This entity will hold the client information.

using Microsoft.EntityFrameworkCore;
namespace UserService.Domain.Entities
{
    [Index(nameof(ClientName), Name = "Index_ClientName_Unique", IsUnique = true)]
    public class Client
    {
        public string ClientId { get; set; } = null!;
        public string ClientName { get; set; } = null!; // e.g., "Web", "Android", "iOS"
        public string? Description { get; set; }
        public bool IsActive { get; set; }
        public ICollection<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>();
    }
}
Entities/RefreshToken.cs

Create a class file named RefreshToken.cs within the Entities folder of your UserService.Domain project and copy and paste the following code. The RefreshToken entity represents a security token used to renew JWT access tokens without requiring the user to re-login, containing the unique ID, associated UserId, the token string, timestamps for creation, expiry, and optional revocation, as well as IP tracking for both creation and revocation events. It includes logic to determine if a token is active or expired, strengthening session security.

namespace UserService.Domain.Entities
{
    public class RefreshToken
    {
        public Guid Id { get; set; }
        public Guid UserId { get; set; }
        public string Token { get; set; } = null!;
        public string ClientId { get; set; } = null!;    // Web/Android/iOS etc.
        public Client Client { get; set; } = null!;
        public string UserAgent { get; set; } = null!;   // Chrome, Firefox, Safari, etc.
        public DateTime CreatedAt { get; set; }
        public string CreatedByIp { get; set; } = null!;
        public DateTime? RevokedAt { get; set; }
        public string? RevokedByIp { get; set; }
        public DateTime ExpiresAt { get; set; }
        public bool IsActive => RevokedAt == null && !IsExpired;
        public bool IsExpired => DateTime.UtcNow >= ExpiresAt;
    }
}
Repositories/IUserRepository.cs

The IUserRepository interface defines the contract for all user-related data access and manipulation operations in the domain, including finding users by email, username, or ID, creating users, validating passwords, updating user data, managing user roles, handling email and password tokens, login tracking, lockout and two-factor checks, refresh token lifecycle, and address management. By abstracting these operations, it decouples domain logic from data persistence, supporting both testability and extensibility.

Create a class file named IUserRepository.cs within the Repositories folder of your UserService.Domain project and copy and paste the following code:

using UserService.Domain.Entities;
namespace UserService.Domain.Repositories
{
    public interface IUserRepository
    {
        Task<User?> FindByEmailAsync(string email);
        Task<User?> FindByUserNameAsync(string userName);
        Task<User?> FindByIdAsync(Guid id);
        Task<bool> CreateUserAsync(User user, string password);
        Task<bool> CheckPasswordAsync(User user, string password);
        Task<bool> UpdateUserAsync(User user);
        Task<IList<string>> GetUserRolesAsync(User user);
        Task<bool> AddUserToRoleAsync(User user, string role);
        Task<bool> VerifyConfirmaionEmailAsync(User user, string token);
        Task<string?> GenerateEmailConfirmationTokenAsync(User user);
        Task<string?> GeneratePasswordResetTokenAsync(User user);
        Task<bool> ResetPasswordAsync(User user, string token, string newPassword);
        Task<bool> ChangePasswordAsync(User user, string currentPassword, string newPassword);
        Task UpdateLastLoginAsync(User user, DateTime loginTime);
        Task<string> GenerateAndStoreRefreshTokenAsync(Guid userId, string clientId, string userAgent, string ipAddress);
        Task<RefreshToken?> GetRefreshTokenAsync(string token);
        Task RevokeRefreshTokenAsync(RefreshToken refreshToken, string ipAddress);
        Task<List<Address>> GetAddressesByUserIdAsync(Guid userId);
        Task<Guid> AddOrUpdateAddressAsync(Address address);
        Task<bool> DeleteAddressAsync(Guid userId, Guid addressId);
        Task<bool> IsLockedOutAsync(User user);
        Task<bool> IsTwoFactorEnabledAsync(User user);
        Task IncrementAccessFailedCountAsync(User user);
        Task ResetAccessFailedCountAsync(User user);
        Task<DateTime?> GetLockoutEndDateAsync(User user);
        Task<int> GetMaxFailedAccessAttemptsAsync();
        Task<int> GetAccessFailedCountAsync(User user);
        Task<bool> IsValidClientAsync(string clientId);
        Task<bool> IsUserExistsAsync(Guid userId);
        Task<Address?> GetAddressByUserIdAndAddressIdAsync(Guid userId, Guid addressId);
    }
}

The Domain Layer serves as the foundation of the User Microservice, providing the blueprint for all user-related operations. By keeping this layer free from technical dependencies, the system gains long-term maintainability, scalability, and flexibility for evolving business needs.

Leave a Reply

Your email address will not be published. Required fields are marked *