(Идеи и правила по структуированию синхронного кода серверной части, которые могут дать профит в перспективе, документ не завершен)
- Одна главная папка в корне (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.