Как хранятся заказы корзины каталога
Как применяется механизм карты спотов для распределения данных
Как встроить код роутинга запросов в active record фреймворка Laravel
Как написать behat-тесты, покрывающие распределенную структуру хранения
10. class PreviewUpdater
{
public function created(Order $order)
{
OrderPreview::create([
'id' => $order->id,
'user_id' => $order->user_id,
'status' => $order->status,
// ...
]);
}
public function updated(Order $order)
{
/** @var OrderPreview $orderPreview */
$orderPreview = OrderPreview::find($order->id);
$orderPreview->user_id = $order->user_id;
$orderPreview->status = $order->status;
// ...
$orderPreview->save();
}
public function deleted(Order $order)
{
OrderPreview::destroy($order->id);
}
}
11. abstract class Model extends EloquentModel
{
public static function find($id, $columns = ['*'])
{
if (is_array($id)) {
return static::findMany($id, $columns);
}
return static::spotQuery(Map::spot($id))->find($id, $columns);
}
public static function spotQuery(Spot $spot)
{
$instance = new static;
$instance->spot = $spot;
$connection = $instance->spotConnection($spot);
$builder = $instance->newEloquentBuilder(
new QueryBuilder($connection, $connection->getQueryGrammar(), $connection->getPostProcessor())
);
return $builder->setModel($instance);
}
// ...
}
12. $order = OrderPreview::orderQuery()->whereOrderKey($orderId)->first();
class OrderPreview extends Model
{
public static function orderQuery()
{
$instance = new static;
return (new QueryBuilder($instance->newBaseQueryBuilder(), new Order))->setModel($instance);
}
}
class QueryBuilder extends Builder
{
/** @var Model */
private $spotted;
private $with = [];
public function getModels($columns = ['*'])
{
return $this->spotted->findMany(array_pluck(parent::getModels($columns), 'id'), $columns,
$this->with)->all();
}
}
Запросы через превью
13. abstract class Model extends EloquentModel
{
public function hasMany($related, $foreignKey = null, $localKey = null)
{
//...
$instance = new $related;
$instance->spot = $this->spot();
return new HasMany(/*...*/, $instance->getTable() . '.' . $foreignKey);
}
}
Поддержка relations
14. Scenario: Spots with different databases
Given spots map contains spots:
| start_id | finish_id | connection_name | database | spot_prefix |
| 1 | 1 | catalog_cart | <test_db> | |
| 2 | 2 | catalog_cart_spot2 | <test_db>_spot2 | |
# ...
When I send POST request to "/orders"
Then request expectations should be met
And log file should be empty
And response status code should be 201
And SQL query "select * from <test_db>.orders" result should be like:
| id | user_id | shop_id | status | delivery_city | delivery_address | comment |
| 1 | 1 | 1 | 0 | Минск | пр-т Дзержинского, 5 | comment1 |
# ...
Behat-сценарии для спотов
16. Генерация ID
● Auto-increment ID (1, 2, 3, …)
○ Закрыть информацию
○ Шардинг
● Hashids (3jR-OCl4rG0)
○ Number vector
○ Недостаточно безопасные
● UUID (550e8400-e29b-41d4-a716-446655440000)
○ Нечитаемые
● ID sequence
○ Читаемый набор символов (без i и 1)
○ Единый центр выдачи идентификаторов
○ Механизм отслеживания коллизий
17. $this->sequence = $connection->table('order_id_sequence');
// ...
for ($i = 1; $i <= config('order.key.collisions.limit'); $i++) {
try {
$order->key = $this->generateOrderKey();
$order->id = $this->sequence->insertGetId(['order_key' => $order->key]);
return;
} catch (QueryException $ex) {
if ($ex->getCode() != 23000) { // Integrity constraint violation
throw $ex;
}
}
if ($i == config('order.key.collisions.warning')) {
(new OnlinerLogger)->warning(
"cart-api: $i collisions occurred, think about increasing of order key length"
);
}
}
throw new RuntimeException(
"Failed to generate key for order from user {$order->user_id} to shop {$order->user_id}"
);
18. class Order extends SpotsModel
{
public $incrementing = false;
protected $spotIdKey = 'id';
protected $table = 'orders';
public function statusLogEntries()
{
return $this->hasMany(OrderStatusLogEntry::class);
}
public function save(array $options = [])
{
if (!$this->exists) {
// ...
app(IdGenerator::class)->generate($this);
}
return parent::save($options);
}
protected static function boot()
{
parent::boot();
// ...
static::observe(PreviewUpdater::class);
}
}
Editor's Notes
Привет, петр трофимов, спасибо, несмотря на дедлайны, чтото полезное, вопросы в конце
1 мин
Кто заказывает товары через корзину в каталоге? Поднять
Кто нет? А кто будет теперь? (из названия хехе)
До этого - телефоны, але?, рога и копыта, нету да, положить денег на телефон
Октябрь 2015 - декабрь 2015
Все проще, нажимаем, превращается вот в такую
Конечно не все так просто, там еще эту и эту нажать
Уведомление
3 мин
Какова специфика каталога онлайнер? 800 тысяч, 600, 1000, 5 млн
Заказов больше, чем в одиночном магазине - много данных
Много разнородных магазинов - частые обновления, получить актуальные данные
Частые изменения прайсов - фиксация данных, много данных
Состав может меняться - нужно фиксировать, снэпшоты, много данных
Заказы храним очень долго, пока не удалят - много данных, чем старше тем ненужнее
Можно ли сохранить в одной таблице? Приватные сообщения
Шардинг в начале, потом очень сложно
Что происходит после нажатия кнопки отправить заказ?
4 мин
Разбивка по магазину, JWT токен, микросервисы, внутренние запросы, выбор хранилища
Кусочек данных, перенос между серверами, балансировка, закрытие открытие
Что из себя представляет карта?
Просто таблица, неравномерные диапазоны, хост, база, префикс, открытые диапазоны
Все ходят в эту таблицу? Нет, офлайн сброс
Абстрактный, спот айди,
Выбор спота, переопределить системный метод подключения,
Сет подключения в конфиг, выбор таблицы - переопределить
Спотов много, делать запрос к каждому?
Запись и туда и туда, чтение через превью, репликация ()
Листенер, создание, обновление, удаление
Метод поиска, выбор подключения
Что такое релейшнс, связанные таблицы, ограничение - один спот, джойны
Коллизии - 5 и 100, уникальный индекс,
Кто знает что такое Hashids? Реакция - ты крутой
Сиквенс обычная таблица, несколько попыток перед исключением,
Вставка в уникальный индекс, ловим исключение,
Ворнинг
Спец родительский класс, отключаем автоинкремент,
Указываем спотируемое поле, таблица обычное имя,
Релейшны можно, генерация айди,
Превью апдейтер, сколько нужно