Link Search Menu Expand Document

.NET Web API - いち

Nội dung

  1. Cài đặt và cấu hình
  2. Kiến trúc Web API
  3. Xây dựng dịch vụ
  4. Biên dịch và thực thi
  5. Commit & Push
  6. Kết luận

Bài viết này cung cấp hướng dẫn xây dựng một microservice RemindersManagement của ứng dụng FriendReminders. Microservice này có nhiệm vụ quản lý một danh sách reminders với hoạt động dựa trên mô hình Web API, tiếp nhận các yêu cầu từ phía client, cập nhật dữ liệu và gửi trả về kết quả với định dạng JSON.


Cài đặt và cấu hình

.NET SDK

  • .NET Core

Tham khảo Cài đặt và Cấu hình. Kiểm tra version trên Terminal với lệnh:

dotnet --version
  • Code Generator
dotnet tool install -g dotnet-aspnet-codegenerator
  • Entity Framework Core (EF Core)
dotnet tool install --global dotnet-ef

Tham khảo EF Core Configuration

SQLite Database

Trong môi trường phát triển localhost, chúng ta sử dụng cơ sở dữ liệu SQLite để lưu trữ danh sách reminders. Khi triển khai ứng dụng trên AWS, dữ liệu sẽ chuyển sang lưu trữ trong Amazon RDS.

AWS CodeCommit

Project FriendReminders sử dụng dịch vụ AWS CodeCommit để lưu trữ và quản lý mã nguồn. CodeCommit có khả năng tích hợp với nhiều dịch vụ AWS khác như SNS, Lambda cho phép gửi đi thông báo khi mã nguồn thay đổi, hoặc tự động biên dịch, kiểm thử và triển khai thông qua qui trình CI/CD dựa trên CodeBuild và CodePipeline

Cách tạo Repository được hướng dẫn trong bài FriendReminders Repository


Kiến trúc Web API

Định nghĩa API

Trước khi xây dựng RemindersManagement, chúng ta cần định nghĩa endpoints và phân tích cấu trúc dữ liệu mà API sẽ cung cấp.

Bảng tóm tắt:

API Mục đích Request body Response body
GET /api/reminders Danh sách Reminders None Reminders list
GET /api/reminders/{id} Nội dung Reminder None Reminder item
POST /api/reminders Tạo Reminder mới Reminder item Reminder item
PUT /api/reminders/{id} Cập nhật Reminder Reminder item None
DELETE /api/reminders/{id} Xoá Reminder None None

Mỗi message gửi qua Web API (request hoặc response) bao gồm 3 phần chính:

  • Start line - phương thức HTTP, kiểu dữ liệu hoặc trạng thái kết quả
  • Headers - mô tả kiểu dữ liệu, ngôn ngữ, kết nối, client / server
  • Body - thông tin yêu cầu dữ liệu hoặc thông tin phản hồi

Khi gửi trả kết quả của một yêu cầu qua Web API, bên cạnh nội dung trong phần Body, các API cũng cần cung cấp status code phản ánh trạng thái kết quả của từ việc thực thi yêu cầu trên server.

Giá trị và ý nghĩa status code phổ biến:

Status Code Ý nghĩa
200 OK Yêu cầu xử lý thành công
201 Created Nội dung mới được tạo
400 Bad Request Yêu cầu có nội dung không hợp lệ
403 Forbidden Client không được truy cập nội dung yêu cầu
404 Not Found Không tìm thấy nội dung yêu cầu
500 Internal Server Error Lỗi xảy ra trên server

Tạo Solution & Projects

Chú ý

Theo hướng dẫn trong FriendReminders Repository, giả định trên môi trường localhost, chúng ta đã tạo ra được folder FriendReminders liên kết với Repository FriendReminders.

  • Trong folder FriendReminders, tạo solution :
dotnet new sln
  • Tạo folder Services chứa microservice projects:
mkdir Services
mkdir -p Services/RemindersManagement/RemindersManagement.API
mkdir -p Services/RemindersManagement/RemindersManagement.UnitTests
mkdir -p Services/RemindersManagement/RemindersManagement.FunctionalTests
  • Trong RemindersManagement.UnitTests tạo xUnit Test project
