Dependency Injection (DI) cho hệ thống tìm kiếm

Công nghệ - 20/03/2025 04:45:16

Đã từng dùng nhiều Search Provider cùng lúc chưa? Bài viết này sẽ bật mí cách tích hợp Lucene, Solr và Elasticsearch bằng Dependency Injection — kèm ví dụ thực tế và gợi ý tối ưu hệ thống của bạn!

Tại sao Dependency Injection quan trọng trong hệ thống tìm kiếm?

Hãy tưởng tượng bạn đang xây dựng một hệ thống tìm kiếm sản phẩm cho một trang thương mại điện tử. Bạn bắt đầu với Lucene, nhưng sau một thời gian, hệ thống của bạn cần mở rộng, nhóm của bạn cần thử nghiệm với Solr hay Elasticsearch để tối ưu hiệu suất.

Nếu không sử dụng Dependency Injection (DI), bạn sẽ phải sửa đổi rất nhiều mã nguồn để thay đổi công cụ tìm kiếm. Nhưng với DI, bạn chỉ cần thay đổi một vài dòng cấu hình mà không cần đụng đến logic cốt lõi.

Bài viết này sẽ giúp bạn hiểu cách áp dụng DI để thiết kế một hệ thống tìm kiếm linh hoạt và dễ bảo trì trong .NET, cùng với các ví dụ thực tế sử dụng Lucene, Solr và Elasticsearch.


Dependency Injection là gì và tại sao nên dùng trong hệ thống tìm kiếm?

Dependency Injection (DI) là một kỹ thuật lập trình giúp giảm phụ thuộc giữa các thành phầnbằng cách cung cấp các phụ thuộc từ bên ngoài thay vì tạo chúng bên trong lớp.

Trong hệ thống tìm kiếm, điều này có nghĩa là bạn có thể trừu tượng hóa logic tìm kiếm qua một giao diện (interface), chẳng hạn ISearchService, và triển khai riêng cho Lucene, Solr, hoặc Elasticsearch. Điều này sẽ giúp chúng ta:

Dễ dàng thay đổi công cụ tìm kiếm: Bạn có thể chuyển từ Lucene sang Solr hoặc Elasticsearch chỉ bằng cách thay đổi cấu hình mà không cần chỉnh sửa code lõi.

Dễ kiểm thử: Bạn có thể mock dịch vụ tìm kiếm (ISearchService) để kiểm thử mà không cần kết nối thực tế.

Mã nguồn sạch hơn: Giúp tách biệt trách nhiệm giữa các thành phần, tăng khả năng bảo trì.

Nhược điểm của DI:

  • Tăng độ phức tạp ban đầu khi phải thiết kế giao diện (interface) và cấu hình.
  • Hiệu suất có thể bị ảnh hưởng nhẹ do cơ chế indirection.
  • Khó debug hơn so với cách tiếp cận trực tiếp.

Triển khai DI trong hệ thống tìm kiếm, với Lucene, Solr, và Elasticsearch

Chúng ta sẽ triển khai DI để xây dựng một dịch vụ tìm kiếm bài viết (Article) trong ASP.NET Core với Lucene, Solr và Elasticsearch.

1. Định nghĩa Interface chung

Để đảm bảo tính linh hoạt, chúng ta sẽ trừu tượng hóa logic tìm kiếm thông qua interface ISearchService:

public interface ISearchService
{
    void IndexArticle(Article article);
    List<Article> SearchArticles(string query);
}

public class Article
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

Triển khai với Lucene.NET

Lucene.NET là một thư viện tìm kiếm mạnh mẽ dành cho .NET. Trước tiên, cài đặt gói NuGet:

dotnet add package Lucene.Net

Sau đó, tạo dịch vụ LuceneSearchService:

using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;

public class LuceneSearchService : ISearchService
{
    private readonly string _indexPath;
    private readonly IndexWriter _writer;

    public LuceneSearchService(string indexPath = "lucene_index")
    {
        _indexPath = indexPath;
        var directory = FSDirectory.Open(_indexPath);
        var analyzer = new StandardAnalyzer(LuceneVersion.LUCENE_48);
        _writer = new IndexWriter(directory, new IndexWriterConfig(LuceneVersion.LUCENE_48, analyzer));
    }

    public void IndexArticle(Article article)
    {
        var doc = new Document
        {
            new StringField("Id", article.Id.ToString(), Field.Store.YES),
            new TextField("Title", article.Title, Field.Store.YES),
            new TextField("Content", article.Content, Field.Store.YES)
        };
        _writer.AddDocument(doc);
        _writer.Commit();
    }

