⛓️

3.2. Process First Design

За свою карьеру я успел разработать и переработать с нуля несколько десятков крупных систем.
 
Бизнес области были сильно непохожи друг на друга: всероссийская электронная библиотека, федеральная система аккредитации химических лабораторий, интернет магазины, включая крупнейший в СНГ B2B магазин товар для взрослых, система по автоматизации выращивания растений, криптоплатежные шлюзы, несколько чатов, телеметрия и автоматизация городских парковок, и так далее.
 
Каждый раз, когда я сталкивался с новой системой вставал главной вопрос: «А с чего начать проектирование?»
 
И я нашел ответ, который работает в 100 из 100 случаев:
 
Начинать проектирование системы надо с Процессов

Как это работает

Если абстрактно:
 
  1. Абсолютно любой код решает задачу, поэтому для начала мы должны выяснить «к какому результату мы хотим прийти», то есть описываем Задачи
  1. Далее описываем Процессы – последовательность действий, которые мы должны произвести, чтобы решить Задачу.
  1. Далее мы проходимся по описанию всех Процессов и можем вычленить Структур Данных, разделив их таким образом, который будет хорошо отвечать Процессам.
  1. Следующим этапом, мы выделяем шаги, которые повторяются в Процессах и выносим их в Операции.
 
Рассмотрим на на примере разработки интернет магазина:

i. Задачи

 
  1. Клиент должен иметь возможность:
    1. Искать товары
    2. Добавлять их в корзину
    3. Оформлять заказ
    4. Смотреть историю заказов
    5. Изменять данные еще несобранного заказа
    6. Получать нотификации об изменении статуса заказа
    7. Регистрироваться и Аутентифицироваться
  1. Админ должен иметь возможность:
    1. Добавлять товары
    2. Видеть заказы клиентов
    3. Изменять данные заказа
    4. Изменять статус заказа
 

ii. Процессы

 
  1. Клиент
    1. Оформлять заказ
      1. Просматривать список товаров в моей корзине
        1. Входные данные (Input или просто I): идентификатор пользователя
        2. Выходные (Output или O): Корзина
      2. Видеть сумму, скидки, акции, условия доставки
        1. I: идентификатор Корзины, идентификатор Условия доставки
        2. O: оригинальная сумма, размер скидки, стоимость доставки, итоговая сумма
      3. Оплачивать заказ
        1. I: идентификатор Корзины
        2. O: ссылка на систему оплаты
      4. Видеть оформленный заказ
        1. I: id Заказа
        2. O: данные по Заказу
  1. Админ
    1. Добавлять товар
      1. Добавлять описание товара
        1. I: описание
        2. O: OK
      2. Стоимость
        1. I: число
        2. O: OK
      3. Особенности доставки
        1. I: набор идентификаторов условий доставки
        2. O: OK
      4. Выставлять категории и тэги
        1. I: набор идентификаторов категорий и тэгов
        2. O: OK
      5. Добавлять его в акции
        1. I: набор идентификаторов акций
        2. O: OK
      6. Описывать к какой политике скидок он относится
        1. I: набор идентификаторов политики скидок
        2. O: OK

iii. Структуры Данных

 
Теперь сформируем условные Структуры Данных, которые нужны для этих процессов:
  1. Пользователь – Пользователь или Админ
    1. id
  1. Продукт
    1. id
    2. name
    3. price
  1. Корзина
    1. id
    2. productIds
    3. userId
  1. Заказ
    1. id
    2. productIds
    3. userId
    4. status
    5. addressId
    6. deliveryTypeId
  1. Чек
    1. id
    2. status
    3. orderId
  1. Адрес пользователя
    1. id
    2. city
    3. street
    4. userId
  1. Условия доставки
    1. id
    2. name
    3. prices
  1. Тип оплаты
    1. id
    2. name
  1. Акция
    1. id
    2. name
    3. productIds
    4. priceCoeficient
  1. Политика скидок
    1. id
    2. name
    3. productIds
    4. userIds
    5. priceCoeficient
 
Хочу отметить, что эти структуры сейчас отвечают только тем двум процессам, который я расписал в предыдущем пункте. Чем больше было бы процессов, тем больше структур данных я бы расписал.
 