dotnet new xunit

Output:

The template "xUnit Test Project" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /Users/anh/Workspace/git/FriendReminders/Services/RemindersManagement/RemindersManagement.UnitTests/RemindersManagement.UnitTests.csproj...
  Determining projects to restore...
  Restored /Users/anh/Workspace/git/FriendReminders/Services/RemindersManagement/RemindersManagement.UnitTests/RemindersManagement.UnitTests.csproj (in 999 ms).

Restore succeeded.
  • Trong RemindersManagement.API tạo Web API project
dotnet new webapi

Output:

The template "ASP.NET Core Web API" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on FriendReminders/Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj...
  Determining projects to restore...
  Restored /Users/anh/Workspace/git/FriendReminders/Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj (in 166 ms).

Restore succeeded.
  • Trong folder RemindersManagement.API, cài đặt EF Core packages:
dotnet add package Microsoft.EntityFrameworkCore 
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
  • Bổ sung và tham chiếu giữa các project trong solution:
dotnet sln add --solution-folder Services Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj
dotnet sln add --solution-folder Services Services/RemindersManagement/RemindersManagement.UnitTests/RemindersManagement.UnitTests.csproj
dotnet add Services/RemindersManagement/RemindersManagement.UnitTests/RemindersManagement.UnitTests.csproj reference Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj

Tham chiếu từ RemindersManagement.UnitTests đến RemindersManagement.API cho phép chúng ta phát triển Unit Test Cases cho dịch vụ RemindersManagement. Phần thực hiện Test Cases được giới thiệu trong bài Web API - P.3

Trong RemindersManagement.API, chúng ta chú ý các files:

  • *.csproj khai báo target framework, library packages, dependencies
  • Program.cs với phương thức Main thực thi đầu tiên khi ứng dụng khởi động
  • Startup.cs khai báo và cấu hình các services, middlewares sẽ sử dụng
  • Controllers/WeatherForecastController.cs xử lý HTTP requests gửi đến endpoint WeatherForecast

Hướng dẫn

Với mục đích hạn chế commit files không cần thiết lên repository, sử dụng câu lệnh dotnet new gitignore để tạo file .gitignore.

Xác nhận kết quả của các bước thiết lập bên trên qua các câu lệnh sau:

  • Biên dịch projects
dotnet build
  • Thực hiện test cases
dotnet test
  • Thực thi ứng dụng
 dotnet run --urls="http://localhost:8000/" --project Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj

Tham số câu lệnh:

  • urls: chỉ định port thực thi ứng dụng
  • project: tham chiếu đến csproj file

Kết quả trên trình duyệt qua địa chỉ: https://localhost:8000/weatherforecast

WebAPI Ouput Kết quả Weather Forecast dưới định dạng JSON

Startup Process

Khi thực thi ứng dụng Web API, quá trình khởi động bao gồm các bước:

  • Tạo ra đối tượng Host quản lý ứng dụng - Program.cs
  • Khởi tạo resources hỗ trợ xử lý request/response - Startup.cs

Việc thực thi gọi đến phương thức Main trong Program.cs. Phương thức này tạo ra một đối tượng Host có trách nhiệm khởi động và quản lý vòng đời của ứng dụng. Host tạo ra một Web Server (i.e, Kestrel, HTTP.sys) cùng luồng xử lý các yêu cầu gửi đến - pipeline.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging =>
            {
                logging.SetMinimumLevel(LogLevel.Debug);
            })            
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Phương thức CreateHostBuilder tạo đối tượng Host dựa trên cấu hình khai báo từ các nguồn:

  • appsettings.json
  • appsettings.{Environment}.json
  • Environment variables
  • Commad-line arguments

Phương thức này cũng tạo ra đối tượng Startup để khởi tạo hoạt động của ứng dụng.

Về cơ bản, Startup khai báo và cung cấp các resources sau:

  • Services - khởi tạo theo cơ chế Dependency Injection
  • Middlewares - thực thi khi có yêu cầu gửi đến hoặc có kết quả trả về
  • Controllers - tiếp nhận và phản hồi HTTP messages

