পাইথনের ফ্যাক্টরি পদ্ধতি বনাম ইনজেক্ট ফ্রেমওয়ার্ক - ক্লিনারটি কী?


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())

আমি মনে করি এটি ক্লিন / হেক্স আর্কিটেকচার থেকে বেশ দূরে, যেহেতু পরিষেবা কীভাবে একটি ডেটাবেস তৈরি করতে হয় এবং কোন ডাটাবেস টাইপ তৈরি করে তা জানে (এটি ইনম্মিরিডাটাবেস বা মঙ্গো ডেটাবেসও হতে পারে)

আমি অনুমান করি যে পরিষ্কার / হেক্স আর্কিটেকচারে আমার কাছে থাকবে

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

নির্ভরতা ইনজেকশন কৌশলটিতে বেশ কয়েকটি প্রধান লক্ষ্য রয়েছে, যা (তবে সীমাবদ্ধ নয়) সহ:

  • আপনার সিস্টেমের অংশগুলির মধ্যে সংযুক্তি হ্রাস করা। এইভাবে আপনি কম চেষ্টা করে প্রতিটি অংশ পরিবর্তন করতে পারেন। দেখুন "হাই সংযোগ, কম কাপলিং"
  • দায়িত্ব সম্পর্কে কঠোর নিয়ম প্রয়োগ করা। একটি সত্তা অবশ্যই তার বিমূর্ততার স্তরে কেবল একটি কাজ করতে হবে। অন্যান্য সত্তাগুলি অবশ্যই এটির নির্ভরতা হিসাবে সংজ্ঞায়িত করা উচিত। দেখুন "আইওসি"
  • ভাল পরীক্ষার অভিজ্ঞতা। সুস্পষ্ট নির্ভরতা আপনাকে এমন কিছু আদিম পরীক্ষার আচরণের মাধ্যমে আপনার সিস্টেমের বিভিন্ন অংশকে স্টাব করতে দেয় যা আপনার প্রোডাকশন কোডের তুলনায় একই পাবলিক এপিআই রয়েছে। দেখুন "ঠাট্টা arent 'নিবন্ধসমূহ"

মনে রাখা অন্য জিনিসটি হ'ল আমরা সাধারণত বিমূর্তির উপর নির্ভর করব, বাস্তবায়ন নয়। আমি প্রচুর লোককে দেখি যারা কেবল নির্দিষ্ট প্রয়োগের জন্য ইঞ্জিন করতে ডিআই ব্যবহার করে। একটি বড় পার্থক্য আছে।

কারণ আপনি যখন ইনজেক্ট করেন এবং কোনও বাস্তবায়নের উপর নির্ভর করেন তখন আমরা কোন পদ্ধতিতে অবজেক্ট তৈরি করতে ব্যবহার করি তার মধ্যে কোনও পার্থক্য নেই। এটা ঠিক কোন ব্যাপার না। উদাহরণস্বরূপ, আপনি যদি requestsযথাযথ বিমূর্ততা ছাড়াই ইনজেকশন করেন তবে আপনার এখনও একই পদ্ধতি, স্বাক্ষর এবং রিটার্নের ধরণের সাথে অনুরূপ কিছু প্রয়োজন হবে। আপনি এই বাস্তবায়নটি মোটেও প্রতিস্থাপন করতে পারবেন না। তবে, আপনি যখন ইনজেকশন দিচ্ছেন fetch_order(order: OrderID) -> Orderতার অর্থ হল যে কোনও কিছু ভিতরে থাকতে পারে। requests, ডাটাবেস, যাই হোক না কেন।

বিষয়গুলি সংক্ষিপ্ত করতে:

ইনজেকশন ব্যবহারের সুবিধা কী?

প্রধান সুবিধাটি হ'ল আপনাকে নিজের নির্ভরতাগুলি ম্যানুয়ালি একত্রিত করতে হবে না। যাইহোক, এটি একটি বিশাল ব্যয়ের সাথে আসে: আপনি সমস্যাগুলি সমাধানের জন্য জটিল, এমনকি যাদুকর, সরঞ্জামগুলি ব্যবহার করছেন। একদিন বা অন্য কোনও জটিলতা আপনাকে লড়াই করবে fight

ইনজেকশন ফ্রেমওয়ার্কটি বিরক্ত করা এবং ব্যবহার করা কি উপযুক্ত?

