Вызов:
cargo run
от корня репозитория, и вы готовы к работе.
Также попробуйте исследовать документы API, чтобы получить представление о том, где все живет:
cargo doc --open
Этот репозиторий содержит образец приложения ржавчины для интернет -магазина. Цель состоит в том, чтобы изучить некоторые шаблоны дизайна, которые используют язык ржавчины для создания масштабируемых и обслуживаемых приложений.
Это игровая площадка для разных идей, некоторые из них могут не на практике. Если у вас есть какие -либо отзывы о чем -то здесь, пожалуйста, не стесняйтесь открыть проблему!
Трудно разработать программное обеспечение в вакууме. Если у вас нет реального домена, чтобы управлять тем, что важно, дизайнерские решения могут казаться произвольными. Я приложил усилия, чтобы документировать решения и причины, стоящие за ними, но такие вопросы, как мы должны разделить пункты заказа от заказов? Или запросы в закатах должны иметь возможность получить доступ к таблицам базы данных для продуктов? Нельзя на самом деле ответить с чисто технической точки зрения. Они также требуют взгляда на цели проекта. Для тех, кто читает этот код, я бы посоветовал вам тщательно изучить его на основе этих произвольных дизайнерских решений, подумать о ограничениях, с которыми вы сталкиваетесь в вашей собственной среде, и о том, как они могут проинформировать о ваших собственных решениях при создании приложений в Rust.
Речь идет не о конкретных фреймворках или библиотеках ржавчины или о решении проблем, присущих онлайн -приложению для покупок.
В следующих разделах описываются части приложения и объясняют, почему они собирают такими, какие они есть.
Макет проекта сосредоточен на конфиденциальности. Ограничивая объем определенных элементов, вы также ограничиваете объем потенциального поломки. Ограничивая объем определенных предметов, вы также ограничиваете объем бремени поддержания состояния применения. В ржавчине предметы, которые являются частными в модуле , видны всем детям этого модуля . Это может показаться плохой вещью, но мы используем его, чтобы предотвратить доменные API не протекать детали реализации ради внешних проблем, таких как сериализация и хранение.
Каждая основная бизнес-концепция в приложении разделена на свою собственную (в основном) автономную папку, например, products или customers . Каждый модуль инкапсулирует все, что нужно знать о конкретном наборе сущностей:
/store )/queries )/commands ) Организации могут зависеть от сущностей из другого модуля, например, Order в зависимости от Product при его добавлении. В каждом доменном модуле есть иерархия конфиденциальности:
from_datafrom_data до гидратных сущностейЭти модули немного тяжелые, но в правильном приложении добавление новых доменных модулей может быть упрощено с помощью макросов. Я не использовал макросы в этом приложении, поэтому код остается простым.
Одна из проблем с идеально созданной иерархией модуля заключается в том, что все это может развалиться, когда вы получите концепцию, которая просто не вписывается в текущий макет. Чем чаще это происходит, тем сложнее становится соответствовать планировке, которая существовала раньше, потому что становится невозможным сказать, каким он должен быть.
Мы хотим, чтобы эти модули управляли своей собственной судьбой, но мы не хотим, чтобы они были автономны до такой степени, что их можно разделить на отдельные услуги. Это чтобы сделать вещи простыми. Если вы действительно хотели это сделать, то я бы посоветовал использовать отдельные ящики вместо отдельных модулей.
Приложение следует простому дизайну сегрегации командных запросов. Это подход, который хорошо работает для приложения, управляемого данными, без большой сложной логики. Команды отражают некоторое взаимодействие домена и работают непосредственно на объектах, тогда как запросы абсолютно произвольные. Это приложение не использует какую -либо специальную инфраструктуру для реализации CQRS, это просто простые черты, внедренные с использованием шаблона впрыскивания зависимостей. По сути:
Result<()>Result<T>&mut self&self -приемникаРазница в изменении означает, что команды могут вызывать запросы, но запросы не могут вызывать команды.
Сущности являются сердцем применения. Несмотря на отсутствие реального бизнеса, я приложил усилия, чтобы сохранить богатую доменную модель. Сущности - не просто сумки с грудным государством. Они есть:
.to_data() . При просмотре сущности вы не можете назвать изменяющее поведение. Это гарантировано системой заимствования Руста. Сущность может перенести право собственности в свои данные только для чтения с .into_data() . Это односторонняя операция, поэтому любые изменения, внесенные в утверждение, нельзя сохранить в магазине.Цель сущности - инкапсулировать инварианты некоторой концепции ключевой области. Сущности здесь просты в использовании либо с макетом в памяти, либо с внешней базой данных. Мы должны быть осторожны, чтобы не полагаться на изменения состояния, когда одна организация отражается в другом, потому что они указывают на тот же источник.
Организации также должны быть осторожны, чтобы не зависеть от типов данных другого объекта, потому что нет никакой гарантии, что данные фактически действительны. Вместо этого они зависят от сущности и преобразуют его в данные по мере необходимости, поэтому они всегда знают, что состояние действительнее.
Мы используем следующие функции ржавчины, чтобы защитить состояние нашего субъекта:
Serialize и Deserialize . Это может быть изменено по дорожке, но мне легче сохранить сериализуемое состояние быстро и похолоть на обратную совместимость.Организации инкапсулируют какое -то состояние или данные и гарантируют, что любые изменения, внесенные в эти данные, не нарушают никаких инвариантов, которые, как ожидается, данные. Вместо того, чтобы внедрять Getters, мы раскрываем представление данных только для чтения как структуры. Преимущество заключается в том, что вам не нужно отказываться от хороших функций Rust для работы с Datastructures, как вы бы с методами Getter. Эта точка зрения только для чтения , поэтому изменения не могут быть записаны непосредственно обратно в структуру. Сущность по -прежнему предоставляет методы сеттера для этого.
Вы могли бы утверждать, что раскрытие состояния таким образом утечкает детали реализации, например, version , которая не имеет значения публичной. Это, вероятно, правда. Чтобы обойти его, вы можете перемещать время жизни только для чтения на поля и составить потенциально различный заимствованный представление о состоянии и сохранить структуру данных, управляемую частной организацией.
Вы также можете утверждать, что инварианты на структуре, которая их не хранит, является хрупкой. Это имеет смысл, когда граница конфиденциальности для какого-то поля находится на уровне объекта, как в C#. Ржавчина немного отличается, хотя. Самая жесткая граница конфиденциальности находится в модуле и его детях . Таким образом, бремя поддержания инвариантов данного поля падает на все предметы в модуле, в котором он определен, плюс все дети этого модуля.
Это может звучать как ужасная утечка, но это приложение использует это для создания красиво абстрактного хранилища. Вместо того, чтобы разоблачить отверстия в нашем API, чтобы поддержать ORM, поддержание состояния инвариантов просто распространяется на магазин моделей, не пропустив обратно к общественности.
Id и типы Version имеют фантомный общий параметр. Этот параметр существует исключительно для того, чтобы вы выражали идентификаторы с несовместимыми типами, такими как Id<ProductData> и Id<OrderData> , но все же делиться другими деталями реализации.
Это шаблон, которой легче следовать, чем использовать макрос для уменьшения шаблона, потому что в источнике всегда есть куда -то, к которой можно вернуться.
У каждой настойчивой сущности есть поле version . Это поле представляет собой не последовательный идентификатор, который соответствует состоянию сущности в заданный момент времени. Когда сущность извлекается из магазина, мы увлажняем его версию, затем проверяется непосредственно перед обновлением, и если они не совпадают, мы отказываемся.
Проверка версий работает нормально для магазина в памяти, потому что у нас есть эксклюзивная блокировка данных (только 1 вызывающий абонент может изменять состояние за раз), но потребуется другой подход для правильного БД. Вероятно, мы можем обновить, где совпадают идентификатор и версию, выберите количество обновленных записей и отказались, если это 0 (означает, что версия не совпадает, или ее не существует).
Уровень хранения использует простую транзакционную схему, которая позволяет независимым хранилищам данных участвовать в транзакциях. Центральный репозиторий отслеживает активные транзакции и консультируется, когда данные извлекаются из хранилищ данных, чтобы убедиться, что они готовы к использованию. Оптимистическая параллелизм на данных гарантирует, что несколько активных транзакций не могут попробовать установить одно и то же значение одновременно. Это нарушает истинную изоляцию, но делает вещи простыми, и позволяет нам минимизировать состояние, необходимое для каждого значения, хранящегося.
Инъекция зависимости полезна как практика, на которую можно опираться при проектировании приложений. Это позволяет отделить проблемы разрешения зависимостей от логики приложений. Это также дает вам очевидный способ масштабирования применения. Это приложение принимает простой шаблон, который дает нам эти преимущества без большого количества инфраструктуры.
Это приложение не использует инверсию управляющего контейнера, как вы можете использовать, если вы пишете приложения .NET. Это в основном потому, что на самом деле нет ничего для ржавчины. Это сложная проблема. В нем используется простой шаблон впрыска зависимостей для составления команд и запросов, даже без сложного контейнера.
Основная цель инъекции зависимостей здесь не заключается в том, чтобы поддерживать насмешки. Это для уменьшения сложности, выдвигая периферийные проблемы дальше от логики отдельного компонента.
Инъекционные компоненты живут в своем собственном модуле. Этот модуль содержит:
Resolver , который содержит метод, который возвращает реализацию по умолчанию, не требуя его зависимостей.impl Trait . Вы никогда не знаете, какой конкретный тип использует эта реализация по умолчанию.Arc , Box . Общий Resolver звучит немного с сервисным локатором-Y, и это так, но, поскольку разрешение зависимостей полностью содержится в блоках IMP на самом Resolver , мы избегаем проблемы в зависимости от Magic Global State в нашей логике приложения.
Чтобы уменьшить пакет, для компонентов только с одним методом мы также используем их для признаков Fn . Это позволяет вам не объявлять структуру для них, которая общей по всем их зависимостям. Компилятор ржавчины позаботится об этом для вас.
Этот шаблон трудно описать в прозе, вам нужно ее увидеть. Посмотрите на модуль domain/products/commands/create_product , или на модули domain/products/model/store для примеров этой схемы впрыска зависимостей на работе.
Resolver «объект Бога»? Объект Бога " - это объект в вашем приложении, который собирает всю важную логику в тот момент, когда вы не можете работать с компонентами, не проработав объект Бога. Они являются проблемой, потому что их становится трудно строить или изменить. Образец Resolver здесь - это объект Бога, но не обязательно для строительства отдельных компонентов. Resolver имеет дело только с передачей концепции, которые он сами не требует, чтобы они сами не требовались.