The original title “How to Decouple Code with Observer Pattern?” “, created on 2019-05-21

background

After the order payment is successful, the payment channel will call us back to inform us that the order has been paid. At this point, we need to do a series of operations:

  1. Modify order payment status;
  2. Send notification to merchant;
  3. Join queue for push order;
  4. other

We found that these are actually a series of operations that occur when the order has been paid. “_ one-to-many dependencies, where all dependent objects are notified and automatically updated when an object’s state changes” _ is a good fit for decoupling these business logic using “event-listener”. Many frameworks implement support for this pattern, but if not, it can be implemented manually to decouple different operations, weaken dependencies, and specifically address procedural noodle code issues. In addition, this may not be a problem if you use the current framework directly. This case can also be used to study the decoupling scheme.

implementation

The class diagram

steps

Create the event base class


      

namespace sdk\events\base;

use sdk\listeners\base\BaseListener;

abstract class BaseEvent
{
    /** * Listeners to subscribe events *@var array
     */
    protected $listens = [];

    public function __construct()
    {
        $this->dispatch();
    }

    /** * distribution event */
    protected function dispatch()
    {
        if (is_array($this->listens) && count($this->listens)) {
            foreach ($this->listens as $item) {
                $listener = new $item(a);if ($listener instanceof BaseListener) {
                    $result = $listener->handle($this); }}}}/** * Manually register a listener to the event *@param $listener
     * @return null
     */
    public function push($listener)
    {
        $result = null;
        if ($listener instanceof BaseListener) {
            $result = $listener->handle($this);
        }

        return $result; }}Copy the code

Create a listener base class


      

namespace sdk\listeners\base;

abstract class BaseListener
{
    abstract public function handle($event);
}
Copy the code

Create the order Paid event class


      

namespace sdk\events;

use sdk\events\base\BaseEvent;
use sdk\listeners\NotifyAgentListener;

class OrderPaidEvent extends BaseEvent
{
   / * * *@varString Paid order number */
    public $order_id;

    / * * *@varArray * Automatically registers listeners, note the order */
    protected $listens = [
        MarkOrderPaidListener::class,
        NotifyAgentListener::class,
    ];

    public function __construct(string $order_id.$promotion_amount)
    {
        $this->order_id = $order_id;
        parent::__construct(); }}Copy the code

Add listener logic

Modify order status


      

namespace sdk\listeners;

use sdk\events\OrderPaidEvent;
use sdk\listeners\base\BaseListener;

class MarkOrderPaidListener extends BaseListener
{
      / * * *@param OrderPaidEvent $event
     * @return bool
     */
    public function handle($event)
    {
        // Modify the database code}}Copy the code

Notify agent


      

namespace sdk\listeners;

use sdk\events\OrderPaidEvent;
use sdk\listeners\base\BaseListener;

class NotifyAgentListener extends BaseListener
{
    / * * *@param OrderPaidEvent $event
     * @return bool
     */
    public function handle($event)
    {
  		/ /... Specific processing logic}}Copy the code

More can be added later

use

The specific usage is as follows


      

use sdk\channel\yeebao\Pay as YeeBaoPay;
use sdk\events\OrderPaidEvent;
use sdk\listeners\PushYeebaoPaidOrderQueue;
use sdk\services\OrderService;
use sdk\tools\Func;

Class route_pay_notify_yb */
class route_callback_yb extends BaseApi
{

    private $pay_type;

    private $yeebaoPay;

    public function __construct()
    {
        $this->yeebaoPay = new YeeBaoPay();
    }

    public function post()
    {
        try {
            $this->handle();
        } catch (Exception $exception) {
            $log = $exception->getMessage() . ': file:' . $exception->getTraceAsString();
            Func::wrtLog('An exception has occurred'.$log);
            die($exception->getMessage()); }}private function handle()
    {
         $success_info = $this->getCallBackData();
        // Here's the key point: instantiating this event automatically triggers the BaseEvent dispatch method
        Listeners registered with the OrderPaidEvent class property $listens are executed sequentially, and the handle method performs the logical operation
 		
        // A single line of logic can trigger several listeners. The listener's processing logic is maintained independently, decoupling the code
        $event = new OrderPaidEvent($success_info['order_id']);
        
        // If there is a logic, you can register a listener manually after judgment
         $event->push(new OrderSomeHandleListener());
        
        / /... The actual business logic judgment is more, omitted here
        // Assembles the returned data upstream
    }
    

    private function getCallBackData() :array
    {
        // Receive data processing
        return $success_info; }}Copy the code

conclusion

Using object-oriented technology, this dependency can be weakened

The follow-up to optimize

The downside of registered listener sequential execution is that it can block subsequent execution if there is a jam or an error. In this case, the listener’s processing logic can be queued and executed asynchronously. The specific practices


      
/ / BaseEvent

/** * $var bool * Add parameter control: default synchronization */
protected $sync = true;

protected $link =  'default';

protected function dispatch()
    {
        if (is_array($this->listens) && count($this->listens)) {
            foreach ($this->listens as $item) {
                $listener = new $item(a);if ($listener instanceof BaseListener) {
                    if($listener->sync === true) {   
                           $listener->handle($this);
                    } else {
                       // Add queue logic, serialize $this and $listener into the queue. Queue drivers and links can be configured
                    }
                }
            }
        }
    }
Copy the code

Message-oriented middleware implementation considerations

inspiration

Laravel was used more before, the whole design can refer to Lavavel- event

extension

  • Design patterns
  • Observer model