> For the complete documentation index, see [llms.txt](https://quyvu.gitbook.io/quy/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://quyvu.gitbook.io/quy/v0-bat-dau-phat-trien/quickstart.md).

# Ý tưởng, những dòng code đầu tiên...

Cách đây một thời gian, vào khoảng tháng 1/2023, nhóm của tôi triển khai một dự án theo kiến trúc microservices và gặp phải vấn đề trong việc ánh xạ dữ liệu giữa các services...

### Ý tưởng

Ý tưởng về OfX (tên hiện tại, ban đầu chưa có tên) xuất phát từ nhu cầu đơn giản hóa việc mapping dữ liệu giữa các dịch vụ trong hệ thống microservices.

Khi đó, dự án của chúng tôi phát triển nhanh chóng, một phần do yêu cầu tiến độ (một phần do tôi đề xuất lãnh đạo xây dựng lại phần mềm hiện tại). Tôi đã xây dựng code base và kiến trúc cho dự án. Mọi thứ có vẻ ổn định cho đến khi chúng tôi gặp vấn đề trong việc ánh xạ dữ liệu khi trả về cho Front-End để hiển thị. Ban đầu, hai bên phối hợp, nhường nhịn nhau một chút; có những chỗ phía Back-End trả về dữ liệu dạng ID và phía Front-End dựa vào đó gọi API để lấy dữ liệu khác. Trường hợp đơn giản nhất là lấy tên của một ID nào đó (ví dụ: Back-End trả về payload chứa organizationId, Front-End gọi API getOrganizations để lấy thông tin tên...). Ban đầu, cách làm này tạm ổn nhưng hiệu năng ngày càng giảm và công sức bỏ ra cũng ngày một lớn. Lúc đó, tôi cũng chịu áp lực phải xây dựng lại phần mềm nhanh chóng nên chưa có thời gian tìm cách tối ưu hóa việc mapping dữ liệu giữa các dịch vụ.

Vào một ngày nào đó trong năm 2023 (hiện tại tôi không nhớ rõ thời gian), tôi ngồi lại với anh em trong nhóm phát triển Back-End và nói: "Em thấy việc mapping dữ liệu này cực quá, mà code lại trùng lặp nhiều, sai sót cũng nhiều, nhìn trông quá xấu, để em xử lý vụ ánh xạ dữ liệu cho!". Rồi tôi bắt đầu nghĩ xem liệu có cách nào để làm việc này tiết kiệm công sức cho anh em không.

**Những ý tưởng bắt đầu.**

Đầu tiên, tôi nghĩ đến việc cấu hình các thuộc tính của một đối tượng phản hồi bằng một cách nào đó hiệu quả và vẫn thực hiện được ý tưởng loose-coupling mà tôi luôn theo đuổi!

**Ý tưởng 1:** Qua trải nghiệm thực tế với thư viện **FluentValidation**, tôi thấy việc tạo ra một lớp con có vẻ ổn và đẹp. Tôi tưởng tượng sẽ như thế này:

<pre class="language-csharp"><code class="lang-csharp"><strong>// Sample response model
</strong><strong>public class MemberResponse
</strong>{
    public string Id { get; set; }
    public string UserId { get; set; }
    public string UserName { get; set; }
}
// Sample mapping abstractClass
public class MemberResponseMapping : AbstractMapping&#x3C;MemberResponse>
{
    public MemberResponseMapping()
    {
        MapFor(m => m.UserName, async memberResponse => {
            // How to get UserName data, seems we can use MediatR to handle how to get data
        });
    }
}
</code></pre>

Ý tưởng này có vẻ được nhưng gặp phải vấn đề:

1. Nếu viết như vậy thì số lượng file phải tạo cho mỗi ModelDTO quá nhiều (chưa nói đến việc sẽ xử lý như thế nào).
2. Có thể sẽ phải trùng lặp `AbstractMapping`, có vẻ như nếu viết như vậy thì nhiều mã quá... Chắc phải tìm phương án khác thôi!

**Ý tưởng 2:** Liệu rằng `Attribute` có khả thi không nhỉ? Ngay lập tức trong đầu tôi hiện ra code sẽ trông giống như thế này:

```csharp
public class MemberResponse
{
    public string Id { get; set; }
    public string UserId { get; set; }
    [UserFor(x => x.UserId)] public string UserName { get; set; }
}
```

Đầu tiên mình rất muốn viết như vậy, rõ ràng và biểu thức truyền vào là 1 `Expression`, lúc ý cũng chưa nghĩ làm cách nào để xử lý việc làm thế nào để mapping được data, trước mắt cứ nghĩ có cách để application biết được cái nào cần mapping đã.&#x20;

Nhưng thực tế lại phũ phàng 1 lần nữa, nếu mình truyền `Expression` lúc ý thì `UserForAttribute`phải là generic class như là `UserForAttribute<TResponse>` thì mình mới có thể sử dụng được `Expression` vì là mình không biết được mình đang Map cho cái nào trong lúc compile-time.

Sau nhiều lần đắn đo, mình nghĩ có khi phải dùng `PropertyName` thôi, và cách đơn giản nhất là `UserFor(nameof(UserId)).` Tạm thời bài toán được giải quyết 1 chút, thống nhất được response sẽ trông như thế nào...

À quên, ban đầu mình cũng không nhớ rõ là mình đặt là `UserForAttribute` hay là gì đâu :smile:!

Tới lúc bắt đầu tính tìm cách implement thì mình có nghĩ lại và chuyển thành `UserOfAttribute.` Đó cũng là lý do vì sao hiện tại lại là OfX chứ không phải là ForX...(chữ X mình sẽ nói sau vì sao lại là X).

Từ ý tưởng 2, mình cảm thấy rất khả quan, mình cũng có đưa cho anh em xem thử việc viết như vậy có làm anh em thấy thoải mái không, và nhận được phản hồi rất tốt(thực tế lúc ý team mình chỉ có 3 Back-End là bao gồm cả mình). Vậy là ý tưởng ban đầu đã được hình thành và đó là những bức hình sơ khai của `OfX` !&#x20;

Hãy cùng tìm hiểu về lịch sử hình thành nhé!

***

### Những dòng code đầu tiên...

Khi mà ý tưởng bắt đầu nhen nhóm, mình quyết định bắt tay vào làm thôi... Hôm ý mình có nói chuyện với anh em trong team mình. "Nay chắc em sẽ ngồi xử quả mapping data kia nhé anh em!"

Mọi thứ ban đầu có vẻ bộn bề, cái gì làm trước, cái gì làm sau bây giờ nhỉ?&#x20;

Okey, cứ bắt đầu từ việc tạo `Attribute` đã. Cứ thử nghiệm rồi xem như thế nào, mình tạo vài cái Attribute như là `UserOfAttribute, ProvinceOfAttribute, DistrictOfAttribute...` (đương nhiên là nó có chung 1 implement là interface nào đó, tới giờ mình cũng không nhớ nữa\~\~\~.

**Bước 1**: Tìm cách collect được hết các properties của 1 object response chứa các `Attribute`trên. Cái này cũng không khó lắm, ban đầu chỉ có mấy cái như là object, nested object, array...

**Bước 2**: Làm sao để lấy được data nhỉ? Qua bước 1 là mình đã có được các properties, object chứ properties đó và các Attribute tương ứng với các properties đó nữa. Giờ là lúc (nhìn lại 1 năm đã qua :smile:, đùa chút thôi ạ!) bắt đầu triển chiêu thôi!

Ở đây mình có quên chưa kể 1 việc khác với anh em, ở dự án của mình, mình đã xử lý xong việc `dynamic consumer, dynamic producer...`, việc giao tiếp giữa các service giờ đây đã dễ dàng hơn nhiều rồi, không cần code nhiều cho phần ý. Mình có 1 service là Centralize, service này như `Gateway` ý, nó sẽ hứng requests và thực hiện request tới các service khác thông qua message(ở dự án  của mình thì mình dùng Request/Reply pattern cho việc này, mình sử dụng `Masstransit` là thư viện để thực hiện việc giao tiếp, sử dụng `MediatR` để tối ưu hoá việc thực hiện request, kết hợp 2 cái này thì quá là tuyệt vời luôn!).&#x20;

Việc đầu tiên sau khi test thử nghiệm việc get được hết các properties kia thì mình bắt đầu tạo ra 1 cái Dictionary để mapping `Attribute` với cả `RequestContract`. Ví dụ `UserOfAttribute` với `GetUsersQuery` rồi sau đó mình group lại các Attributes và gom lại các Ids để chuẩn bị việc Send Request đi để lấy data thôi!

Mọi thứ dẫn đi đúng hướng, mình có thành quả ban đầu, việc lấy data đã thành công và có thể set được dữ liệu vào trường đã mark! Hic, cảm xúc lúc ý tuyệt vời quá trời luôn ý. Mình nghĩ tới việc ít nữa giảm được lượng lớn code là vui lắm rồi, hơn nữa cái này cũng cảm giác như là 1 sự sáng tạo nữa chứ, keke!&#x20;

Đấy, bước đầu của OfX là như vậy đấy, thô sơ, tận dụng request đã viết trước đó rồi mapping data (tuy nhiên việc reuqest trước đó chưa được tối ưu riêng cho `Attribute mapping`, thừa dữ liệu nhiều, dẫn tới việc performance chưa được thực sự tốt!).&#x20;

*Những dòng code ban đầu...*

```csharp
...
var districtDatas = GetPropertyToMapDatasOf<DistrictOfAttribute>(propertyToMapDatas);
var languageDatas = GetPropertyToMapDatasOf<LanguageOfAttribute>(propertyToMapDatas);
var provinceDatas = GetPropertyToMapDatasOf<ProvinceOfAttribute>(propertyToMapDatas);
var userEmailDatas = GetPropertyToMapDatasOf<UserEmailOfAttribute>(propertyToMapDatas);
var userNameDatas = GetPropertyToMapDatasOf<UserNameOfAttribute>(propertyToMapDatas);
...
```

<pre class="language-csharp"><code class="lang-csharp"><strong>...
</strong><strong>var languageIds = languageDatas
</strong>            .Where(x => Guid.TryParse(x.Id, out _))
            .Select(x => new LanguageId(Guid.Parse(x.Id)))
            .Distinct()
            .ToList();
var provinceIds = provinceDatas
            .Where(x => Guid.TryParse(x.Id, out _))
            .Select(x => new ProvinceId(Guid.Parse(x.Id)))
            .Distinct()
            .ToList();
...
</code></pre>

```csharp
...
var districtFuncTask = () =>
{
    if (districtIds is not { Count: > 0 })
        return Task.FromResult(new PaginationResponse<DistrictResponse>(new List<DistrictResponse>(), 0));
    return _sender.Send(new GetDistrictsQuery(null, null, districtIds));
};
var userFuncTask = () =>
{
    if (userIds is not { Count: > 0 })
        return Task.FromResult(new List<ApplicationUserData>());
    return _sender.Send(new GetApplicationUsersRequest(userIds));
};
...
```

```csharp
propertyToMapDatas.ForEach(x =>
{
    var valueSet = x.AttributeType switch
    {
        ...
        DistrictOfAttribute => districtTask.Result.Items.FirstOrDefault(a => a.Id == x.Id)?.Name,
        JobCategoryOfAttribute => jobCategoryTask.Result.Items.FirstOrDefault(a => a.Id == x.Id)?.Name,
        JobSourceOfAttribute => jobSourceTask.Result.Items.FirstOrDefault(a => a.Id == x.Id)?.Name,
        ProvinceOfAttribute => provinceTask.Result.Items.FirstOrDefault(a => a.Id == x.Id)?.Name,
        UserEmailOfAttribute => userTask.Result.FirstOrDefault(a => a.UserId == x.Id)?.Email,
        UserNameOfAttribute => userTask.Result.FirstOrDefault(a => a.UserId == x.Id)?.FullName,
        _ => default
    };
    x.PropertyInfo.SetValue(x.Model, valueSet);
});
```
