👾Подписывайтесь на мой канал в Telegram: t.me/ntuzov ❤ Если у вас есть желание поддержать развитие канала: Секретный телеграм-канал: - В рублях: t.me/+1UPXV_DGnG1mODJi - В евро: t.me/+hedI8LevYTc5MDM6 boosty.to/nikolay.tuzov www.patreon.com/tuzov
Спасибо, ты лучший из ютуберов по го) по поводу mockery, использую эту библиотеку для юнит тестирования, но к сожалению опыта в тестах пока имею мало. Так что жду более широкого раскрытия данной темы)
Gomock несколько раз мегяла главного мейнтейнера, что негативно сказалось на ее развитии + в плане конфигурации - mockery более гибкая, удобно в yaml формате указать отдельные методы интерфейса, которые хотим мокать, что на выходе делает меньше кода, более тонкие мержи на ревью Еще мокери больше стимулирует генерить код там гле используешь, с gomock часто виду что мок сгенеренный для одноко пакета, потом тянут в других, в итоге меняешь интерфейс в 1 месте, а тесты падают по всему проекту)
Николай, благодарю за науку. Этой информации о том, как использовать "кубики" очень не хватает в курсах. В книгах это может быть, но часто это ещё и в голове нужно уложить. А тут инфа от практикующего кодера из первых уст т.с.
если мы будем рассматривать handler из реальной жизни, который оркестрирует например базой данный, кешем, дергает какие-то third party библиотеки ну и еще много чего он может делать у себя под капотом. обычно таких вот handler-ов может бы очень много если мы говорим о реальном приложении. так вот в таком случае получается будет очень много вот таких вот мелких интерфейсов, плюс многие из них будут/могут пересекаться своими методами. так же будет большая пачка моков делающих практически одно и тоже(их можно не хранить в самом проекте а генерировать только по надобности до запуска тестов). в общем в реальном приложении получается слишком большой оверхед по тому, сколько надо писать и поддерживать. к этому всему мы подключаем упомянутый факт, что иногда нам надо что-то поменять и тут начнется самое интересно даже при условии что IDE будет в этом помогать. в общем достаточно неоднозначный подход с большим количеством минусов на самом деле.
Вы своим сообщением описали минус подхода из других яп с наследованием одного интерфейса как будто, а в конце сказали, что много минусов у гошного подхода. Можете, пожалуйста, дать пояснение?
@@maratfatkulov4993всë верно он описал. Это полное непонимание как работают интерфейсы. Начну по порядку: 1. То что интерфейс разбивается на методы может быть не плохо и соответствует single responsibility. Но это зависит от задачи. Если задача не требует, нет необходимости усложнять. В приведённом примере правильно оставить все методы хранилища в одном интерфейсе, т к они выполняют одну задачу - сохранение объекта для последующего получения. Есть также вариант архитектуры когда сохранение отделено от получения, но опять же что бы это было оправдано, вся архитектура приложения должна это использовать 2. Дак-тайпинг тут вообще не причём. Тем более его и нет в примере - все интерфейсы явно передаются и явно реализуются. 3. Где размещать интерфейсы? Точно не в месте использования, т к таких мест много! В этом и смысл интерфейса, что он формализует контракт, который применяется во многих местах. Подход из видео приведёт к дублированию кода, а в конечном счёте к тому что код перестанет быть унифицированным и станет не читаемым. Интерфейсы надо размещать в отдельном пакете с интерфейсами. Тем более если это граница слоёв приложения. 4. Кэш вместо хранилища... Или по другому, кэш делается декоратором (гуглить паттерн декоратор) На мой взгляд не удачное решение. Лучше делать его паттерном "стратегия". Т е передать внутрь реализации хранилища как зависимость и дать хранилищу им оперировать.
Просто говоря про интерфейсы нужно подключать знания чистой архитектуры, ddd и н-лет практики их использования и постоянных экспериментов, в итоге трейдов количества интерфейсов vs 1 файл, начинает склоняться в сторону кучи интерфейсов, особенно если поработал над проектами которым 3+ года, и где успело смениться 2-3 поколения команды) По мне интерфейсы действительно стоит писать по месту применения, что бы писать чистые юнит-тесты как минимум
Добрый день, спасибо за видео. На практике столкнулись с тем, что такой подход не удобен в случае, если необходимо вернуть интерфейс. Например, есть некое хранилище балансов. Баланс реализован сложно на основе event sourcing. Хранилище дальше инъецируется в другие структуры. В этом случае, если использовать такой подход, то это значительно усложняет тестирование, потому что возвращается именно баланс с конкретной реализацией, который совсем не просто привести в нужное состояние. Если же сделать хранилище с возвращаемым интерфейсом, то утиная типизация перестаёт работать. Go не может понять, что передаваемый извне интерфейс, пусть и совпадающий по сигнатуре, соответствует локальному интерфейсу баланса. В итоге, для таких случаев приходится делать один внешний общий интерфейс балансов, который уже используется везде.
Спасибо за такой развёрнутый комментарий. У меня есть подозрение, что описанный тобой подход не очень правильный. Но это хорошая тема для обсуждения - приходи в наш чатик Gopher Club, обсудим (см. ссылку в описании).
Вполне нормальное решение хранить интерфейс в отдельном пакете, если это требуется. Например мы делаем инверсию зависимости логера, там 5 методов. Не плодить же нам миллион однотипных интерфейсов:)
Перемещение интерфейса к месту использования - это инверсия зависимости (soliD). Утиная типизация не убирает эту зависимость, и код репозитория должен соответствовать интерфейсу объявленному в пакете хэндлеров. Количество зависимостей (зацепление, а не связность) не уменьшилось, изменилось только направление зависимости. Инверсия зависимсоти обычно нужна для создания независимого слоя бизнес-логики. Бизнес-логика задаёт интерфейс взаимодействия с хранилищем, а не наоборот. Это кажется довольно очевидным, но я встречал код, в котором метод репозитория требует передачи коннекта к бд :-) Такое неправильное направление зависимости не позволяет заменить постгрес на мемкэш. Таким образом, интерфейс надо размещать не просто в месте использования, а там где он позволит задать правильное направление зависимости. Если бы на видео была бы какая-то бизнес-логика (например, ограничение количества созданных пользователей в день), и в доменном слое возник какой-то интерфейс, то переносить этот интерфейс в слой хэндлеров было бы непрактично.
А часто приходится менять БД на мем кеш?) Вопрос проброса коннекта (для транзакций) очень интересный, я видел 3 реализации: - проброс коннекта напрямую (очевидная и прозрачная работа, недостаток вы описали) - проброс внутри контекста (неочевидно и непрозрачно) - при каждом запросе создаётся экземпляр менеджера репозиториев с одним коннектом Поделитесь, как вы это делаете?
@@JanePilotessa 2-й вариант я бы сразу отбросил, слишком уж неочевидный Обычно в рабочих проектах коннект передаётся в конструктор, т.е. при создании экземпляра репозитория. Но в некоторых случаях бывает удобней передавать коннект в виде аргумента метода - например, когда мы хотим объединять в транзакцию несколько вызовов методов репозитория. Такие решения тоже встречаются.
я что-то не могу понять последний абзац. Может кто-нибудь прояснить? В видео доменный слой он где? И зачем интерфейс доменного слоя переносить в слой хэндлеров? И как тут нарушается направление зависимости?
@@h3ckphy246 в видео нет отдельного слоя бизнес-логики, так как пример очень простой. Я как раз отмечаю, что интерфейс доменного слоя не надо переносить куда бы то ни было. Переносить интерфейс надо не просто так, а с целью создать независимый слой приложения. Независимость упростит изменения кода в этом слое. А поскольку чаще всего меняется слой бизнес-логики, то обычно стараются сделать независимым именно его. Более подробно об этом можно почитать в очень интересной книжке Чистая архитектура Роберта Мартина.
B как все таки размещать? поднял вопрос но не ответил. Сказал бы: " мужики, я делаю так" или "в компаниях принято так" название ролика "Почему интерфейсы лучше размещать в месте использования" а сам ролик "который вариант имеет + и -сы"
Свичнулся в Go с другого языка в котором стандартная номинативная типизация и такой подход конечно кажется конечно не говнокодом, но болью. Тут следует смотреть, что мы чаще делаем с кодом. Есть такая аксиома в программировании - "код чаще читается, чем пишется". С этой позиции конечно с кодом становится работать куда менее удобно, придется 100500 файлов облазить, о чем сказано в видео. Этот недостаток лично для меня нивелирует преимущества от разделения интерфейсов.
Это конечно сильно... закрывать часть кода своим лицом. Спрашивается, для чего? Можно было бы окно с лицом сделать в два три раза меньше и этого было бы достаточно, или допустим включать иногда, в важных моментах. Некоторые вообще не выводят лица, что увеличивает объем рабочей области, за что благодарности авторам. Самое лучшее, это показать вначале, и в конце видео, вполне достаточно.
Спасибо самые лучшие видео по golang ❤❤❤🙏🏻 А будут ли видео про многопоточность в golang, ещё бы очень интересно было послушать про профилирование. Спасибо за видео. Процветания каналу
Что выходит. Если в 2 разных местах надо получать пользователя, то будут описаны 2 интерфейса, которые полностью совпадают, но описаны в разных местах ? Таким образом получается некое нарушение dry ?
Я вам скажу что dry в некоторых примерах усложняет код чем его упращает и тут мы не следуем kiss , dry это не панацея и иногда от него больше вреда чем пользы
Вопрос про файлы, где у нас интерфейс с одним методом и сразу же его реализация. А в этом случае зачем нам вообще интерфейс? Т.е. почему его не выкинуть (как лишнюю обертку) и не написать сразу обычную функцию под наш конкретный объект? Ведь плюс интерфейсов прежде всего в том, что мы можем использовать один интерфейс в нескольких местах - это и есть полиморфизм. А полиморфизм из одной реализации вроде как не совсем полиморфизм, нет)?
Большое спасибо! Очень интересно и полезно. Если есть возможность, покажите два минимальных проекта отличающихся наличием и отсутствием интерфейса. И какие плюсы у того у которого интерфейс есть?
@@nikolay_tuzov Внимание автора к аудитории очень приятно) Спасибо, что ответил! Даже спустя год от публикации видео. Хочу сказать, что у тебя очень понятные, классные видео, огромное спасибо за твой труд! Ты очень помогаешь новичкам, твои "ультимативные" видео пересылаем друг другу, как золотой стандарт исчерпывающей информации)) Большое спасибо!
Николай, а что будет, если сигнатура метода Use() в исходном интерфейсе взяла и поменялась? Мало того, что в хендлерах это сразу не узнают, так ещё и исправлять придётся массу вот таких вот дополнительных интерфейсов с тем же методом.
Я предпочитаю выносить интерфейсы в отдельный модуль и проектировать из так, чтобы они опирались только на интерфейсы и стандартные типы. Это позволяет модулям реализации и использования вообще не знать друг о друге.
Благодарю за видео! А у Вас нет ссылки на какие-то официальные заявления авторитетных людей, чтобы можно было не только дать ссылку на Ваше видео, но и дать ссылку на сообщение в доке, где черным по белому написано - интерфейс должен быть размещен в месте использования.
Николай, спасибо за видео. Вопрос: я немного не понял, вы сказали, что этот подход уменьшает связность, но в хендлере у вас все равно осталась зависимость от импорта структуры User, или подразумевается, что структуры лежат в независимом пакете, где лежат модели условные?
@@nikolay_tuzov а это go way? Я уже реально запутался, одну статью читаешь, там говорят, что храните модели прямо в сервисе с бизнес логикой и что package models это плохо, в другой говорят, что храните модели в отдельном пакете от сервиса. А где истина то?
@@АртемРогозин-д5ш истина, как обычно, где-то рядом =) Не пытайся найти какое-то "абсолютное мнение". Выслушивай аргументы всех сторон, взвешивай, сравнивай, делай выводы сам.
@@nikolay_tuzov я не понимаю, но в сервисе/хендлере в любом случае же придется проверять ошибки, которые возвращают storage, т.е в хэндлере условно так придется делать: if err == storage.ErrUserExists. и тогда в любом случае мы импортируем пакет storage
Интерфейс который объявлен рядом с использованием нужно объявлять приватным, так как нет нужды ему быть публичным. И пример с использованием storage в слое транспорта выглядит не очень, так как это означает что бизнес логика находится либо в слое транспорта, либо в слое базы данных. Бизнес логика в транспорте это плохо, так как при смене транспорта придется логику переносить и в целом это нарушает high cohesion. Бизнес логика в слое базы данных даже хуже, потому что есть риск реализовать логику на языке sql, а при смене базы данных переписывать ее заново
Не очень понятно, почему интерфейсы в месте использования сделаны экспортируемыми (публичными)? Полагаю приватный вариант был бы более корректен. Вообще экспортируемый интерфейс - это что-то глобальное, скорее прерогатива sdk, определять экспортируемые интерфейсы для повсеместного использования. Сложно что-то придумать, что будет гарантированно использоваться всеми частями проекта. Даже логгер, по той же логике может быть приватным, и часть объектов использует часть возможностей логирования, которую и описывает в месте использования.
Спасибо автору за проделанную работу. Хотелось бы узнать такой же материал, касательно моделей и структур. Где лучше их размещать? Обычно в моих проектах они лежат в pkg/models, но видел как некоторые размещают их в месте реализации
С моделями мы в любом случае будем связаны, и тут уже ничего не поделаешь. Как вариант, можно создать отдельный пакет models, или что-то подобное, чтобы не зависеть конкретно от Storage. Но тут уже нужно исходить из контекста самого проекта.
Классный подход, правда при нем получается, что вместо одной структуры Handler с условными методами CreateUser, GetUser, DeleteUser, UpdateUser, которая принимает один интерфейс Storage со всеми методами, нам придется иметь много структур-хендлеров под каждый метод, то есть CreateUserHandler, GetUserHandler, итд. И соответственно весь этот огромный список инициализировать в main. А когда таких хендлеров десятки или сотни, не начинает ли это выглядеть слишком монструзным, или даже в таком случае продолжают использовать такой подход?
В main ты инициализируешь лишь одну структуру - Storage, и он один реализует все частные интерфейсы всех хендлеров. Дублируется лишь описание интерфейса.
Было время когда я изучал Python, и тогда я нашел просто замечательный канал с подробными объяснениями. Долго хотел найти что-то похожее для Go и наконец нашел) ❤
Не знаю, мож короткий видос сделаешь как делаются(пишутся) разные движки для игр, чтобы вот объединялись в итоге анимация модели движения, ну в чём некая суть пояснить
Не бесплатны - как в плане памяти, так и в плане быстродействия. А насколько, тут уже нужно разбирать конкретные случаи. Можете глянуть этот мой ролик, например: th-cam.com/video/-cX0CqG6rgA/w-d-xo.html Он шуточный, конечно! Но замеры в части про интерфейсы вполне честные. Шуточные там только выводы, которые я из этого делаю.
@@nikolay_tuzov Ага, спасибо за пример. А насчет видео по ссылке... Вот вы там шутили, а я сейчас работаю с людьми, которые именно так и думают на полном серьезе и переубедить их невозможно. Хорошо только одно - они работают в соседней команде.
@@nikolay_tuzov Спасибо за тест, только вот ссылки на его код я под видео не нашел. А насчет самого ролика... вы вот там шутите, а я работаю с людьми которые именно так и пишут код. Хорошо только одно, что они работают в соседней команде.
Конечно в месте использования. Описывать интерфейсы в месте реализации - это где? Если структуры их реализуют неявно - то как понять где у нас место реализации? Сама задача невыполнима, не говоря уже о ее нелогичности.
Размещение интерфейсов в месте использования является правильным подходом и в c++ и в других подобных языках. Никакой специфики go в этом нет. Если для кого-то это кажется странным, возникает большой вопрос к его профессионализму
Видос досмотрел, так как видел анотацию в видео про url-shortnere, а в чём проблема чтобы создать не целый объект Users, а тот же ЮзерКреайте, и не нужно будет создавать все методы, хотя странно раз ты меняешь хранилище, то по идеи нужно переписывать всё, а если лишь просто часть заменить, не большие куски, то можно их ведь вынести
Предыдущему товарищу ответил, продублирую)) Копать сюда: eli.thegreenplace.net/2021/a-comprehensive-guide-to-go-generate/ Как доберутся руки, сделаю об этом отдельный видос
👾Подписывайтесь на мой канал в Telegram: t.me/ntuzov
❤ Если у вас есть желание поддержать развитие канала:
Секретный телеграм-канал:
- В рублях: t.me/+1UPXV_DGnG1mODJi
- В евро: t.me/+hedI8LevYTc5MDM6
boosty.to/nikolay.tuzov
www.patreon.com/tuzov
Николай можно пожалуйста с кафкой очень мало на инете таких видео
очень хотелось бы видео по мокам, да. Спасибо за огромную работу, которую Вы делаете!
Спасибо за видео. Да, подобный подход очень каноничный для го. Но это выглядит как противопоставление чистой архитектуре со своими плюсами и минусами
Про моки очень интересно) И вообще, хотелось бы плейлист про тестирование целый. Успехов автору и развития каналу. Всем Go!
Спасибо, ты лучший из ютуберов по го) по поводу mockery, использую эту библиотеку для юнит тестирования, но к сожалению опыта в тестах пока имею мало. Так что жду более широкого раскрытия данной темы)
Любопытно сравнение с gomock, почему не эта либа выбрана для генерации моков?
Gomock несколько раз мегяла главного мейнтейнера, что негативно сказалось на ее развитии + в плане конфигурации - mockery более гибкая, удобно в yaml формате указать отдельные методы интерфейса, которые хотим мокать, что на выходе делает меньше кода, более тонкие мержи на ревью
Еще мокери больше стимулирует генерить код там гле используешь, с gomock часто виду что мок сгенеренный для одноко пакета, потом тянут в других, в итоге меняешь интерфейс в 1 месте, а тесты падают по всему проекту)
Лучшее объяснение. Подпись
Николай, благодарю за науку. Этой информации о том, как использовать "кубики" очень не хватает в курсах. В книгах это может быть, но часто это ещё и в голове нужно уложить. А тут инфа от практикующего кодера из первых уст т.с.
Как всегда отличное видео!) Спасибо за труд!
если мы будем рассматривать handler из реальной жизни, который оркестрирует например базой данный, кешем, дергает какие-то third party библиотеки ну и еще много чего он может делать у себя под капотом. обычно таких вот handler-ов может бы очень много если мы говорим о реальном приложении. так вот в таком случае получается будет очень много вот таких вот мелких интерфейсов, плюс многие из них будут/могут пересекаться своими методами. так же будет большая пачка моков делающих практически одно и тоже(их можно не хранить в самом проекте а генерировать только по надобности до запуска тестов). в общем в реальном приложении получается слишком большой оверхед по тому, сколько надо писать и поддерживать. к этому всему мы подключаем упомянутый факт, что иногда нам надо что-то поменять и тут начнется самое интересно даже при условии что IDE будет в этом помогать. в общем достаточно неоднозначный подход с большим количеством минусов на самом деле.
Вы своим сообщением описали минус подхода из других яп с наследованием одного интерфейса как будто, а в конце сказали, что много минусов у гошного подхода. Можете, пожалуйста, дать пояснение?
@@maratfatkulov4993всë верно он описал. Это полное непонимание как работают интерфейсы. Начну по порядку:
1. То что интерфейс разбивается на методы может быть не плохо и соответствует single responsibility. Но это зависит от задачи. Если задача не требует, нет необходимости усложнять. В приведённом примере правильно оставить все методы хранилища в одном интерфейсе, т к они выполняют одну задачу - сохранение объекта для последующего получения. Есть также вариант архитектуры когда сохранение отделено от получения, но опять же что бы это было оправдано, вся архитектура приложения должна это использовать
2. Дак-тайпинг тут вообще не причём. Тем более его и нет в примере - все интерфейсы явно передаются и явно реализуются.
3. Где размещать интерфейсы? Точно не в месте использования, т к таких мест много! В этом и смысл интерфейса, что он формализует контракт, который применяется во многих местах. Подход из видео приведёт к дублированию кода, а в конечном счёте к тому что код перестанет быть унифицированным и станет не читаемым. Интерфейсы надо размещать в отдельном пакете с интерфейсами. Тем более если это граница слоёв приложения.
4. Кэш вместо хранилища... Или по другому, кэш делается декоратором (гуглить паттерн декоратор) На мой взгляд не удачное решение. Лучше делать его паттерном "стратегия". Т е передать внутрь реализации хранилища как зависимость и дать хранилищу им оперировать.
@@maratfatkulov4993 а кому нужен интерфейс действующий в пределах только одного класса или простигосподи сотня одинаковых интерфейсов?
те 1 файл интерфейса это более приемлемо? или как?
Просто говоря про интерфейсы нужно подключать знания чистой архитектуры, ddd и н-лет практики их использования и постоянных экспериментов, в итоге трейдов количества интерфейсов vs 1 файл, начинает склоняться в сторону кучи интерфейсов, особенно если поработал над проектами которым 3+ года, и где успело смениться 2-3 поколения команды)
По мне интерфейсы действительно стоит писать по месту применения, что бы писать чистые юнит-тесты как минимум
Спасибо за видео!
Очень доступно и понятно объснил.
Добрый день, спасибо за видео. На практике столкнулись с тем, что такой подход не удобен в случае, если необходимо вернуть интерфейс.
Например, есть некое хранилище балансов. Баланс реализован сложно на основе event sourcing. Хранилище дальше инъецируется в другие структуры.
В этом случае, если использовать такой подход, то это значительно усложняет тестирование, потому что возвращается именно баланс с конкретной реализацией, который совсем не просто привести в нужное состояние.
Если же сделать хранилище с возвращаемым интерфейсом, то утиная типизация перестаёт работать. Go не может понять, что передаваемый извне интерфейс, пусть и совпадающий по сигнатуре, соответствует локальному интерфейсу баланса.
В итоге, для таких случаев приходится делать один внешний общий интерфейс балансов, который уже используется везде.
Спасибо за такой развёрнутый комментарий. У меня есть подозрение, что описанный тобой подход не очень правильный. Но это хорошая тема для обсуждения - приходи в наш чатик Gopher Club, обсудим (см. ссылку в описании).
Вполне нормальное решение хранить интерфейс в отдельном пакете, если это требуется. Например мы делаем инверсию зависимости логера, там 5 методов. Не плодить же нам миллион однотипных интерфейсов:)
Перемещение интерфейса к месту использования - это инверсия зависимости (soliD). Утиная типизация не убирает эту зависимость, и код репозитория должен соответствовать интерфейсу объявленному в пакете хэндлеров. Количество зависимостей (зацепление, а не связность) не уменьшилось, изменилось только направление зависимости.
Инверсия зависимсоти обычно нужна для создания независимого слоя бизнес-логики. Бизнес-логика задаёт интерфейс взаимодействия с хранилищем, а не наоборот. Это кажется довольно очевидным, но я встречал код, в котором метод репозитория требует передачи коннекта к бд :-) Такое неправильное направление зависимости не позволяет заменить постгрес на мемкэш.
Таким образом, интерфейс надо размещать не просто в месте использования, а там где он позволит задать правильное направление зависимости. Если бы на видео была бы какая-то бизнес-логика (например, ограничение количества созданных пользователей в день), и в доменном слое возник какой-то интерфейс, то переносить этот интерфейс в слой хэндлеров было бы непрактично.
Звучит убедительно)
А приходи в наш чатик, вдруг кто-то захочет подискутировать об этом: t.me/+WyjmnP6la_QyYjAy
А часто приходится менять БД на мем кеш?)
Вопрос проброса коннекта (для транзакций) очень интересный, я видел 3 реализации:
- проброс коннекта напрямую (очевидная и прозрачная работа, недостаток вы описали)
- проброс внутри контекста (неочевидно и непрозрачно)
- при каждом запросе создаётся экземпляр менеджера репозиториев с одним коннектом
Поделитесь, как вы это делаете?
@@JanePilotessa 2-й вариант я бы сразу отбросил, слишком уж неочевидный
Обычно в рабочих проектах коннект передаётся в конструктор, т.е. при создании экземпляра репозитория.
Но в некоторых случаях бывает удобней передавать коннект в виде аргумента метода - например, когда мы хотим объединять в транзакцию несколько вызовов методов репозитория. Такие решения тоже встречаются.
я что-то не могу понять последний абзац. Может кто-нибудь прояснить? В видео доменный слой он где? И зачем интерфейс доменного слоя переносить в слой хэндлеров? И как тут нарушается направление зависимости?
@@h3ckphy246 в видео нет отдельного слоя бизнес-логики, так как пример очень простой. Я как раз отмечаю, что интерфейс доменного слоя не надо переносить куда бы то ни было.
Переносить интерфейс надо не просто так, а с целью создать независимый слой приложения. Независимость упростит изменения кода в этом слое. А поскольку чаще всего меняется слой бизнес-логики, то обычно стараются сделать независимым именно его.
Более подробно об этом можно почитать в очень интересной книжке Чистая архитектура Роберта Мартина.
B как все таки размещать? поднял вопрос но не ответил. Сказал бы: " мужики, я делаю так" или "в компаниях принято так"
название ролика "Почему интерфейсы лучше размещать в месте использования" а сам ролик "который вариант имеет + и -сы"
Спасибо огромное за ваш контент!
Свичнулся в Go с другого языка в котором стандартная номинативная типизация и такой подход конечно кажется конечно не говнокодом, но болью. Тут следует смотреть, что мы чаще делаем с кодом. Есть такая аксиома в программировании - "код чаще читается, чем пишется". С этой позиции конечно с кодом становится работать куда менее удобно, придется 100500 файлов облазить, о чем сказано в видео. Этот недостаток лично для меня нивелирует преимущества от разделения интерфейсов.
Будет видео о gRPC? Не соображу как в проекте добавить клиент для gRPC. Спасибо за труд и стримы.
Будет =)
@@nikolay_tuzov Благодарю, можно потом еще по мокам пройтись?
@@nikolay_tuzov Клиент добавил. Теперь вопрос организации кода и в принципе как это поддерживать если выходит новая версия API.
Это конечно сильно... закрывать часть кода своим лицом. Спрашивается, для чего? Можно было бы окно с лицом сделать в два три раза меньше и этого было бы достаточно, или допустим включать иногда, в важных моментах. Некоторые вообще не выводят лица, что увеличивает объем рабочей области, за что благодарности авторам. Самое лучшее, это показать вначале, и в конце видео, вполне достаточно.
Спасибо самые лучшие видео по golang ❤❤❤🙏🏻 А будут ли видео про многопоточность в golang, ещё бы очень интересно было послушать про профилирование. Спасибо за видео. Процветания каналу
Отличный ролик! Жду с нетерпением ролик про моки
Спасибо за развёрнутый ответ!
Классное объяснение. Пошел внедрять в проекты! Ждем ролик по мокам)
Что выходит. Если в 2 разных местах надо получать пользователя, то будут описаны 2 интерфейса, которые полностью совпадают, но описаны в разных местах ? Таким образом получается некое нарушение dry ?
этот видос лучшая антиреклама языка Го
Я вам скажу что dry в некоторых примерах усложняет код чем его упращает и тут мы не следуем kiss , dry это не панацея и иногда от него больше вреда чем пользы
видео по мокам ! Було б куруто . Дякую за Вашу роботу
Вопрос про файлы, где у нас интерфейс с одним методом и сразу же его реализация.
А в этом случае зачем нам вообще интерфейс? Т.е. почему его не выкинуть (как лишнюю обертку) и не написать сразу обычную функцию под наш конкретный объект?
Ведь плюс интерфейсов прежде всего в том, что мы можем использовать один интерфейс в нескольких местах - это и есть полиморфизм.
А полиморфизм из одной реализации вроде как не совсем полиморфизм, нет)?
вместо структур можно тоже использовать интерфейсы получая значение полей GetID() тогда реально будет отвязка от каких либо пакетов
Круто, жду ещё контент
Спасибо за ваш труд. Жду ролик про моки
Большое спасибо! Очень интересно и полезно. Если есть возможность, покажите два минимальных проекта отличающихся наличием и отсутствием интерфейса. И какие плюсы у того у которого интерфейс есть?
Ух.. Больно будет писать целый проект без интерфейсов, даже минимальный(
Да, о МОК очень хотелось бы узнать! Запиши видео, пожалуйста)
Уже давно записал, ищи на канале)
@@nikolay_tuzov Внимание автора к аудитории очень приятно) Спасибо, что ответил! Даже спустя год от публикации видео.
Хочу сказать, что у тебя очень понятные, классные видео, огромное спасибо за твой труд! Ты очень помогаешь новичкам, твои "ультимативные" видео пересылаем друг другу, как золотой стандарт исчерпывающей информации)) Большое спасибо!
Николай, а что будет, если сигнатура метода Use() в исходном интерфейсе взяла и поменялась? Мало того, что в хендлерах это сразу не узнают, так ещё и исправлять придётся массу вот таких вот дополнительных интерфейсов с тем же методом.
спасибо. Видео про моки было бы полезно.
Пишу комментарий про строчку go:generate :)
Копать сюда: eli.thegreenplace.net/2021/a-comprehensive-guide-to-go-generate/
Как доберутся руки, сделаю об этом отдельный видос
Спасибо за видео. По мокам было бы интересно отдельное видео
Спасибо за видео. Хочется гайд по Mockery.
Perfect !
Я предпочитаю выносить интерфейсы в отдельный модуль и проектировать из так, чтобы они опирались только на интерфейсы и стандартные типы. Это позволяет модулям реализации и использования вообще не знать друг о друге.
Можно уточнить по поводу интерфейса логера? Для него допустимо описать интерфейс не по месту использования?
От данного автора узнаю больше чем от родителей за всю свою жизнь
Благодарю за видео!
А у Вас нет ссылки на какие-то официальные заявления авторитетных людей, чтобы можно было не только дать ссылку на Ваше видео, но и дать ссылку на сообщение в доке, где черным по белому написано - интерфейс должен быть размещен в месте использования.
Очень круто, спасибо! Только не совсем понятно, как это делать, когда я, например пишу grpc сервис, ведь там сигнатуры функции уже фиксированы
Николай, спасибо за видео. Вопрос: я немного не понял, вы сказали, что этот подход уменьшает связность, но в хендлере у вас все равно осталась зависимость от импорта структуры User, или подразумевается, что структуры лежат в независимом пакете, где лежат модели условные?
Да, именно так - в отдельном пакете
@@nikolay_tuzov а это go way? Я уже реально запутался, одну статью читаешь, там говорят, что храните модели прямо в сервисе с бизнес логикой и что package models это плохо, в другой говорят, что храните модели в отдельном пакете от сервиса. А где истина то?
@@АртемРогозин-д5ш истина, как обычно, где-то рядом =) Не пытайся найти какое-то "абсолютное мнение". Выслушивай аргументы всех сторон, взвешивай, сравнивай, делай выводы сам.
@@nikolay_tuzov я не понимаю, но в сервисе/хендлере в любом случае же придется проверять ошибки, которые возвращают storage, т.е в хэндлере условно так придется делать: if err == storage.ErrUserExists. и тогда в любом случае мы импортируем пакет storage
да, хотелось бы поподробнее про generate, плиз
Интерфейс который объявлен рядом с использованием нужно объявлять приватным, так как нет нужды ему быть публичным.
И пример с использованием storage в слое транспорта выглядит не очень, так как это означает что бизнес логика находится либо в слое транспорта, либо в слое базы данных. Бизнес логика в транспорте это плохо, так как при смене транспорта придется логику переносить и в целом это нарушает high cohesion. Бизнес логика в слое базы данных даже хуже, потому что есть риск реализовать логику на языке sql, а при смене базы данных переписывать ее заново
Хорошие замечания. На счет приватности интерфейса точно согласен.
Остальное дискутивно, но звучит тоже справедливо.
Не очень понятно, почему интерфейсы в месте использования сделаны экспортируемыми (публичными)? Полагаю приватный вариант был бы более корректен. Вообще экспортируемый интерфейс - это что-то глобальное, скорее прерогатива sdk, определять экспортируемые интерфейсы для повсеместного использования. Сложно что-то придумать, что будет гарантированно использоваться всеми частями проекта. Даже логгер, по той же логике может быть приватным, и часть объектов использует часть возможностей логирования, которую и описывает в месте использования.
Почему-то изображение и звук немного не совпадают по времени.
А если описывать эти мини интерфейсы как раз в storage, а общий крупный интерфейс создавать путем их композиции?
В месте использования! Хотя бы потому, что реализаций может быть больше 1
Про моки интересно было бы послушать
Как насчет DTO между интерфейсами?
Спасибо автору за проделанную работу. Хотелось бы узнать такой же материал, касательно моделей и структур. Где лучше их размещать? Обычно в моих проектах они лежат в pkg/models, но видел как некоторые размещают их в месте реализации
Привет, на 5:09 повторяется фраза и где-то дальше по видео тоже. Видимо была перезапись и чет недомонтировалось.
Ага, недомонтировалось, проглядел (
Но, вроде, не сильно мешает просмотру
Мы становимся не зависимым от storage.user? Ага. А то что мы отдаём storage.users.User ниче?
Кто читал совершенный код, заметит ещё одно преимущество в таком подходе.
какое?
Мы все равно не отвязались от пакета, т.к. ещё ссылаемся на модель из этого пакета. Как с этим быть?
Наверно dto создавать, хотя мне такая практика не особо нравится
С моделями мы в любом случае будем связаны, и тут уже ничего не поделаешь. Как вариант, можно создать отдельный пакет models, или что-то подобное, чтобы не зависеть конкретно от Storage. Но тут уже нужно исходить из контекста самого проекта.
@@nikolay_tuzov Не мог бы ты привести кейсы когда модели нужно оставлять рядом со стораджем и оставлять зависимость хэндлера от стораджа?
Классный подход, правда при нем получается, что вместо одной структуры Handler с условными методами CreateUser, GetUser, DeleteUser, UpdateUser, которая принимает один интерфейс Storage со всеми методами, нам придется иметь много структур-хендлеров под каждый метод, то есть CreateUserHandler, GetUserHandler, итд. И соответственно весь этот огромный список инициализировать в main. А когда таких хендлеров десятки или сотни, не начинает ли это выглядеть слишком монструзным, или даже в таком случае продолжают использовать такой подход?
В main ты инициализируешь лишь одну структуру - Storage, и он один реализует все частные интерфейсы всех хендлеров. Дублируется лишь описание интерфейса.
Было время когда я изучал Python, и тогда я нашел просто замечательный канал с подробными объяснениями. Долго хотел найти что-то похожее для Go и наконец нашел) ❤
камера закривает код, можно слева внизу разместить
Учту, спасибо
Не знаю, мож короткий видос сделаешь как делаются(пишутся) разные движки для игр, чтобы вот объединялись в итоге анимация модели движения, ну в чём некая суть пояснить
А насколько бесплатны интерфейсы в гошке?
Не бесплатны - как в плане памяти, так и в плане быстродействия. А насколько, тут уже нужно разбирать конкретные случаи.
Можете глянуть этот мой ролик, например: th-cam.com/video/-cX0CqG6rgA/w-d-xo.html
Он шуточный, конечно! Но замеры в части про интерфейсы вполне честные. Шуточные там только выводы, которые я из этого делаю.
@@nikolay_tuzov Ага, спасибо за пример. А насчет видео по ссылке... Вот вы там шутили, а я сейчас работаю с людьми, которые именно так и думают на полном серьезе и переубедить их невозможно. Хорошо только одно - они работают в соседней команде.
@@nikolay_tuzov Спасибо за тест, только вот ссылки на его код я под видео не нашел. А насчет самого ролика... вы вот там шутите, а я работаю с людьми которые именно так и пишут код. Хорошо только одно, что они работают в соседней команде.
@@ViktorPolyakov15 бывает, я тоже с такими сталкивался =)
Бро, попробуй New UI от Jetbrains
Я пока морально не готов к этому))
Конечно в месте использования. Описывать интерфейсы в месте реализации - это где? Если структуры их реализуют неявно - то как понять где у нас место реализации? Сама задача невыполнима, не говоря уже о ее нелогичности.
Видео по мокам +1
Размещение интерфейсов в месте использования является правильным подходом и в c++ и в других подобных языках. Никакой специфики go в этом нет. Если для кого-то это кажется странным, возникает большой вопрос к его профессионализму
Спасибо!
Видос досмотрел, так как видел анотацию в видео про url-shortnere, а в чём проблема чтобы создать не целый объект Users, а тот же ЮзерКреайте, и не нужно будет создавать все методы, хотя странно раз ты меняешь хранилище, то по идеи нужно переписывать всё, а если лишь просто часть заменить, не большие куски, то можно их ведь вынести
Пишу комментарий про строчку go:generate :)
Предыдущему товарищу ответил, продублирую))
Копать сюда: eli.thegreenplace.net/2021/a-comprehensive-guide-to-go-generate/
Как доберутся руки, сделаю об этом отдельный видос
@@nikolay_tuzov спасибо