vijoni/organized-modules 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

vijoni/organized-modules

Composer 安装命令:

composer require vijoni/organized-modules

包简介

Organize PHP modules into isolated business units.

README 文档

README

Organize PHP modules into isolated business units.

Imagine Service Oriented Architecture (SOA) in Monolith code base.
This library handles creation and dependency injection of modules nested inside a business service or in a bounded context (term from DDD). This concept could be also known as Majestic Monolith.

Let's take as an example room renting application. In your business you could identify following units and related with them use cases:

  • booking; room reservation, confirmation, cancellation,
  • sales; payment, refund, discount
  • operations; login management, user management, room management
  • marketing; notification, campaign

In either SOA, Microservices, Monolith approach you need to identify your business cases. The difference is that in case of SOA and Microservices you need to take into account the complexity of a distributed system. With this library you can isolate and group business requirements inside single repository.

Structure

Check acceptance tests for details

The image presents Sales unit with Order and Payment modules.

Actions/Controllers

You have to define your Action/Controller classes in a directory inside the Module. This is important, as it is assumed that ModuleFacade, ModuleFactory, ModuleConfig, DependencyProvider exist directly in the Module's directory, one-level above the Action/Controller class. The name of the directory is not important.
Dependency injection logic discovers the classes following this directory structure.
Gain access to $this->moduleFactory().

UnitFacade

Every Unit should contain a UnitFacade class. It should access ONLY its own Unit's ModuleFacades. UnitFacade exposes the methods/functionality you need in other Units.
For example in Booking Unit you may need to access a price calculation for the renting a room.

ModuleFacade

Every Module, should contain a ModuleFacade. It should access ONLY its own Module's ModuleFactory. ModuleFacade exposes methods/functionality you need in other Modules inside its own Unit. For example Order Module may need to access Payment Module's functionality. Gain access to $this->moduleFactory().

ModuleFactory

Here you define object creation methods and handle their dependencies. Inside ModuleFactory you should manually instantiate ONLY classes defined in its own Module. Dependencies coming from outside the Module should be defined in ModuleDependencyProvider. ModuleFactory should not access its own ModuleFacade. Every factory also has access to application configuration object.
Factories can also be runtime variant specific, more on that later. Gain access to $this->dependencyProvider(), $this->config().

DependencyProvider

It exposes external dependencies, like UnitFacades, ModuleFacades and other objects like database connection, api client, logger etc.

ModuleConfig

Here you define methods returning Module specific configuration values or expose application configuration values. Every ModuleConfig has access to global AppConfig object.
ModuleConfigs can also be runtime variant specific, more on that later.

Usage

Initialize Action/Controller

There are different ways to load the dependencies.

Through Interface

Action/Controller class has to implement ModuleActionInterface, use the ModuleActionDependency trait and call DependencyProvider::fillActionDependencies($this) on itself.

class CreateOrderAction implements ModuleActionInterface
{
  use ModuleActionDependency;

  public function __construct()
  {
    DependencyProvider::getInstance()->fillActionDependencies($this);
  }
}

Through Inheritance

Action/Controller class has to extend ModuleAction class.

class CreateOrderAction extends ModuleAction
{
    public function __construct()
    {
        parent::__construct();
    }
}

Custom integration

Action/Controller class has to implement ModuleActionInterface and use the ModuleActionDependency trait.
You can overwrite your framework's controller resolution class or register your own autoloader and use the ModuleActionInterface to identify classes upon which DependencyProvider::fillActionDependencies() should be called.

Expose configuration to ModuleFactories

Call DependencyProvider::setConfig([]) on the application bootstrap.

// app_config.php
<?php

return [
  'database' => [
    'host' => '127.0.0.1',
    'user' => 'dbuser',
  ],
  'stripe' => [
    'api_key' => 'xxx-xxx-xxx',
  ]
];
---------------------------------------

// index.php
$appConfig = AppConfig::fromFile(__DIR__ . '/app_config.php');
$dependencyProvider = DependencyProvider::getInstance();
$dependencyProvider->setConfig($appConfig);

Action/Controller dependencies

Every Action/Controller has access to its Module's ModuleFactory.

class CreateOrderAction extends ModuleAction
{
  public function __invoke(): void
  {
    $orderService = $this->moduleFactory()->newCreateOrderService();
  }
}

ModuleFactory dependencies

Every ModuleFactory has access to its Module's ModuleConfig and DependencyProvider.
Factory methods can create new object instance with every call or cache the instance for future usages. You can use your own naming convention. Here methods are prefixed with new or share. Shared instances will always be the same. Anonymous function is called only once per ModuleFactory instance.

/**
 * @method ModuleDependencyProvider dependencyProvider()
 * @method ModuleConfig config()
 */