Nội dung của Startup.cs:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public IConfiguration Configuration { get; }
    
    // This method called by the runtime, use to add services.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    }
    // This method called by the runtime, use to configure HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Các điểm chú ý trong nội dung này

  • ConfigureServices: khai báo danh sách các services được sử dụng
  • Configure xây dựng middlewares pipeline qua các phương thức Use...

Ví dụ trên sử dụng Routing Middleware cùng cơ chế Attribute routing thông qua lời gọi đến các phương thức UseRoutingUseEndpoints cho phép việc ánh xạ tự động giữa giá trị URL trong request và đối tượng controller cần tiếp nhận request đó. Do vậy, request với URL https://localhost:5001/weatherforecast sẽ xử lý bởi controller WeatherForecastController

Định nghĩa

Middlewares - là một dạng module hoạt động với vai trò trung gian trong một chuỗi xử lý các yêu cầu hoặc phản hồi. Một Middleaware tiếp nhận kết quả từ thành phần middleware trước đó, thực thi các xử lý logic lên dữ liệu và chuyển kết quả sang thành phần Middleware kế tiếp.

Tham khảo danh sách Middlewares thường sử dụng trong ứng dụng Microservices.

Xử lý yêu cầu

Nội dung WeatherForecastController.cs:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly"
    };

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

Với logic trong class WeatherForecastController, khi client gọi đến endpoint /WeatherForecast với HTTP Get, phương thức Get() thực thi và trả về dữ liệu kiểu Enumerable gồm tập hợp các đối tượng WeatherForecast.

Quá trình xử lý và phản hồi yêu cầu của một Microservice dựa trên Web API:

Pipeline Ouput Web API Request / Response Workflow

Mutil-layers Web API

WeatherForecastController chứa logic khởi tạo và trả về danh sách các đối tượng WeatherForecast thông qua phương thức Get.

Tuy vậy, để giảm thiểu độ phức tạp, ứng dụng Web API thường sử dụng kiến trúc đa tầng - multi-layers với các thành phần:

  • Application - tích hợp, cung cấp API (Controller, Extension)
  • Business / Domain - xây dựng nghiệp vụ (Services, Model classes)
  • Infrastructure - cung cấp cơ sở hạ tầng (Respository, Entity classes)

Trong phần tiếp theo, chúng ta sẽ phát triển logic của dịch vụ theo kiến trúc multi-layers.


Xây dựng dịch vụ

Business / Domain Layer

Trong mô hình kiến trúc Microservices, chúng ta cần có sự phân chia và định nghĩa rõ ràng chức năng mỗi dịch vụ thành phần cần cung cấp. Quá trình phân tích đó được dựa trên phương pháp thiết kế phần mềm theo hướng nghiệp vụ - Domain Driven Design.

Để đơn giản hoá, ví dụ này chỉ gồm một đối tượng nghiệp vụ duy nhất - Reminder.

Các bước thực hiện:

  • Trong folder RemindersManagement.API, thực hiện các lệnh
rm WeatherForecast.cs 
rm Controllers/WeatherForecastController.cs 
mkdir -p Domain/Models
mkdir -p Domain/Services
mkdir -p Domain/Interfaces
  • Folder Domain chứa sub-folders:
    • Models - thể hiện cấu trúc dữ liệu của mô hình nghiệp vụ
    • Services - bao gồm services mô tả logic trong xử lý nghiệp vụ
    • Interfaces - khai báo interfaces được sử dụng bởi Services

Trong folder Models, tạo file Reminder.cs:

using System;

namespace RemindersManagement.API.Domain.Models
{
    public enum ReminderStatus
    {
        Doing,
        Finished,
    }
    public class Reminder
    {
        public Guid Id { get; set; }
        public string Description { get; set; }
        public ReminderStatus Status { get; set; }
    }
}

Mỗi Reminder bao gồm 3 thuộc tính: Id, DescriptionReminderStatus

Trong Interfaces, tạo file IRemindersRepository.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using RemindersManagement.API.Domain.Models;

