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 (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:
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.
Để đả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; }
}
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;
}
}
Để 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.
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();
}
}
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 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