class ModuleFactory extends BaseModuleFactory
{
  public function newCreateOrderService(): CreateOrderService
  {
    return new CreateOrderService($this->shareOrderRepository(), $this->shareOrderValidator());
  }
  
  protected function shareOrderRepository(): OrderRepository
  {
    /** @var OrderRepository */
    return $this->share(
      OrderRepository::class,
      fn () => new OrderRepository($this->shareOrderReadGateway(), $this->shareOrderWriteGateway())
    );
  }

  protected function shareOrderValidator(): OrderValidator
  {
    $paymentFacade = $this->dependencyProvider()->sharePaymentFacade();

    /** @var OrderValidator */
    return $this->share(
      OrderValidator::class,
      fn () => new OrderValidator($paymentFacade)
    );
  }
}

Variant specific ModuleFactory

By default, classes named ModuleFactory are looked for, but you may need different logic for every country in which you run your application. For example You serve DE, GB application and for GB you need different behaviour than the default one or than the one for DE.
For this you could create separate Factory classes and overwrite the default method. To make this to work you need to set a variant on the DependencyProvider::setVariant in your application's bootstrap. If variant specific ModuleFactory does not exist, the default one will be used.

// index.php
$dependencyProvider = DependencyProvider::getInstance();
// for example, you could grab the value from environment variable defined
// in your http server domain specific configuration
$dependencyProvider->setVariant(env(APP_STORE)); 
---------------------------------------

// ModuleFactory.php
/**
 * @method ModuleDependencyProvider dependencyProvider()
 * @method ModuleConfig config()
 */
class ModuleFactory extends BaseModuleFactory
{
  public function newCreateOrderService(): CreateOrderService
  {
    return new CreateOrderService();
  }
}
---------------------------------------

// ModuleFactoryGB.php
/**
 * @method ModuleDependencyProvider dependencyProvider()
 * @method ModuleConfig config()
 */
class ModuleFactoryGB extends ModuleFactory
{
  public function newCreateOrderService(): CreateOrderService
  {
    return new CreateOrderServiceAllForFree();
  }
}

ModuleConfig dependencies

Every ModuleConfig has access to AppConfig, set using DependencyProvider::setConfig().

class ModuleConfig extends BaseModuleConfig
{
  public function readStripeApiKey(): string
  {
    return $this->config()->getString('stripe.api_key');
  }
}

Variant specific ModuleConfig

By default, classes named ModuleConfig are looked for, but you may need different values for every country in which you run your application. For example You serve DE, GB application and for GB you need different setting than default one or than the one for DE.
For this you could create separate Config classes and overwrite the default values. To make this to work you need to set a variant on the DependencyProvider::setVariant in your application's bootstrap. If variant specific ModuleConfig does not exist, the default one will be used.
Alternative could also be to load a country specific configuration file in the first place.

// index.php
$dependencyProvider = DependencyProvider::getInstance();
// for example, you could grab the value from environment variable defined
// in your http server domain specific configuration
$dependencyProvider->setVariant(env(APP_STORE)); 
---------------------------------------

class ModuleConfig extends BaseModuleConfig
{
  public function isPurchaseEnabled(): bool
  {
    return true;
  }
}
---------------------------------------

class ModuleConfigGB extends ModuleConfig
{
  public function isPurchaseEnabled(): bool
  {
    return false;
  }
}

ModuleDependencyProvider dependencies

Objects like database connection or api clients are defined outside the Module or maybe in different Units. ModuleDependencyProvider can expose any ModuleFacade, UnitFacade or other objects registered in DepedencyProvider. This creates a wall between Modules, Units and "outside world".

// index.php
$dp = DependencyProvider::getInstance();
$dp->register(
  DatabaseConnection::class
  fn => new DatabaseConnection();
);
---------------------------------------

class ModuleDependencyProvider extends BaseModuleDependencyProvider
{
  public function sharePaymentFacade(): PaymentModuleFacade
  {
    /** @var PaymentModuleFacade */
    return $this->dependencyProvider()->shareModuleFacade(PaymentModuleFacade::class);
  }

  public function shareDatabaseConnection(): DatabaseConnection
  {
    /** @var DatabaseConnection */
    return $this->dependencyProvider()->share(DatabaseConnection::class);
  }
}

vijoni/organized-modules 适用场景与选型建议

vijoni/organized-modules 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 13 次下载、GitHub Stars 达 0, 最近一次更新时间为 2022 年 04 月 02 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 vijoni/organized-modules 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 vijoni/organized-modules 我们能提供哪些服务?
定制开发 / 二次开发

基于 vijoni/organized-modules 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 13
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2022-04-02