Rpc — концепция

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Свою первую апишку я написал лет 7-8 назад и это был первый блин. В целом этот блин прошел кучу испытаний и модернизаций и получилось, что то вполне вменяемое. Даже сейчас я понимаю, что это ядро актуально и его можно развивать дальше и оптимизировать к текущим реалиям (пока нет подходящего проекта).

Как можно догадаться это было Rpc. Наверно стоит начать с того, что я выделил ряд слоев (какие то можно опустить, какие то добавить).

  • получение запроса в текс

  • конвертация текста в ассоциативный массив

  • конвертация ассоциативного массива в класс запроса (понять имя метода, данные метода, токен и другие доп. данные (язык, версия, ...))

  • валидация наличия метода

  • права доступа (не доделал)

  • валидация (и преобразование данных в объект)

  • DI контейнер (не доделал на php)

  • логика метода (возвращает статус ответа и данные)

  • конвертируем статус объекта и данные в структуру ответа

  • преобразовываем структуру ответа в текст

Таким образом у нас получилось 10 слоев которые нам надо реализовать и которые нам кажутся очень сложными (наверно по этой причине Rpc называют сложной и это очень сильно отталкивает). На самом же деле большая часть этих слоев уходит в ядро и вам не нужно их трогать. В моем случае остается зарегистрировать метод, описать схему валидации (иногда дописать метод валидации и преобразования) и написать саму логику. До схемы ответа, обычно руки доходят в последний момент...

Отдельным слоем можно добавить работу с файлами, их валидацию. У меня этот кейс встречал не так часто, по этому идем дальше.

Примеры написания методов.

Простой пример метода на Go

Я не являюсь супер go разработчиком и тут было конвертировано rpc ядро с php на go за неделю. Народ на go очень специфичный и я не думаю поймать бурю аплодисментов, но пример думаю стоит вставить.

package methodGroup2

import (
    "project-my-test/example/rpcApp/methodRequestShemaItem"
    "project-my-test/src/rpc"
    "project-my-test/src/rpc/rpcInterface"
    "project-my-test/src/rpc/rpcStruct"
)

type MethodMyTest struct {
    rpc.RpcMethod
    Data struct {
        Name  *string
        Email *string
    }
    Logger rpcInterface.Logger
}

func (r *MethodMyTest) GetRequestSchema() map[string]rpcStruct.ReformSchema {
    rs := make(map[string]rpcStruct.ReformSchema)
    
    rs["Name"]  = methodRequestShemaItem.GROUP2_NAME()
    rs["Email"] = methodRequestShemaItem.GROUP2_EMAIL()
    return rs
}

func (r *MethodMyTest) Run() rpcInterface.Response {
    
    r.Logger.Info("test Info")
    r.Logger.Warning("test Warning")
    r.Logger.Error("test Error")
    r.Logger.Debug("test Debug")
    
    r.Response.SetData("test::string", "string")
    r.Response.SetData("test::int", 20)
  
    r.Response.SetData("test::Name", r.Data.Name)
    r.Response.SetData("test::Email", r.Data.Email)
  
    r.Response.GetError().SetCode("ERROR")
    return r.Response
}
Пример авторизации на php
<?php

namespace CustomRpc\Method\RpcUser;

class RpcUserAuth extends \Oploshka\Rpc\Method {

  public static function description(){
    return <<<DESCRIPTION
Авторизация пользователя (пользователь должен подтвердить почту!)
Пример объекта auth
{ "login": "test@mail.ru", "password": "12345678" }
При успешном ответе вернется session.
DESCRIPTION;
  }

  public static function validate(){
    return [
      'auth' => ['type' => 'array', 'req' => true, 'validate' => [
        'login'     => ['type' => 'string'  , 'validate' => [], 'req' => true ],
        'password'  => ['type' => 'string', 'validate' => [], 'req' => true ],
      ] ],
    ];
  }

  public function run(){

    $RpcUser = \CustomRpc\EntityQuery\RpcUserQuery::auth($this->Data['auth']['login'], $this->Data['auth']['password']);

    if(!$RpcUser){
      // TODO: add error auth count
      $this->Response->setError('ERROR_LOGIN_PASSWORD'); return;
    }

    $userSessionToken = \CustomRpc\EntityQuery\RpcUserSessionQuery::addNewUserSession($RpcUser);

    $this->Response->setData('session', $userSessionToken);
    $this->Response->setError('ERROR_NO');
  }

  public static function return(){
    return [
      'session'     => ['type' => 'string'  , 'validate' => [], 'req' => true ],
    ];
  }

}
Пример обновления данных пользователя
<?php

namespace CustomRpc\Method\RpcUser;

class RpcUserInfoUpdate extends \Oploshka\Rpc\Method {

  public static function description(){
    return <<<DESCRIPTION
Обновление своих данных
gender = 'NULL' 'MALE' 'FEMALE'
dateOfBirth = date format YYYY-MM-DD

region - отправляй regionId
image - отправляется посредством multipart в отдельном поле (от запроса), в бинарном виде.
DESCRIPTION;
  }

  public static function validate(){
    return [
      'session'       => ['type' => 'rpcUserSession'  , 'validate' => [], 'req' => true ],
      'nickname'      => ['type' => 'string'    , 'validate' => [], 'req' => false ],
      'region'        => ['type' => 'region'    , 'validate' => [], 'req' => false ],
      'gender'        => ['type' => 'string'    , 'validate' => [], 'req' => false ],
      'dateOfBirth'   => ['type' => 'string'    , 'validate' => [], 'req' => false ],
      'aboutUs'       => ['type' => 'string'    , 'validate' => [], 'req' => false ],
    ];
  }

  public function run(){
    $RpcUser = $this->Data['session'];

    $updateField = [];


    if( $this->Data['nickname'] ){
      // проверить зарегистрированность nickname
      if ( \CustomRpc\EntityQuery\RpcUserQuery::checkRegisteredNickname( $this->Data['nickname'] ) ) {
        $this->Response->setError('ERROR_NICKNAME_REGISTERED'); return;
      }
      $updateField['nickname'] = $this->Data['nickname'];
    }

    $this->Data['region']       && $updateField['region_id']      = $this->Data['region']->id;
    $this->Data['gender']       && $updateField['gender']         = $this->Data['gender'];
    $this->Data['dateOfBirth']  && $updateField['date_of_birth']  = $this->Data['dateOfBirth'];
    $this->Data['aboutUs']      && $updateField['about_us']       = $this->Data['aboutUs'];

    $file = \CustomRpc\Entity\RpcUserHelper::loadAndSaveImage('image');
    $file && $updateField['image_id'] = $file->id();

    \CustomRpc\EntityQuery\RpcUserQuery::update($RpcUser, $updateField);
    $this->Response->setError('ERROR_NO');
  }

  public static function return(){
    return [];
  }

}

Весь процесс написания Rpc метода сводиться к тому, что нам нужно написать класс (структуру в случае с go), описать схему валидации (можно описать структуру данных в отдельном классе или добавить свойства в текущий класс) и саму логику.

Были идеи, что rpc ядро должно запустить чистую логику, которая ничего не знает про api, но обычно, в этом не было большого смысла (это увеличивало количество кода, а переиспользования как такового не было)

По итогу мы получаем:

  • гибкость. Можно получать и отдавать запрос в разных форматах (на логику, это не влияет)

  • хорошую отказоустойчивость. Ошибки обрабатываются ядром rpc и вероятность падения сведена к миниму. В любом случае мы можем добавить дополнительную проверку/обработку, не меняя код методов.

  • простоту тестирования. Не нужно дергать сетевой слой. Есть возможность написать общие тесты для всех методов. Сам метод можно протестировать, передав сырые данные или же валидный объект.

  • документация rpc методов. В целом можно написать адаптер для swager'a или генерировать простенький html (я выбрал последнее).

Заключение

Я показывал пример rpc разным людям, кому то такой подход нравиться, кому то нет, кто то пытался сделать подобное. В любом случае есть куда расти и есть идеи для реализации. Примеры не тянут на оскор и демонстрируют как можно писать. Rpc ядро на go можно посмотреть тут, на php тут (develop более свежее).

Источник: https://habr.com/ru/post/594825/


Интересные статьи

Интересные статьи

Хочу поделиться опытом автоматизации экспорта заказов из Aliexpress в несколько CRM. Приведенные примеры написаны на PHP, но библиотеки для работы с Aliexpress есть и для...
Как быстро определить, что на отдельно взятый сайт забили, и им никто не занимается? Если в подвале главной страницы в копирайте стоит не текущий год, а старый, то именно в этом году опека над са...
Если в вашей компании хотя бы два сотрудника, отвечающих за работу со сделками в Битрикс24, рано или поздно возникает вопрос распределения лидов между ними.
Если вы последние лет десять следите за обновлениями «коробочной версии» Битрикса (не 24), то давно уже заметили, что обновляется только модуль магазина и его окружение. Все остальные модули как ...
С версии 12.0 в Bitrix Framework доступно создание резервных копий в автоматическом режиме. Задание параметров автоматического резервного копирования производится в Административной части на странице ...