gpt4 book ai didi

python - 工厂方法与 Python 中的注入(inject)框架 - 什么更整洁?

转载 作者:行者123 更新时间:2023-12-03 15:47:37 25 4
gpt4 key购买 nike

我通常在我的应用程序中做的是使用工厂方法创建我所有的服务/dao/repo/clients

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

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

当我创建应用程序时
service = Service.from_env()

是什么创建了所有依赖项

在测试中,当我不想使用真正的数据库时,我只做 DI
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

我会建立注入(inject)器框架来做
# in app
inject.clear_and_configure(lambda binder: binder
.bind(DatabaseInterface, PostgresDatabase()))

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

我的问题是:
  • 我的方式真的很糟糕吗?它不再是一个干净的架构了吗?
  • 使用注入(inject)有什么好处?
  • 是否值得打扰和使用注入(inject)框架?
  • 还有其他更好的方法可以将域与外部分开吗?
  • 最佳答案

    依赖注入(inject)技术有几个主要目标,包括(但不限于):

  • 降低系统各部分之间的耦合。这样,您可以轻松地更改每个部分。见 "High cohesion, low coupling"
  • 执行更严格的责任规则。一个实体必须在其抽象级别上只做一件事。必须将其他实体定义为对此实体的依赖项。见 "IoC"
  • 更好的测试体验。显式依赖项允许您使用与生产代码具有相同公共(public) API 的一些原始测试行为来 stub 系统的不同部分。见 "Mocks arent' stubs"

  • 要记住的另一件事是,我们通常将依赖抽象,而不是实现。我看到很多人使用 DI 只注入(inject)特定的实现。有很大的不同。

    因为当你注入(inject)和依赖一个实现时,我们使用什么方法来创建对象没有区别。没关系。例如,如果您注入(inject) requests如果没有适当的抽象,您仍然需要具有相同方法、签名和返回类型的任何类似内容。您根本无法替换此实现。但是,当您注入(inject) fetch_order(order: OrderID) -> Order这意味着任何东西都可以在里面。 requests ,数据库等。

    总结一下:

    What are the benefits of using inject?



    主要好处是您不必手动组装依赖项。然而,这带来了巨大的成本:您正在使用复杂甚至神奇的工具来解决问题。有一天或另一种复杂性会反击你。

    Is it worth to bother and use inject framework?



    关于 inject 的另一件事特别是框架。我不喜欢我注入(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会自动注入(inject)它们。而且我们没有定义任何具体的实现。只有要遵循的协议(protocol)。这种风格被称为“功能对象”或 SRP风格的类。

    然后我们定义 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 样式类的更多信息:
  • Enforcing Single Responsibility Principle in Python

  • Are there any other better ways of separating the domain from the outside?



    您可以使用函数式编程概念而不是命令式编程概念。函数依赖注入(inject)的主要思想是你不调用依赖于你没有的上下文的东西。当上下文存在时,您可以安排这些调用稍后进行。以下是仅使用简单函数来说明依赖注入(inject)的方法:

    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用纯函数组合自身的方法。就是这样。因此,您只有简单的函数和具有简单 API 的组合助手。没有魔法,没有额外的复杂性。作为奖励,所有内容都正确输入并与 mypy 兼容.

    在此处阅读有关此方法的更多信息:
  • Typed functional dependency injection
  • returns docs
  • 关于python - 工厂方法与 Python 中的注入(inject)框架 - 什么更整洁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59908236/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com