Skip to content

Latest commit

 

History

History
59 lines (47 loc) · 8.75 KB

README.md

File metadata and controls

59 lines (47 loc) · 8.75 KB

server-structural-pattern

(Идеи и правила по структуированию синхронного кода серверной части, которые могут дать профит в перспективе, документ не завершен)

Правила

  • Одна главная папка в корне (api) под весь код, в которой размещены модули с функционалом/апи (имя папки может быть любым, api, eps, plug, plugin...)
  • Разделять все методы на публичные и приватные (для внутреннего использования), приватные методы не должны быть видимы для других модулей. Публичные методы должн быть доступны по прямому пути ( api.http.get("") )
  • Файл-скрипт / точка входа (run.py) в приложление которая подключает api и выполняет любой публичный метод, например ./run.py api.http.get github
  • Во всем проекте вызов публичных методов делать по полному пути ( api.http.get() )
  • При вызове публичных методов на вход подавать простые (builtin) типы/данные (числа, строки, простые словари и т.п.), например api.event.send(user_id, data), передавать идентификатор user_id вместо целого объекта user. Так же допустимы типы которые достпны и используются по всему проекту, например datetime или ObjectId из MongoDB.
  • Входные агрументы не должны быть изменены при вызове метода (если не предусмотрено иное), если в методе есть код изменения входного объекта, то объект должен быть клонирован самим методом.
  • Допускается возращать сложные/не стандартные типы (объекты orm, апи и т.п.), но простые типы предпочтительнее, пример:

вместо:

  file_description = api.file.save(data)
  file_id = file_description.file_id

предпочтительный вариант:

  file_id = api.file.save(data)
  file_description = api.file.describe(file_id)
  • Методы для получения данных, где отсутствие данных - исключительная ситуация, должны выбрасывать исключение + возможность задать значение по умолчанию при отсутствии данных, прим. api.user.get(user_id, default=NoValue)

  • Использовать свои модули вместо стандартных (проксировать), нарпимер api.time.now() вместо: import datetime; datetime.datetime.utcnow(), это позволит не мокать метод когда это будет необходимо, например возможность задать определенное время/сдвиг для всего проекта. Не дает запутаться какой конкретно метод используется для проекта (now/utcnow/today/...)

  • Публичный метод должен гарантировать выполнение своей работы, выполнить свою функцию либо вызвать исключение (не должно быть неопределенности), если не предусморенно иное.

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

  • Можно сделать события на запуск и завершение приложения в run.py: api.pubsub.publish('system.start') -> api.pubsub.subscribe('system.start')

  • Можно сделать специализацию для модулей, например если в модуле есть подмодуль ajax, то все* его методы доступны через ajax/rest, это позволяет быстро "лепить" апи для клиента.

Идеи для обдумывания:

  • Использовать уникальные идентификаторы по всему проекту (по всем типам) на которые могут быть ссылки, например постредством использования префикса (например 10000000001, 10000000002, 10000000003, цифра '1' в начале - префикс для типа), это позволит ссылаться на объеты любых типов одним идентификатором (числом), - такую ссылку удобно хранить, передавать (как в утинной типизации). Уникальный идентификатор может быть более компактным, например 32bit -> 10bit под тип и 22bit под идентификаторы, типы с большими коллекциями могут использовать несколько диапазонов.

Плюсы

  • Проще разработка за счет разделения на отдельные модули (встроенные микро-сервисы)
  • При необходимости (почти) любой модуль можно будет вынести в микросервис просто подменив пулбичные методы. Таким образом мы экономим время за счет создания монолита но с перспективой на микросервисы когда это будет действительно необходимо.
  • Проще рефакторинг, 1) т.к. публичные методы используются по полным путям - легко найти все вызовы, 2) внутри модуля можно безболезненно делать рефакторинг (не трогая определение публичных методов) без (последствий) влияния на проект.
  • Любой (свеженаписаный) метод можно тут же вызвать через ./run.py api...., не нужно дополнительных скриптов на запуск отдельных кусков проекта
  • Использование простых типов для публичных методов повзоляют упростить их вызов, более простое использование копонентов друг друга

Пример использования:

Вместо:

import requests
result = requests.get('https://api.github.com/events').content

Использовать:

result = api.http.get('https://api.github.com/events')

Сам api.http.get может использовать requests или что угодно, либо содержимое метода может быть подмененно с ростом проекта.

Далее если потребуется статистика по закачкам, можно лего добавить счетчик в api.http.get, либо сделать что-бы вызывалось событие api.pubsub.publish('http.get', args) и подписаться на него в нужных местах, наример в модуле статистики. В случае с requests пришлось бы мокать библиотеку - что плохо, либо править код во всех местах где вызывается requests, что тоже плохо, либо сделать обертку на вызов requests.get и её везде использовать, а это аналог текущего подхода.

Далее если потребуется использовать, например, proxy-pool, то его, так же, можно добавить в api.http.get без изменения всего проекта, в результате весь проект начнет работать через proxy-pool.