+ поскольку у нас не ООП, мне не надо делать их по какому-то принципу объединения поведения и данных, нет, я их описываю так, как мне удобно будет их хранить в БД. То есть, если Заказ было бы удобно хранить кусками в 3-х разных БД, то у меня было бы 3 разных структуры данных, отписывающих как это храниться в этих БД.
 

iv. Операции

 
Последний этап, это найти процедуры, которые повторяются между процессами и вычленить их в Операции:
  1. Изменение данных заказа – логика, которая доступна как Админу, так и Пользователю, единственное отличие в которой заключается в том, что админу доступно это изменение в любом статусе Заказа, а Пользователю только статусе «Оформлен».
Это очень утрированный пример, но самое главное, что я хочу показать – как круто и просто такое проектирование накладывается на код:
 
// Главное отличие от этапа проектирования, это то, что мы сначала // описываем Структуры Данных, потом Процессы, потом Операции // # Структуры Данных // ./data/main-db-data-structures.ts type User = { id: string; role: "client" | "admin" } type Product = { id: string; name: string; price: number; } type Cart = { id: string; productIds: string[]; userId: string; } type Order = { id: string; productIds: string[] userId: string; status: string; addressId: string; deliveryTypeId: string; } type Check = { id: string; status: string; orderId: string; } type UserAddress = { id: string; city: string; street: string; userId: string; } type DeliveryType = { id: string; name: string; prices: number[] } type PaymentType = { id: string; name: string; } type Stock = { id: string; name: string; productIds: string[] priceCoeficient: number; } type DiscountPolicy = { id: string; name: string; productIds: string[] userIds: string[] priceCoeficient: number; } // # Процессы, с описание Входных (Input) и Вызодных (Output) Структур Данных // ./process/get-my-cart.ts const getMyCart = (userId: string): Cart = { return db.cart.findOne({ userId: userID }) } // ./process/calculate-cart-sum.ts // а вот этот тип относится только к Процессу ниже, поэтому будет описан // рядом с ним type CalculateCartSumResult = { sumPrice: number deliveryPrice: number totalPrice: number } const calculateCartSum = (cartId: string, deliveryTypeId: string): CalculateCartSumResult => { const carts = db.cart.findOne({ userId: userID }) // Достаем Продукты const products = db.products.find({ id: carts.productIds }) // Достаем относящиеся к ним акции и скидки const stocks = db.stock.find({ productIds: products.map(p => p.id) }) const discountPolicies = db.discountPolicy.find({ productIds: products.map(p => p.id) }) // Достаем тип доставки const deliveryType = db.deliveryType.find({id: deliveryTypeId}) // Ну и здесь какие-то вычисления стоимостей return { sumPrice: ..., deliveryPrice: ..., totalPrice: ..., } } // ./process/confirm-order-ts const confirmOrder = ( cartId: string, deliveryTypeId: string, paymentTypeId: string ) => { // ... } // и так по всем Процессам // ... // # Теперь опишем Операции, которые могут переиспользоваться Процессами // ./opertaions/change-order-info.ts const changeOrderInfo = (order: Order, byWhom: user, newOrerData: Partial<Order>): void => { // Запрещаем изменять информацию о заказе если оне не в статусе "оформлен" и при это // изменять хочет Клиент if (order.status !== "formed" && user.role === "client") return; // Если же все ок, тогда делаем все нужные трансформации // ... }
 
Заметьте:
 
  1. Я не объединяю Процессы в какие «Сервисы» и подобное.
    1. Максимум, во что я бы мог их объединить, это в какой-то модуль или микросервис по домену (например, в одном месте, все что связано с Корзиной, Продуктами и так далее, в другом, что связано с Акциями, в третьем с Оплатами), что влияло бы на то, в каких папках находились бы функции этих Процессов.
  1. Каждый Процесс и Операция могут использовать абсолютно любые Данные
 
Тут есть еще много правил проектирования и построения приложений, об этом мы будем говорить в других главах и даже книгах.
 
Главное, что я хочу донести: проектируйте начиная с Процессов, и пишите свой код начиная с Функций.
 
Именно таким образом вы лучше всего попадёте в требования системы + сделаете ее достаточно гибкой для дальнейшего развития.
 
Это то, что активно используется как в ФП, так и в ПП, так, собственно, и в ФОП.

Что дальше

Ок, начинать будем с процессов, а как правильно подходить к работе с Данными?
 
Именно этому посвящена следующая глава: