Hexagonal Architecture

Hexagonal Architecture

refer to the link https://fideloper.com/hexagonal-architecture

Hexagonal Architecture
  1. Hexagonal Architecture 定义代码概念层,并且指明了层次间解耦的方法。它也澄清了在什么时候、怎么样、为什么要使用接口;
  2. Hexagonal Architecture 分离了外部框架和内部逻辑实现;让应用表达出自己,而不是被应用框架所限制;
  3. Hexagonal 边的数目是任意的;每一条边定义了In Port和Out Port;
  4. 提高维护性和降低技术债;
  • 修改应用的一小部分影响很小
  • 添加features不需要大的代码变化
  • 添加与应用新的交互方式只需要非常小的改动
  • Debugging 非常方便
  • 测试相对容易
Interfaces 和 Implementations
  1. 略过
Ports and Adapters

Before returning to the concept of Ports and Adapters, let’s go over the layers of the Hexagonal Architecture.

  1. 不同的Ports可以适配不同的Layer
Layers

Layers communicate with each other using interfaces (ports) and implementations (adapters).

  • Each layer has two elements: The code and boundary.

  • The boundary between itself and an outside layer.

  • These Ports 就是上述layers 定义的接口;

  • These interfaces 定义了外部的layers如何访问当前的layer;

  • 例子

  1. Domain Layer
    Include Business logic, 该层可以根据需求大小分割的胖或者瘦;
  • 定义 use cases
  • 可以通过Application Layer负责Domain Layer编排,用以完成一个task

Domain logic sample:

class Ticket extends Model {

public function assignStaffer(Staffer &staffer)
{
    if (! &staffer->categories->contains($this->category))
    {
       throw new DomainException("Staffer can't be assigned to ".$this->category);
    }

    $this->staffer()->associate($staffer);

    return $this;
}

public function setCategory(Category &category)
{
    if ($this->staffer instanceof Staffer && !$this->staffer->categories->contains( $category)) {
        $this-staffer = null;
    }

    $this->category()->associate($category);

    return $this;
}

}

//aka a Use Case
This interface is the port, the implementations of it are the adapters to that port;
我们可以从Application Layer 与 Domain Layer 解耦合;Command Buses 有一个定义去执行命令;

interface CommandBus {

public function execute($command);

}

class RegisterUserCommand {

protected $email;
protected $password;

public function __construct($email, $password)
{

}

public function getEmail() {}

public function getPassword() {}

}

//aka a Use Case
class UserCreateEvent {
public function __construct(User $user) {}
}

  1. Application Layer
    作用于Domain Layer和第三方Framework之间的应用层,该层编排了如何使用Domain entities;
  • 触发了Domain Events
  • 处理user cases
  • 我们不需要修改interfaces,对于Framework Layer而言,可以修改Adapters

We saw this in the notifier and event dispatcher examples above.

The Application Layer will implement interfaces (make adapters of the ports) defined in the Domain Layer. It will also contain code for other concerns it may have.

  1. Framework Layer
  • Sample

class SesEmailNotifier implements Notifier {
public function __construct(SesClient $client) { … }
public function notify(Message $message)
{
$this->client->sendEmail([ … ]); // Send email with SES particulars
}
}

class SesEmailController implements BaseAdapter {
public function sendEmail(Message $message) {
applicationUserCase.sendEmail(message)
}
}

  1. Boundaries

定义了layer之间的沟通,每一个layer负责定义后一个layer如何通信;
Boundaries里面有许多的interfaces,它们为后一个layer提供了Ports和使用Adapters去调用它们;

  1. Use-Cases/Commands

Earlier in this writing, I’ve made mention of “Use Cases” and “Commands.” Let’s go deeper into what these are.
There’s also a concept of the Application Boundary, a macro-level concept.
“Use Cases” (also called “Commands”)

定义use cases有一些影响,例如,我们可以清楚而明确的看到我们的应用程序想要如何与之交互。这可以严格遵循应用程序需要执行的业务逻辑。用例对于开发团队的清晰性也很有用。我们可以提前计划用例,或者根据需要添加用例,但我们发现很难在用例之外创建奇怪的逻辑;

如何定义use cases?
创建objects代表应用的usecase.我们可以称之为"command", 用application "Command Bus"的handler去编排use case的执行;
因此:我们有3个actors: A Command The Command Bus A Handler

Sample:
class SimpleCommandBus implements CommandBus {

// Other methods removed for brevity

public function execute($command)
{
    return $this->resolveHandler($command)->handle($command);
}

}
// 我们可以reuse这些业务逻辑,不管是哪种应用上下文(HTTP,CLI,API,AMQP or 消息队列)
public function handleSomeRequest()
{
try {
$registerUserCommand = new RegisterUserCommand(
$this->request->username, $this->request->email, $this->request->password );

    $result = $this->commandBus->execute($registerUserCommand);

    return Redirect::to('/account')->with([ 'message' => 'success' ]);
} catch( \Exception $e )
{
    return Redirect::to('/user/add')->with( [ 'message' => $e->getMessage() ] );
}

}

This is good, as we want to decouple from our framework layer, giving us the benefit of protecting our application from changes in the framework as much as possible (another form if maintainability), and allowing us to run the same code in other contexts (CLI, API calls, etc).

  1. Example Command, Command Bus and Handler
    Handler 存在与Application Layer, 用以编排Domain entities来满足Command

Sample
interface Handler {
public function handle($command);
}

class RegisterUserHandler {

public function handle($command)
{
    $user = new User;
    $user->username = $command->username;
    $user->email = $command->email;
    $user->password = $this->auth->hash($command->password);

    $user->save();

    $this->dispatcher->dispatch( $user->flushEvents() );

    // Consider also returning a DTO rather than a class with behavior
    // So our "view" layers (in whatever context) can't accidentally affect
    // our application - it can just read the results
    return $user->toArray(); 
}

}

class ValidationCommandBus implements CommandBus {

public function __construct(CommandBus $bus, Container $container, CommandInflector $inflector) { ... }

public function execute($command)
{
    $this->validate($command);
    return $this->bus->execute($command);
}

public function validate($command)
{
    $validator = $this->container->make($this->inflector->getValidatorClass($command));
    $validator->validate($command); // Throws exception if invalid
}

}

Dependencies

Domain Layer <- Application Layer <- Framework Layer

  • Going Out (Ioc)
    内部的layers依赖于外部的layers,使用IOC
Gitee Feature
  1. You can use Readme_XXX.md to support different languages, such as Readme_en.md, Readme_zh.md
  2. Gitee blog blog.gitee.com
  3. Upcoming winter framework
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值