[Skr-Shop] Архитектурный дизайн корзины для покупок

Архитектура Дизайн, управляемый доменом

Пришло время погасить, я надеюсь, что все в безопасности в эпидемии, и компания все еще там!


SKR Shop - это группа низкоуровневых кодеров. Из-за безумия подвергается пыткам проектом на работе, а из-за гордости программиста: системы, разработанные другими, все дерьмо, и мой дизайн самый удивительный Вселенная, поэтому я решил сделать это. Сделайте руководство по проектированию электронной коммерции, которые только дизайны без кодирования.

Адрес проекта: https://github.com/skr-shop/manuals

в предыдущей статьеАнализ спроса на дизайн корзины покупокОписываются общие требования к тележкам для покупок. Основное внимание в этой статье уделяется тому, как реализовать архитектурный проект (бизнес + системная архитектура).

инструкция

Архитектурное проектирование можно разделить на три уровня:

  • бизнес-архитектура
  • структура системы
  • Технологическая архитектура

Быстро и просто объясним смысл следующих трех архитектур; когда мы получаем требования к корзине, мы говорим, что используем Golang для ее реализации и используем Redis для хранения; это описывает техническую архитектуру; мы наслаиваем код корзины проект, проектные спецификации и планирование зависимых систем называются системной архитектурой;

Что такое бизнес-архитектура? Бизнес-архитектура — это буквальное языковое описание архитектуры системы, что это значит? Когда мы получаем запрос, мы должны сначала связаться с заявителем, чтобы установить единое познание. Например: нормативные существительные (товары в корзине и товары в товарной системе имеют разное значение); построить модель, понятную каждому, взаимодействие между корзиной, пользователем, товаром, заказом и этими сущностями, и то, что каждый имеет особенности.

Существует много методологий анализа бизнес-архитектуры, таких как проектирование на основе предметной области, но это не единственный и не лучший метод анализа бизнес-архитектуры. Что подходит вам лучше всего. Наши часто используемые диаграммы отношений сущностей и диаграммы UML также относятся к области бизнес-архитектуры;

Что здесь должно быть сильным, так это то, что независимо от того, какой метод вы используете для моделирования дизайна, лучше иметь дизайн, чем его отсутствие Во-вторых, содержание моделирования должно быть отражено в вашем коде.

Анализ бизнес-архитектуры в этой статье опирается наDDD(Domain Driven Design) мышление; все еще это предложение适合的就是最好的.

бизнес-архитектура

Благодаря предыдущему анализу спроса мы выяснили, что будет делать наша корзина. Давайте взглянем на типичный процесс работы с корзиной покупок.

用户旅程
путешествие пользователя

В этом процессе пользователь использует корзину для покупок в качестве носителя для завершения процесса покупки продукта; постоянно поступающие данные — это продукт, а корзина для покупок — стабильный носитель. Это точка стабильности и изменения в нашей системе.

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

Жизненный цикл товара в корзине выглядит следующим образом:

过程
процесс

Следуя этому процессу, давайте рассмотрим операции, соответствующие каждому этапу.

过程对应的操作
Операция, соответствующая процессу

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

Вышеупомянутые три этапа, согласно концепции в DDD, должны называться сущностями, и они составляют домен корзины в целом, сегодня мы пропустим эти понятия, не говоря о них, и у нас будет возможность разместить их отдельно. позже.

перед добавлением

Посредством анализа процесса мы обобщили операционные интерфейсы, которые должны быть в системе, и сущности, соответствующие этим интерфейсам.Теперь давайте посмотрим, что нужно сделать перед добавлением автомобиля;

Перед добавлением корзины в основном проверяются различные широты добавляемых продуктов корзины, чтобы проверить, соответствуют ли они требованиям.