namespace RemindersManagement.API.Domain.Interfaces
{
    public interface IRemindersRepository
    {
        public Task<IEnumerable<Reminder>> ListAsync();
        public Task<Reminder> FindAsync(Guid id);
        public Task<Reminder> AddAsync(Reminder reminder);
        public void Update(Reminder reminder);
        public void Remove(Reminder reminder);
    }
}

Trong Services, tạo file RemindersService.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using RemindersManagement.API.Domain.Interfaces;
using RemindersManagement.API.Domain.Models;

namespace RemindersManagement.API.Domain.Services
{
    public interface IRemindersService
    {
        Task<IEnumerable<Reminder>> GetRemindersAsync();
        Task<Reminder> GetReminderAsync(Guid id);
        Task<Reminder> PutReminderAsync(Reminder reminder);
        Task<Reminder> DeleteReminder(Guid id);
        Task<Reminder> PutReminderAsync(Guid id, Reminder reminder);
    }

    public class RemindersService : IRemindersService
    {
        private readonly IRemindersRepository _remindersRepository;

        public RemindersService(IRemindersRepository reminderRepository)
        {
            _remindersRepository = reminderRepository;
        }

        public async Task<IEnumerable<Reminder>> GetRemindersAsync()
        {
            return await _remindersRepository.ListAsync();
        }

        public async Task<Reminder> GetReminderAsync(Guid id)
        {
            return await _remindersRepository.FindAsync(id);
        }

        public async Task<Reminder> PutReminderAsync(Reminder reminder)
        {
            // Not implement yet
            return await _remindersRepository.AddAsync(reminder);
        }

        public Task<Reminder> DeleteReminder(Guid id)
        {
            // Not implement yet
            throw new NotImplementedException();
        }

        public Task<Reminder> PutReminderAsync(Guid id, Reminder reminder)
        {
            // Not implement yet
            throw new NotImplementedException();
        }
    }
}

RemindersService thực hiện logic nghiệp vụ:

Method Ý nghĩa
GetRemindersAsync Cung cấp danh sách Reminders
GetReminderAsync Hiển thị Reminder dựa trên id
DeleteReminder Xoá Reminder dựa trên id
PutReminderAsync Tạo / Cập nhật Reminder

RemindersService khai báo và khởi tạo thuộc tính _remindersRepository trong constructor. Dựa trên khai báo này, khi ứng dụng thực thi, một dependency RemindersRepository sẽ được inject vào trong RemindersService. Dependency làm việc trực tiếp với cơ sở dữ liệu, giúp tách biệt giữa logic nghiệp vụ và thành phần hạ tầng cơ sở trong ứng dụng.

Việc khai báo ánh xạ giữa interface và đối tượng dependency được thực hiện trong method ConfigureServices của Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RemindersManagement.API.Domain.Interfaces;
using RemindersManagement.API.Domain.Services;

namespace RemindersManagement.API
{
    public class Startup
    {
        ...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<IRemindersRepository, RemindersRepository>();
            services.AddScoped<IRemindersService, RemindersService>();
        }        
        ...
    }
}

Định nghĩa

Dependency Injection - là kĩ thuật lập trình giúp giảm sự phụ thuộc giữa các classes thông qua khai báo interfaces. Thay vì tham chiếu trực tiếp đến phương thức giữa các classes với tính phụ thuộc cao, chúng ta có thể tham chiếu qua những interfaces trung gian. Trong quá trình thực thi, dựa trên khai báo interfaces, framework sẽ thực hiện việc inject các đối tượng dependency cung cấp khả năng thực thi phương thức được yêu cầu.

Infrastructure Layer

Infrastructure layer làm việc trực tiếp với hạ tầng công nghệ sử dụng bởi ứng dụng. Trong ví dụ này, Infrastructure cung cấp khả năng truy cập và thực hiện các câu lệnh truy vấn cơ sở dữ liệu.

  • Trong RemindersManagement.API, thực hiện tạo folders:
mkdir -p Infrastructure/Data
mkdir -p Infrastructure/Repositories
mkdir -p Infrastructure/Migration
  • Folder Infrastructure chứa các sub-folders:
    • Data - định nghĩa DbContext, quản lý hoạt động làm việc với database
    • Repositories - cung cấp các lớp truy vấn database dựa trên DbContext
    • Migration - lưu trữ những thay đổi liên quan đến cấu trúc, dữ liệu database

Trong folder Data, tạo file RemindersDbContext.cs:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using RemindersManagement.API.Domain.Models;

namespace RemindersManagement.API.Infrastructure.Data
{
    public class RemindersDbContext : DbContext
    {
        const string DEFAULT_SCHEMA = "reminders";
        public DbSet<Reminder> Reminders { get; set; }
        
        public RemindersDbContext(DbContextOptions<RemindersDbContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Fluent API
            modelBuilder.Entity<Reminder>(ConfigureReminders);
            // Data Seed
            modelBuilder.Entity<Reminder>().HasData(
                new Reminder
                {
                    Id = Guid.NewGuid(),
                    Description = "Learning Microservices",
                    Status = ReminderStatus.Finished
                },
                new Reminder
                {
                    Id = Guid.NewGuid(),
                    Description = "Writing Blog",
                    Status = ReminderStatus.Doing
                },
                new Reminder
                {
                    Id = Guid.NewGuid(),
                    Description = "Presentation prepare",
                    Status = ReminderStatus.Doing
                }
            );
        }

        void ConfigureReminders(EntityTypeBuilder<Reminder> reminderConfiguration)
        {
            reminderConfiguration.ToTable("Reminders", DEFAULT_SCHEMA);
            reminderConfiguration.HasKey(r => r.Id);
            reminderConfiguration.Property(r => r.Description).IsRequired();
            reminderConfiguration.Property(r => r.Status).HasColumnType("varchar(50)").IsRequired();
        }
    }
}

Đối tượng RemindersDbContext sử dụng Fluent API để mô tả cấu trúc dữ liệu và khởi tạo dữ liệu trong bảng Reminders.

Bên cạnh đó, việc sử dụng Dependency Injection đối với DbContextOptions trong constructor của RemindersDbContext cho phép Startup truyền vào cấu hình kết nối cơ sở dữ liệu khi thực thi ứng dụng.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RemindersManagement.API.Domain.Interfaces;
using RemindersManagement.API.Domain.Services;
using RemindersManagement.API.Infrastructure.Data;
using RemindersManagement.API.Infrastructure.Repositories;

namespace RemindersManagement.API
{
    public class Startup
    {
        ...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<IRemindersRepository, RemindersRepository>();
            services.AddScoped<IRemindersService, RemindersService>();
            services.AddDbContext<RemindersDbContext>(options => {
                options.UseSqlite("Data Source=FriendReminders.db");
            });        
        }
        ...
    }
}
  • Tạo Repository cho việc truy vấn dữ liệu:

Trong folder Repositories, tạo file BaseRepository.cs chứa abtract class đóng gói DbContext

using System;
using RemindersManagement.API.Infrastructure.Data;

namespace RemindersManagement.API.Infrastructure.Repositories
{
    public abstract class BaseRepository
    {
        protected readonly RemindersDbContext _context;
        public BaseRepository(RemindersDbContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }
    }
}

Trong cùng folder, tạo file RemindersRepository.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RemindersManagement.API.Domain.Interfaces;
using RemindersManagement.API.Domain.Models;
using RemindersManagement.API.Infrastructure.Data;

namespace RemindersManagement.API.Infrastructure.Repositories
{
    public class RemindersRepository : BaseRepository, IRemindersRepository
    {
        public RemindersRepository(RemindersDbContext context) : base(context)
        {
        }

        public async Task<IEnumerable<Reminder>> ListAsync()
        {
            // AsNoTracking tells EF Core doesn't need to track changes on  entities
            return await _context.Reminders.AsNoTracking().ToListAsync();
        }

        public async Task<Reminder> FindAsync(Guid id)
        {
            var reminder = await _context.Reminders.FirstOrDefaultAsync(r => r.Id == id);
            return reminder;
        }

        public async Task<Reminder> AddAsync(Reminder reminder)
        {
            await _context.Reminders.AddAsync(reminder);
            _context.SaveChanges();
            return reminder;
        }

