Domain Deriven Design – Tactical Patterns Notes

Professional Search with C# and Elasticsearch – Part 3
آبجکت (Object)

Professional Search with C# and Elasticsearch – Part 3

Table of Contents

Domain-Driven Design (DDD) یک متدولوژی برای توسعه نرم‌افزار است که تاکید بر درک عمیق از دامنه مسئله (Domain) دارد.

Domain Model  از اجزای مختلف دامنه مسئله و ارتباطات بین آن‌ها تشکیل شده است.

Entity :

Rich Domain Model : یه عالمه عملکرد (Behavior) داخلش هستش

Anemic Domain Model : فقط دیتا داخلش هستش

Value Object

Aggregate

Repository

				
					using Elasticsearch.Net;
using Nest;

namespace Songs.Api.Elastic;

public interface ISearchService
{
    Task<ISearchResponse<ElasticSong>> SearchAsync(SearchParameters parameters,
        CancellationToken cancellationToken);
}

public class SearchService : ISearchService
{
    private readonly IElasticClient _elasticClient;

    public SearchService(IElasticClient elasticClient)
    {
        _elasticClient = elasticClient;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="parameters">parameters: یک شیء از نوع SearchParameters که حاوی پارامترهای جستجو است.</param>
    /// <param name="cancellationToken">یک توکن انصراف برای لغو عملیات ناهمزمان.</param>
    /// <returns></returns>
    public async Task<ISearchResponse<ElasticSong>> SearchAsync(SearchParameters parameters,
        CancellationToken cancellationToken)
    {
        //این بخش از کد یک جستجوی Elasticsearch را با استفاده از _elasticClient اجرا می‌کند.
        var result = await _elasticClient.SearchAsync<ElasticSong>(x => x   //خروجی ElasticSong
            .Query(q => q
                .Bool(b => b  //
                    .Should(s => s
                        .MultiMatch(m => m
                            .Fields(f => f
                                .Field(ff => ff.Title, boost: 2)
                                .Field(ff => ff.AlbumTitle)
                                .Field(ff => ff.ArtistName, boost: 3)
                            )
                            .Query(parameters.SearchText)  //از MultiMatch برای جستجو در چندین فیلد با استفاده از parameters.SearchText با فاصله‌گذاری اجزای کلمه استفاده می‌کند.
                            .Fuzziness(Fuzziness.Auto)  
                        )
                    )
                    .MinimumShouldMatch(1)
                    .Filter(f => f
                        .Term(t => t.Genre, parameters.Genre)
                    )
                )
            )
            .Sort(s => s.Descending(SortSpecialField.Score))
            .Skip(parameters.Skip)
            .Take(parameters.Take
            ), cancellationToken);

        return result;
    }
}
				
			

این کد یک سرویس به نام `SearchService` و یک رابط به نام `ISearchService` را در فضای نام `Songs.Api.Elastic` ایجاد می‌کند. این سرویس برای انجام جستجو در Elasticsearch بر اساس پارامترهای ورودی مشخص شده به `SearchAsync` استفاده می‌شود. در زیر توضیحاتی در مورد این کد آورده شده است:

1. **`ISearchService` Interface:**
– این رابط یک تعریف از متد `SearchAsync` دارد که برای انجام جستجو در Elasticsearch با استفاده از `SearchParameters` و لغو عملیات (`CancellationToken`) مورد استفاده قرار می‌گیرد.

2. **`SearchService` Class:**
– **Constructor:** در کانستراکتور این کلاس، یک وابستگی به `IElasticClient` (که یک نمونه از `ElasticClient` از کتابخانه Nest برای ارتباط با Elasticsearch است) دریافت می‌شود.

– **`SearchAsync` Method:** این متد جستجوی Elasticsearch را انجام می‌دهد. در این متد:
– یک جستجوی پیچیده با استفاده از `SearchAsync` انجام می‌شود.
– در بخش `Query` از `MultiMatch` برای جستجو در چند فیلد با اهمیت‌های مختلف استفاده می‌شود. هر فیلد با یک وزن مشخص (boost) و با استفاده از `Fuzziness` برای تطابق در صورت وجود اشتباهات در کلمات انتخاب شده است.
در اینجا، `parameters.SearchText` مقداری است که از سوی کاربر یا سیستم برای انجام جستجو در Elasticsearch مشخص شده است.

`.Query(parameters.SearchText)` در داخل بخش `MultiMatch` جستجوی Elasticsearch استفاده شده است. این قسمت از کد به تنظیم متن جستجویی و اعمال آن به چندین فیلد در Elasticsearch می‌پردازد.

متد `MultiMatch` به شما این امکان را می‌دهد که یک جستجوی متنی را به چندین فیلد اعمال کنید. در اینجا، فیلدهای `Title`، `AlbumTitle` و `ArtistName` برای جستجو مشخص شده‌اند. با اختصاص وزن‌های مختلف (boost) به هرکدام از این فیلدها، می‌توانید تأثیر هرکدام را بر جستجو تعیین کنید. به عنوان مثال، با اختصاص وزن 2 به `Title` و وزن 3 به `ArtistName`، اهمیت این فیلدها در جستجو تغییر می‌کند.

در اینجا، `parameters.SearchText` متن جستجوی وارد شده توسط کاربر یا سیستم را نشان می‌دهد و این متن در فیلدهای مشخص شده برای جستجو مورد استفاده قرار می‌گیرد.
– بخش `Filter` برای اعمال شرایط فیلتر بر اساس مقادیر مشخص شده از `parameters` (مانند `Genre`) استفاده می‌شود.
– سپس نتایج بر اساس امتیاز جستجو مرتب می‌شوند.
– در نهایت، با استفاده از `Skip` و `Take`، تعداد نتایج مورد نظر برگردانده می‌شود.

– **بهینه‌سازی و انتخاب‌ها:**
– از `CancellationToken` برای امکان لغو جستجو در صورت نیاز استفاده شده است.
– از `Fuzziness.Auto` برای تطابق خودکار در صورت وجود اشتباهات کمی در کلمات استفاده شده است.
– از `MinimumShouldMatch(1)` برای تعیین حداقل تعداد شرایطی که باید برآورده شوند استفاده شده است.

تنظیمات Program.cs
				