Прежде чем позволить пользователям добавлять автомобили, мы сначала решаем, откуда пользователи продают, а затем проверяем их? Потому что бывают разные ситуации, когда один и тот же продукт приобретается по разным каналам, например: мобильный телефон Xiaomi, независимо от того, покупаем ли мы его через seckill, через краудфандинг друзей или покупаем напрямую в торговом центре, цена отличается, но в на самом деле это один и тот же продукт;

Второй вопрос, имеете ли вы право на покупку, или как было сказано выше, добавление автомобилей в спайк и краудфандинговые операции, не все могут их добавить, они должны иметь действующую квалификацию. Затем сюда же ставится квалификационная проверка;

Третий вопрос заключается в проверке атрибутов приобретенного продукта, например, есть ли он на полке или нет, в наличии, ограниченное количество покупки и т. д.

И вы обнаружите, что условия проверки здесь могут быть очень изменчивыми. Как создать код, который легко расширять?

加车的验证
Проверка надстройки

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

Способ 1: Это делается через режим стратегии + режим фасада. Стратегия состоит в том, чтобы выполнять различные проверки в соответствии с различными источниками автомобилей, а фасад состоит в том, чтобы инкапсулировать каждую стратегию в соответствии с различными источниками;

Метод 2: через модель цепочки ответственности, но здесь необходимо внести изменения.В процессе выполнения этой цепочки вы можете пропустить определенные узлы, например: Seckill не требует инвентаризации или проверки краудфандинга;

В результате всестороннего анализа я выбрал модель цепочки ответственности. Вставьте основной код

// 每个验证逻辑要实现的接口
type Handler interface {
	Skipped(in interface{}) bool // 这里判断是否跳过
	HandleRequest(in interface{}) error // 这里进行各种验证
}

// 责任链的节点 type RequestChain struct { Handler Next *RequestChain }

// 设置handler func (h *RequestChain) SetNextHandler(in *RequestChain) *RequestChain { h.Next = in return in }

Шаблоны проектирования можно найти на github моего друга: https://github.com/TIGERB/easy-tips/tree/master/go/src/patterns.

корзина

Прежде чем мы закончим добавлять корзины, давайте посмотрим на часть корзины покупок. Ранее мы обсуждали, что корзины для покупок могут иметь различные формы, например: хранить несколько товаров для совместного расчета и немедленный расчет определенного товара. Поэтому корзину необходимо выбирать в соответствии с типом канала.

Эта часть операции относительно стабильна. Давайте выберем еще несколько важных операций, чтобы поговорить об этой идее.

добавить в корзину

Предваряя условную проверку, вы обнаружите, что эта часть логики стала очень легкой при добавлении автомобиля. Главное сделать логику следующих частей.

加入购物车
добавить в корзину

Здесь есть несколько хитростей.Первый это логика получения продукта.Так как он также используется в предыдущей проверке, то он будет продолжать передаваться по параметрам после предыдущего приобретения, поэтому нет необходимости читать библиотеку или позвонить в сервис здесь для получения;

Во-вторых, необходимо получить данные существующей корзины текущего пользователя, а затем добавить добавленный продукт. Это аналогичная операция слияния.Оказывается, что товар существует, что равносильно прибавлению единицы к количеству, необходимо обратить внимание, имеет ли этот товар отношения родитель-потомок с существующим товаром, и является ли он можно изменить какое-либо правило активности после его добавления, например: изначально купили 2. Один получит 1 подарок, а теперь добавляется еще один, чтобы стало 3, и будут отправлены 2 подарка;

Примечание. Добавление здесь не для прямого изменения количества в корзине, это может быть добавление и добавление непосредственно в список и страницу сведений.

Проверив данные объединенной корзины покупок и подтвердив свое согласие в маркетинговой кампании, они будут напрямую записаны обратно в хранилище.

Объединить корзину

Почему существует операция объединения корзин? Поскольку обычной электронной коммерции разрешено работать в качестве туриста, поэтому, когда пользователь входит в систему, их необходимо объединить.

Логика объединения многих частей здесь — это логика, которую можно повторно использовать при добавлении в корзину. Например, объединенные данные нужно проверить на законность, а затем перезаписать обратно в хранилище. Таким образом, вы можете увидеть корреляцию здесь. Подход к дизайну должен быть несколько общим.

список корзины покупок

Список корзины покупок Это очень важный интерфейс, в принципе, интерфейс корзины покупок будет двух типов: простая версия и полная версия;

Краткая версия интерфейса списка в основном используется для получения простой информации, такой как верхний правый угол домашней страницы ПК; полная версия используется в списке корзины покупок.

В реальной реализации корзина — это не просто интерфейс для чтения. Потому что все мы знаем, что и информация о продуктах, и информация о событиях постоянно меняются. Следовательно, каждый интерфейс чтения должен проверять достоверность данных в текущей корзине покупок, а затем перезаписывать исходные сохраненные данные после обнаружения несоответствий.

购物车列表
список корзины покупок

Также есть некоторые практики, которые проверяют достоверность данных на каждом интерфейсе, я предлагаю, чтобы из соображений производительности некоторые интерфейсы могли быть правильно проверены, а полная проверка могла быть выполнена при получении списка. Например, при добавлении интерфейса я буду проверять легитимность только тех товаров, которые добавляю, и никогда не буду проверять всю корзину. Поскольку операция списка обычно вызывается после операции, проверка также будет выполняться в это время, и две операции повторяются, поэтому берется только последняя.

поселок

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

Еще одна вещь, которую нужно обратить внимание на вот: купить немедленно, мы также реализуем ее через интерфейс страницы расчетной страницы, но интерфейс добавления фактически называется внутренне, чтобы добавить продукт в корзину для покупок; Во-первых, это добавление операции это сделано внутри сервиса, и сервисный звонок не должен воспринимать существование этой операции соединения; Во-вторых, ключ корзины в Redis не зависит от обычной корзины для покупок, в противном случае муфта Из двух продуктов очень трудно эксплуатировать и обрабатывать; последняя покупка покупок, которая сразу должна учитывать, что когда учетная запись вошел в систему с несколькими клеммами, данные друг друга не могут влиять на друг друга. Здесь UUID каждого терминала Может использоваться в качестве маркировки корзины, чтобы избежать этой ситуации.

Последним шагом в корзине покупок является создание заказа.Самый важный шаг на этом этапе – блокировка корзины покупок, чтобы избежать подделки данных в процессе отправки.И еще один момент, распределенный код блокировки Redis, написанный многими людьми, имеет дефекты. Обратите внимание на проблему атомарности, многие из этих статей в Интернете не будут их повторять.

После успешной блокировки у нас есть множество методов.Один из них - начать писать таблицу в соответствии с данными, связанными с организацией БД.Это подходит для требований к объему малого бизнеса, таких как объем заказов в секунду. не превышать 2000K, тогда, если ваша система Что делать, если требования параллелизма очень высоки?

На самом деле, это также очень просто, одно из трех волшебных орудий высокой производительности: асинхронность; когда мы отправляем, мы напрямую записываем моментальный снимок данных в MQ, а затем потребляем и обрабатываем асинхронно, что может повысить производительность обработки. контролируя количество потребителей. Хотя производительность этого метода улучшится, его сложность также возрастет.Вы должны выбрать в соответствии с вашей реальной ситуацией.

Что касается проектирования бизнес-архитектуры, то это конец, давайте посмотрим на системную архитектуру.

структура системы

Структура системы в основном включает в себя способ отображения бизнес-архитектуры и описание выходных соответствующих входных и выходных параметров. Поскольку вход и выход определяются для соответствующих бизнесов, и здесь нет никаких сложностей, здесь мы говорим только о том, как сопоставить бизнес-архитектуру с системной архитектурой, а также о выборе основной структуры данных Redis и проектировании структуры данных хранения в Архитектура системы.

Структура кода

Следующий каталог кодов соответствуетGolangразрабатывать. Давайте посмотрим, как сопоставить приведенную выше бизнес-архитектуру с уровнем кода.

