Is it a bad idea to use the builder pattern to construct elasticsearch queries in Python/Django?

So here's the issue. I am maintaining a somewhat large Django app and am tasked with creating a search bar that relies on elastic search data and need to in the near future convert a large number of Django DB queries into Elastic queries. I'm using python's elasticsearch.dsl package version 8.19.2 for this. I put a lot of thought into how to implement this and settled on the builder pattern . Assuming the app uses Articles as a model that needs to be represented as Elastic document. Here is my implementation of the builder pattern

from elasticsearch.dsl import Document, Text, Keyword
class ArticleDocument(Document):

    id = Integer()
    title = Text(
        fields={
            'keyword': Keyword()
        }
    )
    class Index:
        name = INDEX_VAR
from app import handle_elastic_connection

@dataclass
class ElasticSearchResults:
    count: int
    results: object


class ArticleRepository:
    def __init__(self, client: Elasticsearch):
        self.client = client

    def return_results(self, search: Search):
        # attach search object to elastic connection
        search = search.using(self.client)
        return ElasticSearchResults(results=search.execute(), count=search.count())

class ArticleQueryBuilder:
    def __init__(
        self
    ):
        # handles index refer
        self.search: Search = ArticleDocument.search() 

    def builder_method1(self, list_of_values:List):
        if list_of_values:
            self.search = self.search(Q('terms', key=list_of_values))
   
    def builder_method2(self, single_value:str):
        if single_value:
            method2_filter = Q('term', value1=single_value) | Q('term', value2=single_value)
            self.search = self.search(method2_filter)
    # .... more builder methods
    
    def build():
        return self.search


# Usage, most likely in view
ec = handle_elastic_connection()
repo = ArticleRepository(ec)

artcile_search = ArticleQueryBuilder.builder_method1().builder_method2.build()
results: ElasticSearchResults = repo.return_results(artcile_search)

I know the builder pattern gets a lot of hate in some corners of the internet but I'm not using this as a replacement for named parameters lol. It's to make query construction easier and more similar to Django's ORM langage. The downside is that it appears verbose here, but some of the builder methods are a lot more complicated than the example I gave so it feels right to not do all of that directly in the view. I did consider alternatives

  • Using elasticsearch.dsl Q object directly. I could just do this directly in the view, constructing the Search object with queries right there and just calling Search.execute() in the view itself but there are a lot of operations that are going to be reused over and over by different views tied to a single model. I know a simplified the example but one thing I'm doing is filtering results based on user permissions which I'm going to be doing a lot in multiple views I feel like it's enough to justify tucking it behind at least one layer of abstraction

  • Using the repository pattern was what everyone did at my previous job, but the problem with that is people would make a method that was only applicable to one use and returned one set of results from the database, and then there would be a ton of extraneous methods. The builder pattern gives you the flexibility to chain them together on a single query, which I will need to do

Are there alternatives I should consider? There's not a lot of examples or documentation I can find of elastic being used in Django project, and while I'm the sole maintainer of this repo at the moment. It will most likely be maintained by a more people in the future, so I'm being a little bit over-analytical at this stage of development.

Вернуться на верх