					builder.Services.AddScoped<ISearchService, SearchService>();
				
			
Controller
				
					using Microsoft.AspNetCore.Mvc;
using Songs.Api.Contracts;
using Songs.Api.Elastic;

namespace Songs.Api.Controllers;

[ApiController]
[Route("songs")]
public class SongsController : ControllerBase
{
    private readonly ISearchService _searchService;

    public SongsController(ISearchService searchService)
    {
        _searchService = searchService;
    }

    [HttpGet]
    public async Task<IActionResult> Search([FromQuery]SearchSongsRequest request, CancellationToken cancellationToken)
    {
        var parameters = request.ToSearchParameters();
        var songsResponse = await _searchService.SearchAsync(parameters, cancellationToken);
        var songs = songsResponse.Documents;
        var count = songsResponse.Total;
        var response = new SearchSongsResponse(songs.Select(x => x.ToSongResponse()), request.PageNumber,
            request.PageSize, count, (int)Math.Ceiling(count / (double)request.PageSize));
        return Ok(response);
    }
    
    [HttpGet("include-score")]
    public async Task<IActionResult> SearchIncludeScore([FromQuery]SearchSongsRequest request, CancellationToken cancellationToken)
    {
        var parameters = request.ToSearchParameters();
        var songsResponse = await _searchService.SearchAsync(parameters, cancellationToken);
        var count = (int)songsResponse.Total;
        var response = new SearchSongsIncludeScoresResponse(songsResponse.Hits.Select(x => x.ToSongResponseWithScore()), request.PageNumber,
            request.PageSize, count, (int)Math.Ceiling(count  / (double)request.PageSize));
        return Ok(response);
    }

}
				
			

این کد یک کنترلر ASP.NET Core به نام `SongsController` را ایجاد می‌کند که با استفاده از `ISearchService` برای انجام جستجوی Elasticsearch بر اساس درخواست‌های کاربر یا سیستم مرتبط با موزیک‌ها استفاده می‌شود. در زیر توضیحاتی در مورد این کد آورده شده است:

1. **`SongsController` Class:**
– **Constructor:** در کانستراکتور این کلاس، یک وابستگی به `ISearchService` دریافت می‌شود. این سرویس برای اجرای جستجوی Elasticsearch مورد استفاده قرار می‌گیرد.

– **`Search` Action (GET /songs):** این اکشن برای انجام جستجوی معمولی Elasticsearch بر اساس پارامترهایی که از طریق query string درخواست شده‌اند، طراحی شده است. از این اکشن برای بازگرداندن لیست موزیک‌ها استفاده می‌شود.

– **`SearchIncludeScore` Action (GET /songs/include-score):** این اکشن نیز برای انجام جستجوی Elasticsearch طراحی شده است، اما اینبار با درخواست نمایش امتیاز (score) هر نتیجه. از این اکشن برای بازگرداندن لیست موزیک‌ها به همراه امتیاز هرکدام استفاده می‌شود.

هر دو اکشن `Search` و `SearchIncludeScore` از یک مدل درخواست به نام `SearchSongsRequest` برای دریافت پارامترها از query string استفاده می‌کنند و نتیجه جستجو را با استفاده از مدل‌های پاسخ `SearchSongsResponse` و `SearchSongsIncludeScoresResponse` به صورت JSON به کلاینت باز می‌گردانند.

این نمونه کد از معماری RESTful API در ASP.NET Core استفاده کرده و از استانداردهای نام‌گذاری مربوط به آدرس‌ها و نحوه ارتباط با مسیرها پیروی می‌کند.