        public void Update(Reminder reminder)
        {
            _context.Reminders.Update(reminder);
            _context.SaveChanges();
        }

        public void Remove(Reminder reminder)
        {
            _context.Reminders.Remove(reminder);
            _context.SaveChanges();
        }
    }
}
  • Trong folder RemindersManagement.API sử dụng lệnh dotnet ef với mục đích
    • Tạo dữ liệu migration duy trì đồng bộ giữa data model và cơ sở dữ liệu
    • Sử dụng migration để khởi tạo hay cập nhật cơ sở dữ liệu
// create migration
dotnet ef migrations add InitialCreateDb -o Infrastructure/Migrations --context RemindersDbContext
// deploy migration
dotnet ef database update --context RemindersDbContext

Chú ý

Unit Of Work - Trong lớp RemindersRepository, các phương thức gọi đến _context.SaveChanges() để cập nhật dữ liệu xuống database. Trong trường hợp phức tạp hơn, khi cần lưu nhiều dữ liệu liên quan trong cùng một xử lý (transaction), hoặc cần thực hiện rollback khi hệ thống gặp lỗi, chúng ta thường kết hợp hai design patterns RepositoryUnitOfWork. Đối tượng UnitOfWork sẽ đóng gói DbContext, Repositories bên trong và thực hiện việc commit hay rollback các cập nhật dữ liệu liên quan tại cùng một thời điểm.

using System.Threading.Tasks;

namespace RemindersManagement.API.Domain.Interfaces
{
    public interface IUnitOfWork
    {
        IRemindersRepository RemindersRepository { get; }
        Task CommitAsync();
        Task RollbackAsync();
    }
}

Ngoài cách áp dụng migration để xây dựng cơ sở dữ liệu, có thể sử dụng phương thức EnsureCreated để khởi tạo cơ sở dữ liệu dựa trên các khai báo trong DbContext. Cách làm này cũng giúp tránh việc phải thực hiện lại lệnh dotnet ef mỗi khi tạo một môi trường phát triển mới.

Để áp dụng phương thức trên, Program.cs thay đổi lại như sau:

namespace RemindersManagement.API
{
    ...
    [ExcludeFromCodeCoverage]
    public class Program
    {
        public static int Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(Configuration)                  
                .CreateLogger();
            try
            {
                var host = CreateHostBuilder(args).Build();
                using (var scope =host.Services.CreateScope())
                {
                    var context = scope.ServiceProvider.GetService<RemindersDbContext>();
                    context.Database.EnsureCreated();
                }

                Log.Information("RemindersManagement is starting.......");
                host.Run();

                return 0;
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly");
                return 1;
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }        
    }
}

Application Layer

Chức năng của tầng Application tập trung vào việc cung cấp API cho phía client, những logic nghiệp vụ chỉ nên thực hiện trong tầng Services.

Trong folder Controllers, sử dụng dotnet aspnet-codegenerator để tạo code tự động cho đối tượng RemindersController.cs

# Turn on code gen trace for debug
export codegen_trace=1 

dotnet aspnet-codegenerator controller -name RemindersController -nv -outDir Controllers -m Reminder -dc RemindersDbContext -async -api

Các tham số trong câu lệnh:

  • -name: Tên controller
  • -nv: No View - không tạo View tương ứng
  • -outDir: Thư mục chứa controller
  • -m: Tên model
  • -dc: Tên DbContext
  • -async: Tạo method với cơ chế asynchronous
  • -api : Controller chỉ cung cấp API

Output:

[Trace]: Command Line: controller -name RemindersController -outDir Controllers -m Note -dc NotesDbContext -async -api
Building project ...
[Trace]: Command Line: --no-dispatch --port-number 49900 controller -name RemindersController -outDir Controllers -m Note -dc RemindersDbContext -async -api --dispatcher-version 3.1.4+b6993d609d406a42f2b09c27de832e70417f3bfc
Finding the generator 'controller'...
Running the generator 'controller'...
Attempting to compile the application in memory.
Attempting to figure out the EntityFramework metadata for the model and DbContext: 'Reminder'
Added Controller : '/Controllers/RemindersController.cs'.
RunTime 00:00:11.54

RemindersController được tạo ra sử dụng trực tiếp DbContext để truy vấn dữ liệu.

Theo kiến trúc multi-layers, Controller chỉ làm việc với database thông quan các lớp trung gian Services và Repositories. Do đó, RemindersController thay đổi lại như sau:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using RemindersManagement.API.Domain.Models;
using RemindersManagement.API.Domain.Services;

namespace RemindersManagement.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class RemindersController : ControllerBase
    {
        private readonly IRemindersService _remindersService;

        public RemindersController(IRemindersService remindersService)
        {
            _remindersService = remindersService;
        }

        // GET: api/reminders
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Reminder>>> GetReminders()
        {
            var reminders = await _remindersService.GetRemindersAsync();
            return Ok(reminders);
        }

        // GET: api/reminder/5
        [HttpGet("{id}")]
        public async Task<ActionResult<Reminder>> GetReminder(Guid id)
        {
            var reminder = await _remindersService.GetReminderAsync(id);
            if (reminder == null)
            {
                return NotFound();
            }
            return reminder;
        }

        // PUT: api/reminders/5
        [HttpPut("{id}")]
        public async Task<IActionResult> PutReminder(Guid id, Reminder reminder)
        {
            if (id != reminder.Id)
            {
                return BadRequest();
            }
            try
            {
                await _remindersService.PutReminderAsync(id, reminder);
            }
            catch (Exception ex)
            {
                // Handle exception
                return StatusCode(500, "A problem happened while handling your request.");
            }
            return NoContent();
        }

        // POST: api/reminders
        [HttpPost]
        public async Task<ActionResult<Reminder>> PostReminder(Reminder reminder)
        {
            await _remindersService.PutReminderAsync(reminder);
            return CreatedAtAction("GetReminder", new { id = reminder.Id }, reminder);
        }

        // DELETE: api/reminders/5
        [HttpDelete("{id}")]
        public async Task<ActionResult<Reminder>> DeleteReminder(Guid id)
        {
            var reminder = await _remindersService.GetReminderAsync(id);
            if (reminder == null)
            {
                return NotFound();
            }
            try
            {
                await _remindersService.DeleteReminder(id);
            }
            catch (Exception ex)
            {
                // Handle exception
                return StatusCode(500, "A problem happened while handling your request.");
            }            
            return reminder;
        }
    }
}

Biên dịch và thực thi

Trong solution folder FriendReminders thực thi lệnh:

dotnet run --urls="http://localhost:8000/" --project Services/RemindersManagement/RemindersManagement.API/RemindersManagement.API.csproj 

Sử dụng công cụ Postman để gửi yêu cầu đến Microservice

Postman Get Reminders List Yêu cầu danh sách Reminders

Postman Get Single Yêu cầu một Reminder

Postman Delete a Reminder Xoá một Reminder


Commit & Push

Một số câu lệnh git phổ biến:

  • git status - hiển thị danh sách các files thay đổi hoặc tạo mới
  • git add * - đưa các file cập nhật sang chế độ staging
  • git commit - commit các thay đổi trên môi trường local
  • git push cập nhật các thay đổi lên môi trường repository (CodeComit)

Trên AWS Console: CodeCommit -> FriendReminders xác nhận files được đưa lên.

CodeCommit AWS CodeCommit Source Code


Kết luận

Qua bài viết này chúng ta đã xây dựng một dịch vụ đơn giản sử dụng .NET Core, kết hợp việc biên dịch và thực thi ứng dụng trong môi trường Development. Client gửi yêu cầu đến API ứng dụng qua các phương thức HTTP Get, POST, PUT, DELETE và nhận về kết quả dưới định dạng JSON. Trong bài Web API - P.2 chúng ta tiếp tục hoàn thiện dịch vụ RemindersManagement với các chức năng Serilog, UnitOfWork pattern và cách thức giao tiếp với một dịch vụ khác thông qua giao thức HTTP.


Tài liệu tham khảo


Copyright © 2019-2022 Tuan Anh Le.