├── addproducts.go
├── cartlist.go
├── mergecart.go
├── entity
│   ├── cart
│   │   ├── add.go
│   │   ├── cart.go
│   │   └── list.go
│   ├── order
│   │   ├── checkout.go
│   │   ├── order.go
│   │   └── submit.go
│   └── precart
├── event
│   └── sendorder.go
├── facade
│   ├── activity.go
│   └── product.go
└── repo

внешний слой имеетentity,event,facade,repoЭти четыре каталога имеют следующие обязанности:

entity: Сохраняются три сущности в поле покупок, которые мы проанализировали ранее, все основные операции выполняются над этими тремя сущностями;

event: Это используется для обработки сгенерированных событий.Например, если мы отправляем заказ асинхронным способом, каталог должен делать то, как отправлять данные в MQ;

facade: Для чего этот каталог? Это в основном связано с тем, что наши услуги также должны полагаться на такие услуги, как товары и маркетинговая деятельность, поэтому мы не должны вызывать это непосредственно в организации, потому что могут быть изменения в третьей стороне, или может быть увеличение или уменьшение.Здесь мы выполните следующую простую инкапсуляцию (режим фасада в режиме проектирования);

repo: этот каталог в некоторой степени можно понимать какModelСлой, во всей доменной службе, если речь идет о сохранении, это делается через него.

Последние несколько файлов на внешнем уровне — это предоставляемые нами доменные службы, которые вызываются прикладным уровнем.

Чтобы обеспечить компактность контента, я отказался от каталожного представления микросервисов целиком, а представил доменные сервисы отдельно, а всю системную архитектуру микросервисов представлю позже в отдельном документе.

Благодаря вышеуказанному разделению мы достигли двух целей:

  1. Структура анализа бизнес-архитектуры отображена в коде системы, и они отражают друг друга. Самым большим преимуществом этого является обеспечение согласованности дизайна и кода, и вы узнаете, где находится соответствующий код после прочтения документа;

  2. Соответствующие задачи каждого каталога разделены, более связны, их легче развивать и поддерживать.

Хранилище Redis

Глядя на это сейчас, мы выбираем Redis в качестве хранилища данных о покупках.Нам нужно решить две проблемы.Во-первых, какие данные нам нужно хранить? Во-вторых, какую структуру мы используем для ее хранения?

Многие люди, которые пишут корзины покупок в Интернете, сохраняют только один идентификатор продукта, и в реальных сценариях трудно удовлетворить спрос. Подумайте об этом, как идентификатор продукта запоминает раздачу, выбранную пользователем? Последнее действие, выбранное пользователем? И канал покупки товара?

Для более общего сценария привожу ссылочную структуру:

// 购物车数据
type ShoppingData struct {
 Item       []*Item `json:"item"`
 UpdateTime int64   `json:"update_time"`
 Version    int32   `json:"version"`
}

// 单个商品item元素
type Item struct {
 ItemId       string          `json:"item_id"`
 ParentItemId string          `json:"parent_item_id,omitempty"` // 绑定的父item id
 OrderId      string          `json:"order_id,omitempty"`       // 绑定的订单号
 Sku          int64           `json:"sku"`
 Spu          int64           `json:"spu"`
 Channel      string          `json:"channel"`
 Num          int32           `json:"num"`
 Status       int32           `json:"status"`
 TTL          int32           `json:"ttl"`                     // 有效时间
 SalePrice    float64         `json:"sale_price"`              // 记录加车时候的销售价格
 SpecialPrice float64         `json:"special_price,omitempty"` // 指定价格加购物车
 PostFree     bool            `json:"post_free,omitempty"`     // 是否免邮
 Activities   []*ItemActivity `json:"activities,omitempty"`    // 参加的活动记录
 AddTime      int64           `json:"add_time"`
 UpdateTime   int64           `json:"update_time"`
}

