Комментарии 19
Где здесь гексагональная архитектура? Автор просто применил DI рефакторинг и на этом закончил
async_trait это таки прихоть и сейчас можно без него и даже лучше без него делать и юзать подход dynosaur.
глянул dynosaur. хороший подход, если нужно сохранить zero cost для статической диспетчеризации
В моем случае AppState использует только dyn поэтому оверхед async_trait не критичен, а его простота и популярность в экосистеме сыграли в его пользу для туториала
Но за альтернативу спасибо, полезно знать
Там он не такой уж простой, достаточно сделать cargo doc и охренеть, поэтому я стараюсь все же вручную писать трейты
Понимаю вашу боль. Разворот макроса в документации действительно выглядит устрашающе, и дебажить типы, если что-то пошло не так, бывает непросто
Но тут вопрос приоритетов. Если писать вручную возвращаемые типы Pin<Box<dyn Future...>> в каждом методе доменного слоя, это сильно повышает порог входа и зашумляет бизнес логику техническими деталями. async_trait стал стандартом дефакто в веб бэкенде на Rust именно потому, что позволяет писать сигнатуры почти как в синхронном коде, пряча эту сложность от разработчика прикладного уровня
Для обучающего материала мне показалось важнее, чтобы читатель сфокусировался на архитектуре и потоках данных, а не на борьбе с лайфтаймами в боксовых футурах)
Довольно интересный пример с блокчейном! Аккуратно и занимательно.
Суть же подхода, описанного в статье, кажется, можно выразить короче: "Изоляция бизнес-логики от тяжёлых сервисов способствует её тестируемости и поддерживаемости, а также расширяемости сервисов" - что является здоровой практикой.
Небольшое замечание к схеме архитектуры: указано неверное отношение implements от AppState к ChainClient. Implements должно быть от конкретных реализаций же ниже к интерфейсу - но здесь, видимо, указан просто путь запроса, а не иерархия сущностей.
Подскажите пожалуйста, почему в статье используется solana_sdk, а в репозитории по ссылке - rpc via http? Чем они отличаются?
Спасибо за фидбек и отличные вопросы.
Вы правы на счет схемы она больше про поток данных, чем про строгую UML. Сделал для наглядности
solana_sdk и RPC это не разные вещи а части одного целого. solana_sdk - большой набор крейтов который дает и "инструктор" для сборки транзакций (solana_sdk::transaction) и сам RPC клиент для их отправки (solana_client). Этот клиент как раз работает по JSON-RPC (поверх HTTP). Так что в статье и репе используется один и тот же подход - официальный SDK клиент
Попробуйте в C# сделать порт абстрактного Excel движка чтобы юзать или epplus или closexml. Вам не захочется: тяжёлые объекты sheet, разные концепции.
Трейты Rust как порты ложатся естественно, без церемоний с ООП интерфейсами и DI-контейнерами. И трейты склонны к более мелким абстракциям чем ООП.
ООП интерфейсы часто описывают "кто ты", трейты - "что умеешь". Второе лучше композируется.
Примерно так
IWorksheet - "ты лист Excel". Тащишь сразу всё что лист "должен уметь". Разделить интерфейсы можно, но использовать многословно
Трейт ReadCells, WriteCells, MergeCells - отдельные способности, комбинируй как нужно.
Ваше различие между ООП интерфейсами и трейтами самое лаконичное объснение сути которое я слышал)
Именно об этом и вся статья, бизнес логике не нужен весь "тяжелый" клиент соланы, ей нужна лишь одна его способность записать хеш. Трейты в Rust позволяют выразить эту потребность без лишнего багажа
Пример с C# библиотеками для Excel точно в точку, эта боль заставляет искать более гибкие подходы
Асинхроннонность разве не так?
pub trait ChainClient: Send + Sync {
fn write_hash(&self, hash: &str, meta: &Diploma)
-> impl Future<Output = Result<BlockchainRecord, AppError>> + Send;
fn find_by_hash(&self, hash: &str)
-> impl Future<Output = Result<Option<BlockchainRecord>, AppError>> + Send;
fn health_check(&self) -> impl Future<Output = Result<(), AppError>> + Send;
}
...
impl ChainClient for MyClient {
async fn write_hash(&self, hash: &str, meta: &Diploma)
-> Result<BlockchainRecord, AppError> {
// ...
}
async fn find_by_hash(&self, hash: &str)
-> Result<Option<BlockchainRecord>, AppError> {
// ...
}
async fn health_check(&self) -> Result<(), AppError> {
// ...
}
}Вроде, стабилизировали уже
Отличный поинт. Для статической диспетчеризации дженериков это теперь стандарт
Но этот подход к сожелению не object-safe. Его нельзя использовать с dyn Trait
А у меня вся архитектура завязана на Arc<dyn ChainClient> и AppState, поэтому без async_trait (который боксит футуры) пока не обойтись
Так что это осознанный выбор именно под dyn
А зачем dyn?
Вы же не собираетесь использовать набор контрактов одни солана, другие нет.
вся архитектура завязана на
Arc<dyn ChainClient>иAppState
Архитектура или код?
Выбор между dyn и дженериками обычный вопрос в Rust
Я не храню реализации в одной коллекции. Но я выбрал dyn Trait по нескольким причинам которые являются для меня архитектурными
1)Скорость компиляции. Дженерики приводят к мономорфизации изза чего в больших проектах это может замедлить инкрементальные сборки на минуты.
2) Эргономика и невирусность. Если AppState становится AppState<c: ChainClient> , то все хендлеры, которые его используют тоже должны стать дженериками. А это переусложняет сигнатуры и код веб слоя. dyn Trait позволяет сохранить AppState и хендлеры простыми и чистыми создавая четкую компиляционную границу которую дженерики размывают
3) Гибкость. С dyn Trait я могу собрать один бинарник который при старте читает конфиг или переменную окружения и решает какую реализацию поднять. SolanaChainClient для прода, MockChainClient для тестов или стейджинга. С дженериками это сделать не сложнее, и часто требует перекомпиляции с разными #[cfg] флагами.
Архитектура или код?
Архитектура набор осознанных ограничечий и компромиссов. Жертва наносекундами производительности на vtable вызовах ради ускорения разработки, упрощения кода и гибкости конфигурации
Думаю, может тоже Соланой заняться? Контракты писать или что там..
Денежная тема или уже нет?
Если вы уже пишете на Rust, то порог входа будет низким. В solana смарт контракты пишутся на Rust, де-факто стандарт сейчас фреймворк Anchor, он очень производительный
Насчет "денежная или нет" рынок стал взрослее. Времена шальных денег на простых форках прошли, но спрос на сильных инженеров, способных пилить сложный DeFi, инфраструктуру или DePIN (физические сети), огромный. И рейты там часто выше, чем в классическом вебе
Главный плюс для разработчика даже если хайп спадет или вы решите уйти из Web3, опыт написания строгого, высокопроизводительного кода на Rust и понимание работы state machines останутся с вами. Это отличная инвестиция в hard skills
При таком подходе удобно использовать библиотеку mockall https://docs.rs/mockall/latest/mockall/
Вдруг кому пригодится

Гексагональная архитектура в Rust: отвязываем бизнес-логику от Solana