injectবিশেষ করে ফ্রেমওয়ার্ক সম্পর্কে আরও একটি জিনিস । আমি যখন পছন্দ করি না এমন বস্তুগুলি যখন সম্পর্কে জানে তখন আমি পছন্দ করি না। এটি একটি বাস্তবায়ন বিস্তারিত!

একটি বিশ্বের Postcardডোমেন মডেলটিতে, উদাহরণস্বরূপ, এই জিনিসটি কীভাবে জানে?

আমি punqসাধারণ ক্ষেত্রে এবং জটিলগুলির জন্য ব্যবহার করার পরামর্শ দেব dependencies

injectএছাড়াও "নির্ভরতা" এবং অবজেক্ট বৈশিষ্ট্যের পরিষ্কার বিচ্ছিন্নতা প্রয়োগ করে না। যেমনটি বলা হয়েছিল, ডিআই-এর অন্যতম প্রধান লক্ষ্য হ'ল কঠোর দায়িত্ব পালন করা।

বিপরীতে, আমাকে দেখায় যে কীভাবে 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সেগুলি স্বয়ংক্রিয়ভাবে ইনজেক্ট করব। এবং আমরা কোনও নির্দিষ্ট বাস্তবায়ন সংজ্ঞায়িত করি না। কেবল অনুসরণ করতে প্রোটোকল। এই স্টাইলটিকে "ফাংশনাল অবজেক্টস" বা এসআরপি- স্টাইল্ড ক্লাস বলা হয়।

তারপরে আমরা ধারকটি 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())

দেখা? কে এবং কীভাবে এগুলি তৈরি করে তা এখন আমাদের ক্লাসগুলির কোনও ধারণা নেই। কোনও সাজসজ্জার নেই, কোনও বিশেষ মূল্য নেই।

এখানে এসআরপি-স্টাইলযুক্ত ক্লাস সম্পর্কে আরও পড়ুন:

বাইরে থেকে ডোমেনকে আলাদা করার মতো আরও ভাল কোনও উপায় আছে কি?

আপনি আবশ্যকীয়গুলির পরিবর্তে ক্রিয়ামূলক প্রোগ্রামিং ধারণাগুলি ব্যবহার করতে পারেন। ফাংশন নির্ভরতা ইনজেকশনটির মূল ধারণাটি হ'ল আপনি এমন জিনিসগুলিকে কল করবেন না যা আপনার প্রসঙ্গের উপর নির্ভর করে। প্রসঙ্গটি উপস্থিত থাকাকালীন আপনি এই কলগুলির জন্য পরবর্তী সময় নির্ধারণ করুন। আপনি কেবল সাধারণ ফাংশনগুলির সাথে নির্ভরতা ইনজেকশনটি কীভাবে চিত্রিত করতে পারেন তা এখানে:

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পদ্ধতি রয়েছে । এবং এটাই. ফলস্বরূপ আপনার কাছে সহজ এপিআই সহ কেবল সাধারণ ফাংশন এবং রচনা সহায়ক have কোন জাদু নেই, কোনও অতিরিক্ত জটিলতা নেই। এবং বোনাস হিসাবে সবকিছু যথাযথভাবে টাইপ করা হয় এবং এর সাথে সামঞ্জস্যপূর্ণ 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))

আপনি কার সাথে কথা বলছেন তার উপর নির্ভর করে যা খাঁটি / ভ্যানিলা / দরিদ্র মানুষের ডিআই দ্বারা যায় by একটি বিমূর্ত ইন্টারফেস একেবারে প্রয়োজনীয় নয়, যেহেতু আপনি হাঁস-টাইপিং বা কাঠামোগত টাইপিংয়ের উপর নির্ভর করতে পারেন।

আপনি ডিআই কাঠামোটি ব্যবহার করতে চান বা না চান এটি মতামত এবং স্বাদের বিষয়, তবে পাঙ্কের মতো ইনজেকশনের আরও সহজ বিকল্প রয়েছে যা আপনি বিবেচনা করতে পারেন, যদি আপনি সেই পথে যেতে চান তবে choose

https://www.cosmicpython.com/ একটি ভাল সংস্থান যা এই সমস্যাগুলিকে গভীরতার সাথে দেখায়।


0

আপনি অন্য একটি ডাটাবেস ব্যবহার করতে চাইতে পারেন এবং এটি সহজ উপায়ে করার জন্য আপনার নমনীয়তা থাকতে চান, এই কারণে, আমি নির্ভরশীলতা ইঞ্জেকশনটিকে আপনার পরিষেবাটি কনফিগার করার আরও ভাল উপায় হিসাবে বিবেচনা করি

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.