// 活动
type ItemActivity struct {
 ActID    string `json:"act_id"`
 ActType  string `json:"act_type"`
 ActTitle string `json:"act_title"`
}

Сосредоточьтесь на этомItemэта структура,item_idЭто поле является единственным тегом для пометки товара в корзине, потому что, как мы уже говорили ранее, один и тот же артикул будет двумя разными товарами в корзине из-за разных каналов;parent_item_idПоле используется для обозначения отношения родитель-потомок.Здесь возможная древовидная структура преобразуется в последовательную структуру.Будь то родительский продукт или дочерний продукт, мы используем последовательное хранилище, а затем используем это поле для связи;некоторые студенты могут быть удивлены, почему хранится идентификатор порядка поля? Каждый обращает внимание на свои повседневные дела, такие как: другой заказ, предварительная продажа депозита и т. д. Это должно быть связано с заказом, будь то проверка квалификации или статистика данных. Остальные поля являются очень обычными полями, поэтому они не будут вводиться по одному;

Тип поля, вы можете изменить его в соответствии с вашими потребностями.

Далее, как выбрать структуру хранения Redis, обычно используется Redis.Hash Table、集合、有序集合、链表、字符串Пять, мы будем анализировать один за другим.

Прежде всего, корзина для покупок должна иметь ключ для отмены, к которому принадлежит пользователь корзина для покупок. Чтобы упростить, наше ключевое предположение:uid:cart_type.

Давайте сначала посмотрим, используем ли мыHash Table; Когда мы его добавляем, нам нужно использовать следующие команды:HSET uid:cart_type sku ShoppingData; Кажется, нет проблем, мы можем быстро найти товар по артикулу, а затем внести соответствующие изменения и т. д., но учтите, что ShoppingData — это строка json, если у пользователя много товаров в корзине, мы используемHGETALL uid:cart_typeПолученная временная сложность составляет O(n), а затем код необходимо десериализовать один за другим, что составляет сложность O(n).

Если вы используете集合, также будут сталкиваться с подобными проблемами, каждая корзина рассматривается как коллекция, каждый элемент в коллекции — это ShoppingData, и код все равно нужно десериализовать по одному (десериализация — это стоимость), про упорядоченную коллекцию и связанный список не анализируется, вы можете попробовать решить проблему в соответствии с приведенными выше идеями.

Похоже, у нас нет другого выбора, кроме как использоватьString, тогда давайте посмотримStringКак выглядит подгонка. во-первыхSET uid:cart_type ShoppingDataArr; Мы сериализуем все данные корзины покупок в хранилище строк, временная сложность каждого извлечения составляет O(1), а сериализация и десериализация требуется только один раз. Кажется, это очень хороший выбор. Но есть несколько моментов, о которых следует помнить при его использовании.

  1. Одно значение не может быть слишком большим, иначе возникнет большая ключевая проблема, поэтому у общей корзины есть верхний предел, например, количество товаров, которое нельзя превышать;
  2. Операционные характеристики Redis были улучшены, но код неудобна при изменении одного элемента. Вы должны прочитать их каждый раз, а затем найти соответствующий элемент для изменения; здесь мы можем прочитать данные от Redis и хранить его в памяти , Построить Hashtable, чтобы уменьшить сложность каждого обход;

Существует также множество структур данных Redis, используемых в сочетании для сохранения данных корзины покупок в Интернете, но это, несомненно, увеличивает нагрузку на сеть.По сравнению со String это наиболее экономичный и рентабельный вариант.

Суммировать

На этом реализация дизайна корзины завершена, а дизайн таблицы заказов будет размещен отдельно в модуле заказов.

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

В статье есть очень интересные места.Предлагаю сделать это самостоятельно.Если будут вопросы,можем связаться в любое время.

  • Адаптированная модель цепочки ответственности
  • Распределенные блокировки транзакций Redis для

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

Личный общедоступный номер: dayuTalk

Контактный адрес электронной почты: dayugog@gmail.com

Гитхаб:github.com/helei112g