फैक्ट्री के तरीके बनाम पायथन में इंजेक्ट फ्रेमवर्क - क्लीनर क्या है?


9

मैं आमतौर पर अपने अनुप्रयोगों में क्या करता हूं कि मैं कारखाने के तरीकों का उपयोग करके अपनी सभी सेवाएं / डाओ / रेपो / ग्राहक बनाता हूं

class Service:
    def init(self, db):
        self._db = db

    @classmethod
    def from_env(cls):
        return cls(db=PostgresDatabase.from_env())

और जब मैं ऐप बनाता हूं तो मैं करता हूं

service = Service.from_env()

क्या सभी निर्भरता बनाता है

और परीक्षणों में जब मैं असली डीबी का उपयोग नहीं करना चाहता, तो मैं सिर्फ डीआई करता हूं

service = Service(db=InMemoryDatabse())

मुझे लगता है कि सेवा से काफी हद तक साफ / हेक्स आर्किटेक्चर है क्योंकि एक डेटाबेस बनाना जानता है और यह जानता है कि यह किस प्रकार का डेटाबेस बनाता है (InMemoryDatabse या MongoDatabase भी हो सकता है)

मुझे लगता है कि स्वच्छ / हेक्स वास्तुकला में मेरे पास होगा

class DatabaseInterface(ABC):
    @abstractmethod
    def get_user(self, user_id: int) -> User:
        pass

import inject
class Service:
    @inject.autoparams()
    def __init__(self, db: DatabaseInterface):
        self._db = db

और मैं करने के लिए इंजेक्टर फ्रेमवर्क स्थापित करूंगा

# in app
inject.clear_and_configure(lambda binder: binder
                           .bind(DatabaseInterface, PostgresDatabase()))

# in test
inject.clear_and_configure(lambda binder: binder
                           .bind(DatabaseInterface, InMemoryDatabse()))

और मेरे प्रश्न हैं:

  • क्या मेरा रास्ता वाकई खराब है? क्या यह अब एक स्वच्छ वास्तुकला नहीं है?
  • इंजेक्शन का उपयोग करने के क्या फायदे हैं?
  • क्या यह परेशान और इंजेक्शन फ्रेमवर्क का उपयोग करने के लायक है?
  • क्या डोमेन को बाहर से अलग करने के कोई और बेहतर तरीके हैं?

जवाबों:


1

डिपेंडेंसी इंजेक्शन तकनीक में कई मुख्य लक्ष्य शामिल हैं, (लेकिन इन तक सीमित नहीं):

  • आपके सिस्टम के कुछ हिस्सों के बीच युग्मन को कम करना। इस तरह आप कम प्रयास से प्रत्येक भाग को बदल सकते हैं। देखें "उच्च सामंजस्य, कम युग्मन"
  • जिम्मेदारियों के बारे में सख्त नियम लागू करना। एक इकाई को अमूर्त के अपने स्तर पर केवल एक ही काम करना चाहिए। अन्य संस्थाओं को इस पर निर्भरता के रूप में परिभाषित किया जाना चाहिए। "आईओसी" देखें
  • बेहतर परीक्षण अनुभव। स्पष्ट निर्भरताएं आपको अपने सिस्टम के विभिन्न हिस्सों को कुछ आदिम परीक्षण व्यवहार के साथ स्टब करने की अनुमति देती हैं, जिसमें आपके उत्पादन कोड की तुलना में एक ही सार्वजनिक एपीआई है। देखें "Mocks नहीं हैं 'स्टब्स"

दूसरी बात यह ध्यान रखने की है कि हम आमतौर पर अमूर्तता पर भरोसा करेंगे, कार्यान्वयन पर नहीं। मैं बहुत से ऐसे लोगों को देखता हूं जो केवल विशेष कार्यान्वयन को इंजेक्ट करने के लिए DI का उपयोग करते हैं। बड़ा अंतर है।

क्योंकि जब आप इंजेक्शन लगाते हैं और एक कार्यान्वयन पर भरोसा करते हैं, तो वस्तुओं को बनाने के लिए हम किस पद्धति का उपयोग करते हैं, इसमें कोई अंतर नहीं है। यह सिर्फ मायने नहीं रखता। उदाहरण के लिए, यदि आप requestsउचित अमूर्त के बिना इंजेक्ट करते हैं, तो आपको अभी भी समान विधियों, हस्ताक्षर और वापसी प्रकारों के साथ कुछ भी समान की आवश्यकता होगी। आप इस कार्यान्वयन को बिल्कुल भी बदलने में सक्षम नहीं होंगे। लेकिन, जब आप fetch_order(order: OrderID) -> Orderइसे इंजेक्ट करते हैं तो इसका मतलब है कि कुछ भी अंदर हो सकता है। requests, डेटाबेस, जो भी हो।

चीजों को योग करने के लिए:

इंजेक्शन का उपयोग करने के क्या फायदे हैं?

मुख्य लाभ यह है कि आपको अपनी निर्भरता को मैन्युअल रूप से इकट्ठा नहीं करना है। हालांकि, यह एक बड़ी लागत के साथ आता है: आप समस्याओं को हल करने के लिए जटिल, यहां तक ​​कि जादुई, उपकरणों का उपयोग कर रहे हैं। एक दिन या कोई और जटिलता आपको वापस लड़ेगी।

क्या यह परेशान और इंजेक्शन फ्रेमवर्क का उपयोग करने के लायक है?

injectविशेष रूप से रूपरेखा के बारे में एक और बात । मुझे पसंद नहीं है जब मैं वस्तुओं को इंजेक्ट करता हूं जहां इसके बारे में कुछ पता चलता है। यह एक कार्यान्वयन विस्तार है!

Postcardउदाहरण के लिए, विश्व डोमेन मॉडल में यह कैसे पता चलता है?

मैं punqसरल मामलों और dependenciesजटिल लोगों के लिए उपयोग करने की सलाह दूंगा।

inject"निर्भरता" और वस्तु गुणों के स्वच्छ पृथक्करण को भी लागू नहीं करता है। जैसा कि कहा गया था, DI का मुख्य लक्ष्य कठोर जिम्मेदारियों को लागू करना है।

इसके विपरीत, मुझे यह दिखाने दो कि कैसे punqकाम करता है:

from typing_extensions import final

from attr import dataclass

# Note, we import protocols, not implementations:
from project.postcards.repository.protocols import PostcardsForToday
from project.postcards.services.protocols import (
   SendPostcardsByEmail,
   CountPostcardsInAnalytics,
)

@final
@dataclass(frozen=True, slots=True)
class SendTodaysPostcardsUsecase(object):
    _repository: PostcardsForToday
    _email: SendPostcardsByEmail
    _analytics: CountPostcardInAnalytics

    def __call__(self, today: datetime) -> None:
        postcards = self._repository(today)
        self._email(postcards)
        self._analytics(postcards)

देख? हमारे पास कंस्ट्रक्टर भी नहीं है। हम घोषित रूप से अपनी निर्भरता को परिभाषित करते हैं और punqस्वचालित रूप से उन्हें इंजेक्ट करेंगे। और हम किसी भी विशिष्ट कार्यान्वयन को परिभाषित नहीं करते हैं। केवल प्रोटोकॉल का पालन करें। इस शैली को "कार्यात्मक ऑब्जेक्ट्स" या SRP -styled कक्षाएं कहा जाता है ।

तब हम punqकंटेनर को स्वयं परिभाषित करते हैं:

# project/implemented.py

import punq

container = punq.Container()

# Low level dependencies:
container.register(Postgres)
container.register(SendGrid)
container.register(GoogleAnalytics)

# Intermediate dependencies:
container.register(PostcardsForToday)
container.register(SendPostcardsByEmail)
container.register(CountPostcardInAnalytics)

# End dependencies:
container.register(SendTodaysPostcardsUsecase)

और इसका उपयोग करें:

from project.implemented import container

send_postcards = container.resolve(SendTodaysPostcardsUsecase)
send_postcards(datetime.now())

देख? अब हमारी कक्षाओं को पता नहीं है कि उन्हें कौन और कैसे बनाता है। कोई सज्जाकार नहीं, कोई विशेष मूल्य नहीं।

SRP- स्टाइल कक्षाओं के बारे में यहाँ पढ़ें:

क्या डोमेन को बाहर से अलग करने के कोई और बेहतर तरीके हैं?

आप जरूरी के बजाय कार्यात्मक प्रोग्रामिंग अवधारणाओं का उपयोग कर सकते हैं। फ़ंक्शन निर्भरता इंजेक्शन का मुख्य विचार यह है कि आप उन चीजों को कॉल नहीं करते हैं जो आपके पास नहीं है। जब आप संदर्भ प्रस्तुत करते हैं, तो आप बाद में इन कॉल को शेड्यूल करते हैं। यहां बताया गया है कि कैसे आप केवल साधारण कार्यों के साथ निर्भरता इंजेक्शन का वर्णन कर सकते हैं:

from django.conf import settings
from django.http import HttpRequest, HttpResponse
from words_app.logic import calculate_points

def view(request: HttpRequest) -> HttpResponse:
    user_word: str = request.POST['word']  # just an example
    points = calculate_points(user_words)(settings)  # passing the dependencies and calling
    ...  # later you show the result to user somehow

# Somewhere in your `word_app/logic.py`:

from typing import Callable
from typing_extensions import Protocol

class _Deps(Protocol):  # we rely on abstractions, not direct values or types
    WORD_THRESHOLD: int

def calculate_points(word: str) -> Callable[[_Deps], int]:
    guessed_letters_count = len([letter for letter in word if letter != '.'])
    return _award_points_for_letters(guessed_letters_count)

def _award_points_for_letters(guessed: int) -> Callable[[_Deps], int]:
    def factory(deps: _Deps):
        return 0 if guessed < deps.WORD_THRESHOLD else guessed
    return factory

इस पैटर्न के साथ एकमात्र समस्या यह है कि _award_points_for_lettersरचना करना कठिन होगा।

इसलिए हमने रचना की मदद के लिए एक विशेष आवरण बनाया (यह इसका एक हिस्सा है returns:

import random
from typing_extensions import Protocol
from returns.context import RequiresContext

class _Deps(Protocol):  # we rely on abstractions, not direct values or types
    WORD_THRESHOLD: int

def calculate_points(word: str) -> RequiresContext[_Deps, int]:
    guessed_letters_count = len([letter for letter in word if letter != '.'])
    awarded_points = _award_points_for_letters(guessed_letters_count)
    return awarded_points.map(_maybe_add_extra_holiday_point)  # it has special methods!

def _award_points_for_letters(guessed: int) -> RequiresContext[_Deps, int]:
    def factory(deps: _Deps):
        return 0 if guessed < deps.WORD_THRESHOLD else guessed
    return RequiresContext(factory)  # here, we added `RequiresContext` wrapper

def _maybe_add_extra_holiday_point(awarded_points: int) -> int:
    return awarded_points + 1 if random.choice([True, False]) else awarded_points

उदाहरण के लिए, एक शुद्ध कार्य के साथ खुद को रचना करने के लिए RequiresContextविशेष .mapविधि है। और बस। नतीजतन आपके पास सरल एपीआई के साथ बस सरल कार्य और रचना सहायक हैं। कोई जादू नहीं, कोई अतिरिक्त जटिलता नहीं। और एक बोनस के रूप में सब कुछ ठीक से टाइप और संगत है mypy

इस दृष्टिकोण के बारे में यहाँ और पढ़ें:


0

प्रारंभिक उदाहरण एक "उचित" स्वच्छ / हेक्स के बहुत करीब है। क्या गायब है एक रचना रूट का विचार है, और आप किसी भी इंजेक्टर फ्रेमवर्क के बिना साफ / हेक्स कर सकते हैं। इसके बिना, आप कुछ ऐसा करेंगे:

class Service:
    def __init__(self, db):
        self._db = db

# In your app entry point:
service = Service(PostGresDb(config.host, config.port, config.dbname))

जो शुद्ध / वेनिला / गरीब आदमी के डीआई द्वारा जाता है, इस पर निर्भर करता है कि आप किससे बात करते हैं। एक सार इंटरफ़ेस बिल्कुल आवश्यक नहीं है, क्योंकि आप बतख-टाइपिंग या संरचनात्मक टाइपिंग पर भरोसा कर सकते हैं।

आप डीआई फ्रेमवर्क का उपयोग करना चाहते हैं या नहीं, यह राय और स्वाद का विषय है, लेकिन दंड के समान इंजेक्शन लगाने के अन्य सरल विकल्प हैं जिन्हें आप समझ सकते हैं, यदि आप उस रास्ते से नीचे जाना चाहते हैं।

https://www.cosmicpython.com/ एक अच्छा संसाधन है जो इन मुद्दों को गहराई से देखता है।


0

आप एक अलग डेटाबेस का उपयोग करना चाहते हैं और आप इसे सरल तरीके से करने के लिए लचीलापन चाहते हैं, इस कारण से, मैं निर्भरता इंजेक्शन को आपकी सेवा को कॉन्फ़िगर करने का एक बेहतर तरीका मानता हूं

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.