    public List<Article> SearchArticles(string query)
    {
        var directory = FSDirectory.Open(_indexPath);
        var reader = DirectoryReader.Open(directory);
        var searcher = new IndexSearcher(reader);
        var parser = new MultiFieldQueryParser(LuceneVersion.LUCENE_48, new[] { "Title", "Content" }, new StandardAnalyzer(LuceneVersion.LUCENE_48));
        var luceneQuery = parser.Parse(query);

        var hits = searcher.Search(luceneQuery, 10).ScoreDocs;
        var results = new List<Article>();

        foreach (var hit in hits)
        {
            var doc = searcher.Doc(hit.Doc);
            results.Add(new Article
            {
                Id = int.Parse(doc.Get("Id")),
                Title = doc.Get("Title"),
                Content = doc.Get("Content")
            });
        }

        reader.Dispose();
        return results;
    }
}

Cấu hình DI trong ASP.NET Core

Để sử dụng DI, hãy đăng ký dịch vụ tìm kiếm trong Program.cs:

builder.Services.AddSingleton<ISearchService>(provider =>
{
    var config = provider.GetRequiredService<IConfiguration>();
    return config["SearchEngine"] switch
    {
        "Lucene" => new LuceneSearchService(),
        "Solr" => new SolrSearchService(config),
        "Elastic" => new ElasticSearchService(config),
        _ => throw new NotSupportedException("Unsupported search engine")
    };
});

Chúng ta chưa có SolrSearchService và ElasticSearchService, và ta sẽ tiếp tục triển khai các search service ở bên dưới.


Triển khai với Solr

Cài đặt gói SolrNet qua NuGet. Đảm bảo Solr server đã chạy và có core "articles" với schema phù hợp. Lớp Article cần được đánh dấu với thuộc tính Solr:

[SolrDocument("articles")]
public class Article
{
    [SolrField("id", true)]
    public int Id { get; set; }
    [SolrField("title")]
    public string Title { get; set; }
    [SolrField("content")]
    public string Content { get; set; }
}

Sau đó, tạo dịch vụ SolrSearchService

using SolrNet;

public class SolrSearchService : ISearchService
{
    private readonly ISolrOperations<Article> _solr;

    public SolrSearchService(IConfiguration config)
    {
        var solrUrl = config["Solr:Url"];
        var connection = new SolrConnection(solrUrl);
        _solr = connection.GetSolrOperations<Article>();
    }

    public void IndexArticle(Article article)
    {
        _solr.Add(article);
        _solr.Commit();
    }

    public List<Article> SearchArticles(string query)
    {
        var results = _solr.Query(new SolrQuery(query));
        return results.ToList();
    }
}

Triển khai với Elasticsearch

Cài đặt gói Nest qua NuGet. Đảm bảo Elasticsearch server đã chạy và index "articles" được tạo.

Triển khai ElasticSearchService

using Nest;

public class ElasticSearchService : ISearchService
{
    private readonly ElasticClient _client;

    public ElasticSearchService(IConfiguration config)
    {
        var elasticUrl = config["Elastic:Url"];
        var connection = new ConnectionSettings(new Uri(elasticUrl));
        _client = new ElasticClient(connection);
    }

    public void IndexArticle(Article article)
    {
        _client.Index(article, i => i.Index("articles").Id(article.Id.ToString()));
        _client.Refresh("articles");
    }

    public List<Article> SearchArticles(string query)
    {
        var response = _client.Search<Article>(s => s
            .Index("articles")
            .Query(q => q
                .MultiMatch(m => m
                    .Fields(f => f
                        .Field("title")
                        .Field("content")
                    )
                    .Query(query)
                )
            )
            .Size(10)
        );
        return response.Documents.ToList();
    }
}

Phương án triển khai thực tế

  • Với Lucene: Lưu chỉ mục trên SSD, quản lý concurrency cẩn thận.
  • Với Solr/Elasticsearch: Xử lý kết nối, thêm retry logic cho lỗi mạng, và cân nhắc caching cho kết quả tìm kiếm thường xuyên.

Kết luận

Với Dependency Injection, chúng ta có thể xây dựng một hệ thống tìm kiếm linh hoạt, dễ mở rộng, và dễ kiểm thử. Dù chọn Lucene, Solr hay Elasticsearch, DI giúp bạn dễ dàng chuyển đổi mà không ảnh hưởng đến logic ứng dụng, đồng thời giúp bạn chuyển đổi và mở rộng hệ thống theo từng bài toán triển khai.

Nguồn tham khảo

🚀 Bạn đã từng triển khai hệ thống tìm kiếm chưa? Hãy chia sẻ kinh nghiệm của bạn trong phần bình luận nhé!

#search #Data #SearchEngine #Lucene #Elasticsearch #Solr #FullTextSearch #NETDeveloper #TechBlog

#wecommit100xshare #1PercentBetter

 

Tôi đi xây dựng Hệ thống Tìm kiếm - bài 4

/Son Do, 1 ông dev thích xây dựng Hệ thống Tìm kiếm

Công nghệ

Xem tất cả