Третий урок курса по Go для начинающих: основы Git, проблемы сохранения кода и введение в backend технологии.
Key Takeaways
- Git — ключевой инструмент для контроля версий и сохранения кода в разработке.
- Удалённые репозитории (GitHub, GitLab, Bitbucket) обеспечивают надежное хранение и совместную работу.
- Понимание проблем сохранения кода помогает избежать потери данных и улучшить рабочие процессы.
- Приватное IT-сообщество предоставляет дополнительные материалы и поддержку для эффективного обучения.
- Курс ориентирован на подготовку к реальным коммерческим проектам и успешному трудоустройству.
What the video covers
- Курс предназначен для начинающих, желающих освоить навыки для коммерческих проектов и трудоустройства.
- Рассматриваются важные механизмы операционных систем и популярные backend технологии.
- Вводится понятие систем контроля версий, с акцентом на Git как наиболее популярный инструмент.
- Объясняются проблемы сохранения кода и преимущества использования удалённых репозиториев, таких как GitHub.
- Рассматриваются основные принципы работы с Git и его роль в командной разработке.
- Подчеркивается важность приватного IT-сообщества для дополнительного обучения и поддержки.
- Дается обзор архитектуры GenG приложений и других backend инструментов.
- Обсуждаются темы, которые требуют отдельного изучения, такие как базы данных, Docker и Git.
- Приводятся рекомендации по подготовке к собеседованиям и выполнению домашних заданий.
- Видео содержит практические советы по работе с Git и организации кода для надежного хранения.
Chapters
- 00:00 Введение и цели курса
- 10:00 Проблемы сохранения кода и введение в Git
- 47:04 Обзор систем контроля версий и выбор GitHub
- 90:04 Работа с ветками и коммитами в Git
- 120:07 Основы работы с базами данных в backend
- 177:44 Принципы построения архитектуры GenG приложений
- 219:20 Использование Docker и контейнеризация
- 275:13 Подготовка к собеседованиям и домашние задания
Full Transcript — Download SRT & Markdown
Speaker A
Всем привет. Добро пожаловать на третью часть полного курса ПО для начинающих. Цель этой серии уроков — дать все необходимые знания и навыки для работы над настоящими коммерческими проектами.
Speaker A
После освоения всех частей этого курса можно смело идти делать резюме и, подготовившись к собеседованиям, искать работу. Поэтому советую прямо сейчас, не откладывая, подписаться на мои YouTube и Telegram-каналы для того, чтобы одним из первых узнавать о дропе нового обучающего материала и качественного
Speaker A
контента. В рамках этой части мы с вами рассмотрим некоторые важные механизмы в работе операционных систем, познакомимся с популярными энд-технологиями, а в конце разберём принципы построения архитектуры GenG приложений. Некоторые затронутые в этой части курса темы, такие как, например, базы данных, Git
Speaker A
или Docker, будут слишком обширны для того, чтобы рассмотреть их полностью в этом видео. Поэтому в нашем приватном сообществе для таких обширных тем я вынес дополнительные материалы для изучения и качественные интерактивные тренажёры, при помощи которых вы сможете более основательно изучить пройденный
Speaker A
материал. Плюс для каждой рассматриваемой в этом курсе темы в нашем приватном сообществе есть домашние задания и закрепляющие учебные проекты, которые являются очень важной составляющей в освоении навыка программирования и, как следствие, успешного трудоустройства. Поэтому крайне советую присоединяться к нам в
Speaker A
IT-сообщество, ведь помимо дополнительных материалов и домашних заданий у нас есть огромное количество записей настоящих собеседований, в том числе на разработчика. Но, конечно, самая большая ценность сообщества заключается в людях, в участниках этого сообщества. Ведь только благодаря участникам сообщества каждый может
Speaker A
рассчитывать на исчерпывающие ответы и помощь в любых ситуациях. В итоге, благодаря просмотру этого курса, решению домашних заданий, написанию учебных проектов, тренировке на интерактивных тренажёрах и подготовке к прохождению собеседований, вы как нельзя лучше приближаете момент своего трудоустройства на позицию разработчика.
Speaker A
Ссылку на вступление в приватное IT-сообщество я оставлю в описании к этому видео. Ну а теперь всем приятного просмотра.
Speaker A
Первое, о чём мы с вами поговорим в рамках третьей части курса Погошки, — это системы контроля версий. В мире существует несколько различных систем контроля версий, но практически все разработчики на нашей планете Земля используют систему контроля версий Git.
Speaker A
Совсем скоро мы с вами поговорим о том, что именно означает система контроля версий, какие проблемы решают системы контроля версий, для чего их вообще используют разработчики. Но пока нам главное понять, что этих систем контроля версий существует некоторое множество.
Speaker A
Они все отличаются по своему принципу, но практически все разработчики, будь то бэкенд-разработчики, будь то фронт-разработчики, будь то мобильные разработчики, будь то вообще не разработчики, а, например, тестировщики или девопсы — это всё различные IT-профессии, практически все эти люди, за некоторым исключением, используют
Speaker A
именно Git в качестве системы контроля версий, потому что Git наиболее легковесный, наиболее удобный и наиболее простой в использовании. Ну и, как обычно, я думаю, многие из вас уже могли заметить, как выстроено наше обучение: мы в большинстве случаев сначала
Speaker A
разбираем то, какую проблему или какие проблемы существуют в нашей разработке. А потом мы понимаем, как именно тот инструмент, в нашем случае это Git, который мы разбираем, помогает нам эту проблему решать и упрощает нашу жизнь, делает нашу разработку быстрее, лучше и
Speaker A
более качественной. Ну, не будем отходить от традиций, и перед тем, как мы будем учиться пользоваться Git, мы рассмотрим те проблемы, которые Git позволяет нам решать. Проблема первая — это проблема сохранения кода. Что это значит? Представьте, вот у вас есть ваш
Speaker A
компьютер, в вашем компьютере есть жёсткий диск, и на этом жёстком диске хранятся исходные файлы вашего проекта.
Speaker A
Представьте, что вы пишете какой-то большой проект, пишете его длительное время, там недели, месяца, может быть, даже года. Вы на этом проекте уже начали зарабатывать деньги, вы этот проект уже продаёте каким-то заказчикам или у этого проекта есть конечные потребители? Ну,
Speaker A
допустим, у вас какая-то суперкрутая, там анонимная инновационная социальная сеть. И все исходные коды вашего проекта хранятся у вас на жёстком диске вашего компьютера. А чем это чревато? Ну, как минимум, в один день вы можете попробовать включить ваш
Speaker A
компьютер, и окажется, что ваш жёсткий диск вышел из строя. И получается всё. Ваш проект, который вы писали, может быть, несколько лет, просто берёт вот так в один день и исчезает. И всё. И вы теряете все ваши исходные коды. Вы
Speaker A
теряете все ваши наработки, потому что вы хранили свой проект просто на жёстком диске у себя локально на вашем компьютере. Соответственно, мы видим проблему. Проблема в том, что код, который мы пишем, если он начинает представлять какую-то большую ценность, мы боимся его потерять. Мало того, что
Speaker A
мы боимся, это действительно может произойти, да, как минимум вы можете взять свой ноутбук куда-нибудь в кафешку, и там могут его украсть. Ну и что делать? И как мне вот этот вот мой проект, в который я очень много сил
Speaker A
и времени вложил, как мне его сохранить и не бояться потерять просто из-за того, что у меня там сломался жёсткий диск или потому, что у меня сломался или потерялся компьютер? Ну, на самом деле, практически все решения, которые могли прийти вам в голову, они сводятся к
Speaker A
тому, что нужно взять вот эту вот папочку с моим проектом и куда-то ещё сохранить, куда-то сохранить помимо жёсткого диска компьютера. Ну, например, скинуть себе в личку в какой-нибудь социальной сети, да, сделать архив моего проекта и скинуть самому себе в личные
Speaker A
сообщения, либо просто взять вот этот вот мой проект и скинуть его себе куда-нибудь на облачный диск. Ну, например, Яндекс.Диск или Google Диск.
Speaker A
Да, в принципе, если мы рассматриваем только лишь проблему сохранения кода, то, конечно, такие варианты подходят. В том числе есть вариант загрузки своего проекта куда-то в удалённое хранилище при помощи как раз-таки Git. Есть разные компании, которые предоставляют свои удалённые хранилища, куда мы можем
Speaker A
загружать свой проект. Ну, к примеру, это может быть GitLab, это может быть GitHub, это может быть Bitbucket, либо какие-нибудь другие менее популярные облачные хранилища для исходного кода.
Speaker A
Все эти системы работают поверх Git. Мы пока ещё не знаем, что из себя вообще представляет Git, но уже можем абстрактно понять, что GitLab, GitHub, Bitbucket и так далее, они все работают поверх системы контроля версий Git. И по
Speaker A
сути, что GitLab, что GitHub, что Bitbucket предоставляют примерно одинаковую функциональность своим пользователям. Поэтому мы тут вольны выбирать, что мы будем использовать: GitLab, GitHub или Bitbucket. Ну или, может быть, опять же, что-нибудь ещё.
Speaker A
Новичкам я всегда советую GitHub, потому что он просто самый популярный, он бесплатный и наиболее понятный. Хорошо, выбрали GitHub. Значит, GitLab и Bitbucket я убираю с нашей схемы. Вот у нас остаётся GitHub. Значит, что мы можем сделать при помощи нашего GitHub?
Speaker A
Как этот GitHub вообще поможет решить проблему сохранения кода? Да, очень просто. Мы можем взять, зарегистрировать себе аккаунт на GitHub. Это абсолютно бесплатно. И при помощи этого аккаунта мы можем взять и свой проект с локального жёсткого диска отправить в
Speaker A
удалённое хранилище на GitHub. Таким образом, мы перекладываем ответственность за сохранение исходников нашего проекта
Speaker A
себя на компанию GitHub.
Speaker A
Плохо это или хорошо, решайте сами. Но в 99,999% случаев это будет гораздо более надёжно, чем если вы сами попытаетесь что-то там придумать с каким-то там облачным диском, что-то там у себя на Яндекс.Диске попытаться сохранить. И как мы впоследствии с вами выясним, какие-то
Speaker A
свои кастомные решения в виде того, чтобы скинуть себе архив с проектом в личку или скинуть архив с проектом на какой-нибудь Google Диск, это всё оказывается очень неудобно в сравн...
Speaker A
просто. У нас есть наш проект, который хранится у нас на локальном жёстком диске, который внутри нашего компьютера.
Speaker A
Мы берём этот свой проект и загружаем его в облачное хранилище GitHub. Всё таким образом, даже если что-то случится с нашим жёстким диском или что-то случится вообще со всем нашим компьютером или мы даже просто, условно говоря, приехали к бабушке в деревню, а
Speaker A
там у нас уже другой компьютер. Во всех вышеперечисленных случаях мы просто берём и уже с облачного хранилища обратно копируем наш проект себе на компьютер. Таким образом, мы обеспечиваем сохранность этого кода, независимо от того, на каком устройстве мы с вами программируем, и независимо от
Speaker A
того, что с нашим устройством, не дай бог, может случиться. Таким образом, если мы ведём с вами какой-то проект длительное время, проект представляет большую ценность, и мы не хотим потерять все свои наработки просто из-за какой-то глупости, что у нас там что-то сломалось
Speaker A
или потерялось, мы начинаем использовать GitHub. Просто загружаем в это облачное хранилище свой проект. И после этого мы можем быть спокойны за то, что наш код уж точно никуда не потеряется. Ну каким образом вы всё-таки можете потерять доступ к вашему проекту, если вы даже
Speaker A
загрузили его в облачное хранилище GitHub, так это если вы потеряете доступ к вашему GitHub аккаунту, ну забудете логин, пароль или что-нибудь в этом роде. Но это в любом случае гораздо более надёжно и гораздо больше гарантий нам даёт по сохранению нашего проекта,
Speaker A
нежели просто хранить его на каком-то одном устройстве. у себя на локальном жёстком диске. Ну хорошо, в этом разобрались. То есть первую проблему, проблема сохранения кода нам помогает решать Git и, в частности облачные хранилища, которые на этом гите основаны. Ну конкретно в нашем случае мы
Speaker A
будем использовать GitHub просто потому что он бесплатный и самый популярный. Стоит уточнить, что далеко не всегда в коммерческой разработке, в разработке, где участвует большая компания, большая команда, множество людей, далеко не всегда используется именно GitHub.
Speaker A
Иногда используют GitLab, иногда используют Bitбакеet, иногда используют что-нибудь ещё. Но по своей сути эти инструменты, эти облачные хранилища кода оченьочень-очень похожи, и все ключевые операции в них одинаковые. Поэтому, если вы разберётесь с Гитхабом и научитесь им пользоваться, то никакой проблемы в
Speaker A
будущем разобраться с Гитлабом или разобраться с битбакетом у вас не будет. Далее мы переходим ко второй проблеме, которую нам помогает решать инструмент Git. Это проблема версионирования кода.
Speaker A
Ну давайте рассмотрим следующий сценарий, чтобы понять, что же это такая за проблема версионирования кода, в чём она заключается вообще. Вот представим, вы начали разрабатывать какой-то проект, вы создали себе пустую папку для того, чтобы начать в этой пустой папке писать
Speaker A
код проекта. Ну, собственно, начинаете писать код в этой пустой папке. Пишите, пишите, это тернистый путь, у вас что-то не получается, вы какие-то файлы удаляете, потом продолжаете дальше писать свой код, какие-то файлы меняете, меняете там архитектуру вашего приложения, ещё что-то. Дальше пишите,
Speaker A
пишите код, и в один момент вы приходите к какой-то логически завершённой версии. Ну, к примеру, у вас есть какой-то заказчик на фрилансе, и он вам выдвинул какое-то техническое задание, и вы по этому техническому заданию прошлись и в итоге сделали какую-то версию вашего
Speaker A
приложения, которое вроде как удовлетворяет этому техническому заданию. Вы думаете: "Ну, хорошо, вот мой проект была пустая папка, да? Теперь вот у меня папка с моим проектом". Вот этот вот весь проект у нас лежит в нашей папке. Всё хорошо. Мы там берём, да,
Speaker A
например, или архивом просто скидываем код нашему заказчику, или через облачное хранилище GitHub мы это делаем. В общем, заказчик берёт вот этот вот наш код, смотрит его у себя на компьютере и понимает, что в целом хорошо. Но я вот
Speaker A
хочу добавить ещё вот такие-то фичи, вот такую-то функциональность хочу добавить. Можешь добавить? И мы думаем: "Ну, конечно, могу. Я же разработчик, почему бы не добавить?" Ну, хорошо, мы опять возвращаемся к нашей папке с проектом, открываем её. То есть вот в этой папке
Speaker A
лежит наш проект. И начинаем писать свой код дальше. То есть мы берём, смотрим, что там в нашем коде не так, чего же в нашем коде не хватает для того, чтобы новые фичи, которые заказчик у нас потребовал, мы могли реализовать. И
Speaker A
начинаем писать код. Пишем код, пишем код. В каких-то моментах мы понимаем, что старые созданные файлы, они лишние, нужно другие файлы создать. То есть мы удаляем какие-то файлы, создаём какие-то файлы, опять там переделываем архитектуру нашего приложения, например, и мы не успеваем дойти до новой версии
Speaker A
нашего приложения, которое уже удовлетворяло бы новым требованиям нашего заказчика. И понимаем, что что-то мы в кодели много какой-то фигни, каких-то много файлов ненужных посоздавали, нужные файлы мы вообще поудаляли. И как-то совсем мы закопались уже в этом коде, и хочется просто взять
Speaker A
и вернуться к нашей вот этой вот изначальной версии, с которой мы начали дорабатывать наш код. Ну хорошо, мы нажимаем Ctrl Z. Мы там какие-то с вами изменения в коде всё-таки можем удалить, но Ctrl Z имеет ограниченное количество изменений, которые он может сохранить в
Speaker A
памяти. То есть Ctrl Z, он там сохраняет какое-то количество изменений, но не всё. А у нас с вами множество файлов изменено. У нас с вами множество файлов удалено. Множество файлов создано. И в итоге мы понимаем, что мы превратили наш
Speaker A
проект из версии вроде норм. Версия вроде норм, то есть когда заказчика она вроде устраивала в непонятное месиво, в непонятном состоянии, где какие-то файлы созданы, какие-то файлы удалены, какие-то файлы изменены, так что мы уже не можем их обратно откатить. И что нам
Speaker A
делать? Ну, правильно писать заказчику: "Всё, я не справился. Вот, забирайте свои деньги. Я превратил ваш проект в непонятно что". Но ведь это не дело. Ну хорошо, допустим, вы всё-таки там смогли где-то у себя найти какую-то копию этой папки, которую вы когда-то там
Speaker A
создавали, и вы опять начали из неё делать, собственно, ваши изменения в коде. И, допустим, вы там даже действительно до чего-то дошли, до какой-то ещё там второй версии. Вроде норм. Вот это будет вроде норм 2. Ну, хорошо. А тут приходит заказчик и опять
Speaker A
требует что-то поменять. И опять вам нужно что-то придумывать, как-то куда-то сохранять вот эту вот папку с кодом, называть там проект один, проект 2, проект 3, потом вы начинаете в этих папках путаться. А потом представьте ещё более интересный сценарий. Вы сделали
Speaker A
вот эту вот версию вроде Norm 2, потом сделали вроде Norm 3, к ней стрелочка пускай идёт, а потом к вам вот на вот этой вот версии, вроде Norm 3 приходит заказчик и говорит: "Слушай, брат, вот эти вот версии вроде Norm 2, вроде Norm
Speaker A
3, а точнее функциональность, которую мы сделали в этих версиях, мы протестировали на своих пользователях и поняли, что это какая-то шляпа, что денег нам эти фичинот, пользователям они не нравятся. Давай, пожалуйста, откатим наше приложение до версии вот этой вот
Speaker A
вроде NORM 1. А вы такие: "А, подождите, а вот эту папку я уже давно удалил. Типа я её делал 3 месяца назад, я тут уже давно всё удалил. И вроде Norm 2 я тоже удалил, диск себе как бы почистил. У
Speaker A
меня только версия вроде Norm 3 осталась". И всё, эти старые версии кода опять же потеряны, к ним уже не вернутся. То есть мало того, что во время самой разработки у нас огромные неудобство составляет то, что нам нужно куда-то вот наши старые версии кода в
Speaker A
отдельные какие-то папки сохранять, так ещё у нас проблема, что не дай бог, если какая-то из этих папок у нас удалится или потеряется, мы уже никогда не сможем вернуться к нашей старой версии кода, если понадобится, либо просто откатиться на предыдущую версию. А для чего нам
Speaker A
может быть нужно вот такой вот очень частый сценарий, когда нам нужно откатиться ровно на одну версию назад.
Speaker A
Это бывает, когда, допустим, у нас на продовых серверах, то есть на серверах, где крутится наше приложение и им пользуются уже настоящие люди, у нас крутится, к примеру, вот эта вот версия 2. Потом мы сделали какую-то новую фичу и выкатили версию 3. Но мы увидели, что
Speaker A
у нас версия 3 сломала наше приложение. Сайт перестал открываться, пользователи перестали получать услуги, которые они хотели получать на нашем сайте. В итоге компания начинает нести потери. И чтобы долго не думать, что же там у нас вообще изменилось, что же именно у нас сломало
Speaker A
всё-таки на третьей версии наше приложение, мы просто берём и откатываемся на одну версию назад с третьей версии на вторую. То есть такую функциональность тоже нам хотелось бы иметь, да? То есть мы выкатили третью версию, она у нас всё сломала вообще. Мы
Speaker A
откатились на вторую версию, и на второй версии всё работает. А пока вторая версия работает, мы как бы аккуратненько, в спокойном темпе разбираемся, а что же третья версия у нас сломала, почему на третьей версии всё сломалось. Соответственно, все эти
Speaker A
обсуждения нас приводят к тому, что мы бы хотели каким-то образом уметь сохранять какие-то версии нашего приложения, то есть фиксировать исходный код нашего приложения в какой-то версии.
Speaker A
Пускай вот это вот будет у нас версия оди, вот эта версия один, вот эта версия 2, вот эта вот версия три. То есть мы хотим уметь каким-то образом фиксировать версии нашего приложения, чтобы при необходимости мы могли быстро и без
Speaker A
головной боли откатываться на предыдущей версии, либо, наоборот, переключаться на следующие версии. Это и называется версионирование нашего приложения. То есть, когда мы из пустой папки писали, писали какой-то код, потом в какой-то момент мы поняли, что вроде как написанный код, он удовлетворяет
Speaker A
требованиям. То есть, а, требования - это либо заказчик выдвинул вам на фрилансе, либо на работе работодатель дал задачу какую-то конкретную, что нужно сделать, либо вы, например, решаете домашнее задание там в нашем приватном сообществе. То есть есть какие-то конкретные требования, которые
Speaker A
вы вроде как выполнили, и вы хотите зафиксировать эту версию приложения, чтобы даже если вы в будущем начнёте как-то это приложение менять и вдруг сломаете себе вообще всю программу, вы могли легко вот из этой вот непонятной версии, в которую вы пришли, вы могли
Speaker A
легко всегда откатиться до зафиксированных изменений. Либо, опять же, если вы уже там следующей версии вашего приложения будете выкатывать, то, чтобы вы всегда могли, если что, если что-то вдруг сломается, откатиться на предыдущую версию. Версионирование приложений - это очень важно, даже если
Speaker A
вы разрабатываете приложение один. А теперь представьте, насколько это важно, если вы разрабатываете приложение в большой команде. Ну и, соответственно, Git, в частности GitHub, позволяет нам решить эту проблему версионирования кода. Как это выглядит? Вот мы создаём пустую папку нашего проекта или какой-то
Speaker A
наш коллега когда-то давно, там несколько лет назад создал эту пустую папку и начал писать код. Вот он пишет, пишет, пишет, и тут доходит до какой-то версии вроде норм, типа вроде работает, вроде там сайт запускается, вроде всё работает. Он берёт или мы берём, если мы
Speaker A
пишем код, и фиксируем вот эту вот версию нашего приложения, после чего мы её отправляем на удалённый сервер. Вот наша версия приложения один. Она сохранилась в облачном хранилище GitHub.
Speaker A
Хорошо. Потом мы берём эту первую версию и с неё начинаем дальнейшую разработку. Дальше разрабатываем, разрабатываем, разрабатываем. Доразбатывали до второй версии. Протестировали, всё хорошо.
Speaker A
Берём и эту вторую версию тоже сохраняем в облачном хранилище GitHub. Вот. Сохраняем версия 2. После этого мы берём вот эту версию 2 и начинаем дальнейшую разработку, там, внесения новой функциональности с этой версией 2.
Speaker A
Работаем, работаем, получаем версию три. Что делаем? Правильно сохраняем наше приложение вот в состоянии версии 3. То есть мы фиксируем приложение в этом состоянии и запускаем на удалённый сервер. Таким образом, у нас в этом облачном хранилище исходного кода GitHub
Speaker A
хранится не только наш проект, но и ещё различные его версии. И мы при желании и необходимости можем выбирать, с какой версией нам работать. можем откатывать какие-то неудачные изменения нашего кода до любой версии, которая у нас вообще сохранена на этом удалённом сервере. Ну
Speaker A
и при желании мы можем между этими версиями как угодно переключаться. Соответственно, проблема версионирования кода заключается в том, что у нас нету, собственно, версий нашего кода. К каким проблемам это нас ведёт, мы с вами уже обсудили. Первое - это то, что мы из
Speaker A
какой-то там версии нашего приложения, если захотим внести изменения, мы можем просто потеряться в этих изменениях и уже обратно мы вернуться не сможем.
Speaker A
Просто потому что Ctrl Z уже не работает. А какие файлы мы создали, поменяли или удалили, мы уже с вами не помним. Это первое, в чём заключается проблема версионирования кода. Второе, в чём заключается проблема версионирования кода, в том, что мы при необходимости
Speaker A
точно также не можем резко быстро вернуться на предыдущую версию. Просто потому что опять же нету никакой предыдущей версии. У нас есть только одна папка с нашим проектом, и она содержит в себе только самые последние изменения. Чем это чревато? Тем, что
Speaker A
если мы выкатываем на продовый сервер эту версию, а она нам ломает всё приложение, то обратно нам уже некуда откатываться, потому что у нас версий-то нету. Мы эти версии с вами никак не фиксировали. У нас только самая последняя версия, которая в нашей папке
Speaker A
лежит. Вот это, собственно, и есть проблемы версионирования кода. Код мы должны версионировать для того, чтобы этих проблем избегать и для того, чтобы не бояться как-то испортить наш проект, не бояться, что у нас новая версия может что-то там сломать, потому что мы в
Speaker A
обоих случаях всегда сможем спокойно откатиться на предыдущую стабильную версию. Плюс наличие различных версий нашего кода сильно облегчает командную разработку, но это мы уже с вами обсудим дальше. И третья проблема, которую решает гиid - это проблема синхронизации командной разработки. Что это значит?
Speaker A
Допустим, у вас есть какой-то общий проект, который вы разрабатываете совместно с несколькими своими коллегами. Ну, в данном случае, допустим, вот вы, вот ваш первый коллега и вот ваш второй коллега. Тут приходит ваш начальник и даёт вам и каждому из
Speaker A
ваших коллег какое-то задание по этому проекту. То есть вы работаете с кодом этого проекта, ваш первый коллега работает с кодом этого проекта, и ваш второй коллега работает с кодом этого проекта. Что это значит? Это значит, что код этого проекта должен быть
Speaker A
представлен локально на машине у каждого из вас для того, чтобы вы могли вносить туда какие-то изменения. Ну, соответственно, запускать ваш код и его тестировать, кидать там в него запросы из постмана, проверять, как это работает, перед тем, как деплоить,
Speaker A
собственно, в продакшсервер. Хорошо, вот у вас есть у каждого копия этого проекта, вы там вносите какие-то изменения, а потом приходит момент X, когда вам нужно все эти изменения слить как-то в одну кучу и потом задеплоить на продакшн-сервере. И в этот момент вы
Speaker A
сталкиваетесь с большой проблемой, потому что ваш проект, который у вас именно локально сохранён на компьютере, пускай он называется версия 011, у вашего первого коллеги это будет версия 012, и у вашего второго коллеги- это будет версия 013.
Speaker A
Соответственно, из-за того, что вы, ваш первый коллега и ваш второй коллега, вы все одновременно параллельно разрабатывали свои части кода, которые вам нужно было разработать исходя из задания. У вас получилось, пусть и хранимые локально на ваших компьютерах, но три различные версии исходного
Speaker A
проекта. как вам объединить эти три различные версии в какую-то одну полноценную версию, которую потом уже можно будет выкатывать для конечных пользователей? А тут огромная проблема, потому что действительно сделать это никак нельзя удобно. Как бы мы с вами вот могли выйти из этой ситуации? Ну, вы
Speaker A
бы могли в какой-то социальной сети, например, через Telegram написать вашему коллеге и сказать: "Вот смотри, я сделал свою версию. Держи ZI архив как бы с моей версией. Ваш коллега примет эту версию архива. Потом он должен будет одновременно открыть две версии одного
Speaker A
проекта, свою версию и вашу версию, и всматриваться, какие же файлы вы поменяли, чтобы из этой вот версии перенести их в свою версию. Смотрите, какие же файлы вы добавили, какие же файлы вы удалили, чтобы как-то синхронизировать вот эти вот две
Speaker A
различные версии. А мы не берём во внимание ещё второго коллегу, у которого тоже своя какая-то версия, и он тоже может, например, вот коллега 2 напишет коллеге один через Telegram, тоже вот смотри Zipрхив с моей версией. Можешь их синхронизировать, пожалуйста. И коллега
Speaker A
один просто изо всех мест, где у него ещё остались волосы, он их будет оттуда выщипывать. Просто потому что он не будет понимать, а как мне вот эти вот различные версии проекта слить в одну версию проекта. Пускай она называется
Speaker A
вот ноль. Д это очень сложная задача, особенно если проект, в котором несколько десятков или даже сотен файлов. В каждом файле может быть 100, 200-300 строк кода. То есть в сумме там несколько тысяч строк кода. Вы представляете, нужно каждый файл в
Speaker A
проекте глазами отсмотреть, чтобы понять, где там какие изменения от вас, то есть от версии 01.1, где там какие изменения от коллеги 2, то есть версия 01.3, а где там какие изменения от самого себя? И это всё нужно руками
Speaker A
отсмотреть и в итоге родить какую-то версию, вот эту вот 0,2, которая в итоге в себе будет совмещать все вот эти вот три изменения. То есть изменение версии 01.1, изменение версии 01.3 и изменение версии 01.2. И вот это вот будет
Speaker A
результирующая версия. А теперь давайте представим, насколько огромна эта проблема, если у нас IT-отдел состоит не из трёх разработчиков, а из нескольких сотен разработчиков. Получается, синхронизировать вот эту вот версию проекта, когда параллельно сразу куча программистов работают над одним и тем
Speaker A
же кодом, становится практически нереально. Это не посильная задача, если мы пытаемся её решить без помощи каких-то специализированных инструментов. И как раз-таки гит - это и есть такой специализированный инструмент, который помогает нам решить, в том числе, эту третью проблему,
Speaker A
проблему синхронизации командной разработки. Давайте же рассмотрим, как именно Git нам позволяет эту проблему решать. Проблема синхронизации командной разработки в ГИТЕ решается преимущественно при помощи веток и их слияний. Сейчас мы с вами обсудим, что же это такое ветки и что же такое
Speaker A
слияние веток. Вот эту надпись я пока уберу сюда и пока рассмотрим следующую ситуацию. У нас есть IT-отдел, состоящий из трёх человек. Это вы, это ваш первый коллега и это ваш второй коллега. Также у вас есть облачное хранилище GitHub, на
Speaker A
котором вы храните свой проект. Значит, проект у вас там хранится в версии 0.1, то есть какая-то самая-самая начальная версия вашего проекта. И тут вам, вашему первому коллеге, вашему второму коллеге, начальство принесло задачи, которые подразумевают работу с этим проектом.
Speaker A
Значит, что вы будете первым делом делать? Первым делом вы должны взять самую актуальную версию проекта с облачного хранилища. Ну, соответственно, вы это и делаете. То есть клонируете себе локально с облачного хранилища эту версию проекта. Ну, только тут вот
Speaker A
надпись проект я уберу, чтобы не мешалось. Собственно, вы скопировали, ваш коллега скопировал, и ваш второй коллега тоже это взял и скопировал. У вас у всех теперь локально на компьютере копия вот этой вот версии проекта 0.1.
Speaker A
Что значит ветка Mainmaster? Вообще в Gitрепозиториях, а если вы какой-то код храните, например, в Гитхабе, то ваш проект автоматически становится репозиторием. По сути, своей репозиторий - это просто какой-то проект, который находится в облачном хранилище, таком как GitHub. Когда вы свой проект
Speaker A
загружаете на GitHub, он по умолчанию становится репозиторием. И в этом самом репозитории есть такое понятие, как ветки. Значит, что же такое ветки? На самом деле ветка - это такое понятие, которое довольно сложно осознать, абстрактно рассуждая. Гораздо проще рассмотреть сразу же на конкретном
Speaker A
примере. Давайте изначально мы просто смиримся с тем фактом, что у проекта всегда есть ветка под названием main, ну или мастер. Это зависит от того, как её назовёт человек, который ваш проект, собственно, и создал. Ветка может называться либо main, либо мастер. И по
Speaker A
своей сути это одно и то же, то есть главная ветка. Опять же, сейчас мы поймём, что такое ветки. Пока просто смиряемся с тем, что это понятие есть. И у нас в проекте всегда будет какая-нибудь главная ветка. Хорошо. Ну,
Speaker A
ветка и ветка. Ну, и что дальше? Ну, продолжаем рассматривать. Значит, вы и ваши коллеги склонировали себе этот удалённый репозиторий на локальный компьютер и начинают в этих самых локальных копиях разрабатывать свои задачи. То есть та задача, которая была дана вам, вы её разрабатываете в своей
Speaker A
локальной копии. Та задача, которая была дана вашему коллеге, он её разрабатывает в своей локальной копии. Ну и другому коллеге, которая задача была дана, он точно также уже в своей локальной копии разрабатывает эту самую задачу. Но, как мы видим, несмотря на то, что мы
Speaker A
разрабатываем эту задачу у себя локально на компьютере, всё равно, так как мы склонировали проект и больше ничего не сделали, мы продолжаем находиться, пусть и локально, но на ветке мате, ну или же на ветке main. Что это значит? Это
Speaker A
значит, что если мы вдруг внесли какие-то изменения в наш код, и после этого мы захотим загрузить эти самые изменения на удалённый сервер, чтобы их не потерять, то мы сразу же автоматически зальём все эти изменения в мастерку. Мастерка, с которой работают
Speaker A
абсолютно все наши коллеги. А что это значит? Это значит, что если мы, не дай бог, какие-то не до конца законченные изменения захотим сохранить мастер ветки, мы сломаем весь наш проект, потому что мы залили в мастер ветку непонятно что. Как решается эта
Speaker A
проблема? Эта проблема решается тем, что мы, локально разрабатывая свою задачу, мы разработку ведём не в мастер ветке, а в своей ветке. То есть мы берём и от мастер ветки мы создаём свою ветку и называем её, к примеру, я вот её назову
Speaker A
фичер, что означает фича 1. Это наша ветка. Что это значит? Это значит, что если мы тут будем производить какие-то изменения и потом захотим залить эти самые изменения на удалённый сервер, то эти изменения зальются на удалённый сервер в ветку 1. Что это значит? Это
Speaker A
значит, что мы можем ввести разработку в рамках своей ветки, сохранять эти изменения на удалённом сервере. И при этом мы не будем ломать мастер ветку, на которой базируются вообще все наши коллеги. Ага, хорошо. А как же тогда устроена вообще разработка, если мы
Speaker A
изменения делаем в своей ветке? То есть, да, казалось бы, мы в своей ветке что-то делаем, наши коллеги тоже все себе ветки создадут. Давайте вот коллега один себе сделал ветку, коллега два сейчас себе тоже сделает ветку. Каждый назовёт свою
Speaker A
ветку как-нибудь. Вот вот этот коллега назовёт свою ветку Фичер 3. Этот коллега назовёт свою ветку Фичер 2. Хорошо.
Speaker A
Опять же, мы в своей ветке что-то там сделали, сохранили изменения. Наш коллега что-то там в своей ветке сделал, сохранил изменения. Второй наш коллега тоже также сделал. А в итоге-то что? Ну, после этого хорошо, они будут заливать свои изменения на удалённый сервер. Но
Speaker A
мы помним, что если вы работаете в рамках какой-то ветки и эти изменения вы заливаете на удалённый сервер, то на удалённый сервер эти изменения заливаются именно в ветку, в рамках которой вы работали. При этом мастер ветка, от которой базируются вообще все
Speaker A
другие наши коллеги, другие отделы. То есть мастер ветка - это ветка, в которой находится стабильная рабочая версия кода, которая используется в продакш сервере, которым пользуются наши клиенты. А все остальные ветки, которые не являлись мастер веткой, они никак не
Speaker A
влияют на то, что будет показано конечному пользователю. Все остальные ветки сделаны только лишь для удобства, чтобы избегать той проблемы, о которой я только что сказал, что если мы будем всегда, даже локально, работать в мастер ветке, то как только мы захотим эти
Speaker A
изменения загрузить на удалённый сервер, чтобы их сохранить и не потерять, мы их сразу вот так вот зальём в эту мастерку.
Speaker A
Сразу эти изменения все наши коллеги увидят. они сразу же начнут работать с этими изменениями, и это потенциально может сломать нам весь проект, потому что мы не хотим, чтобы вот так вот сразу все наши изменения сразу летели в мастер
Speaker A
ветку. Нет, мы хотим спокойненько работать в своей ветке, сохранять какие-то изменения и потом заливать их на удалённый сервер для того, чтобы, например, можно было спокойно пойти спать или уйти в отпуск. А потом, вернувшись с отпуска, мы могли продолжить работу с тех самых
Speaker A
сохранённых изменений, потом ещё раз их сохранить и ещё раз, например, сохранить. И при этом вот эти вот наши сохранения не влияли на основную ветку.
Speaker A
Потому что, если мы, например, вот это вот неготовое сохранение зальём в мастерку, оно просто, повторюсь, может сломать весь проект, потому что это не до конца законченный код. Мы просто хотели сохранить какую-то версию нашего кода, чтобы уйти в отпуск. И мы взяли и
Speaker A
в мастерветку. закинули, которые увидят все наши коллеги, с который они работают. В итоге мы им всем испортили всю малину, и пока мы будем в отпуске кайфовать, наши коллеги будут фиксить вот эти вот непонятные изменения, которые мы залили в основную ветку
Speaker A
нашего проекта. Хорошо? То есть поняли, почему мы при разработке делаем свои ветки. Коллеги делают свои ветки для разработки, и мы делаем свои ветки.
Speaker A
Правила наименования этих веток абсолютно различаются. В разных компаниях есть разные правила и соглашения касаемо того, как именно нужно называть эти ветки. Но обычно правило где-то в среднем такое. Первое слово в названии ветки - это то, что именно в ветке будет происходить. Если
Speaker A
мы в рамках своей ветки хотим пофиксить какой-то баг, то обычно название таких веток начинаются со слова багфикс. Вот багфикс. Или же просто иногда пишут фикс. Это значит, что в ветке происходит какой-то фикс. фикс какого-то нежелательного поведения, фикс какого-то
Speaker A
бага. Если мы в ветке хотим, к примеру, зарефакторить какой-то старый код, то есть у нас остался старый код, который давным-давно был кем-то написан. Он вроде как свои задачи выполняет, но нужно его переписать, потому что пользоваться им очень неудобно. Но в
Speaker A
таком случае получается, мы в ветке будем делать рефакторинг кода. Ну и в таком случае название ветки будет начинаться с чего-то типа рефакторинг.
Speaker A
Если мы в ветке будем делать какую-то новую фичу, мы называем ветку feature fe и потом название фичи, которую мы делаем, например, добавление возможности отправлять видеосообщения. Ну вот я назову тут feature/vide messages. Наш коллега в этот момент делал фичу, которая позволяет отправлять
Speaker A
аудиосообщения например audio messages. Хорошо. И наш другой коллега делал ну например возможность голосовых звонков. Voice call. Отлично.
Speaker A
Соответственно, мы и наши коллеги создали себе каждый свою ветку. После этого что мы, что наши коллеги, разработку будем продолжать уже в рамках своих веток. Вот мы что-то там делаем по фиче видеосообщений, уже всё в своей ветке. И сколько бы раз мы по ходу
Speaker A
разработки не захотели тут зафиксировать какую-то версию, это всё можно спокойно потом сохранить на удалённом сервере, и эта версия будет зафиксирована и сохранена. И мы потом уже в рамках своей ветки сможем спокойно переключаться между этими версиями, как нам нужно,
Speaker A
откатываться назад и так далее. То есть вот эта вот функциональность версионирования нашего кода, мы можем её применять просто в рамках одной своей ветки. И при этом наши коллеги не будут беспокоиться за то, что мы тут что-то такое вот страшное сохраним, что
Speaker A
случайно попадёт вообще в основную ветку и всем всё сломает. Нет, мы работаем в рамках своей ветки. В рамках своей ветки мы фиксируем какие-то маленькие изменения, чтобы их сохранить. Можем, если что, переключаться между этими версиями. Можем, наоборот, откатываться на какие-то версии, если нам нужно. И мы
Speaker A
поняли, что, например, вот здесь вот и вот здесь вот мы написали какую-то фигню, взяли, откатились. Всё это происходит в рамках нашей ветки. В рамках ветки наших коллег работают, собственно, наши коллеги. они уже в рамках своих веток что-то там пишут,
Speaker A
какие-то там изменения, вот, например, коллега один может фиксировать и точно также загружать эти самые изменения на удалённый сервер. И они будут видны только в рамках его ветки. Что это нам даёт пока вот на настоящий момент? То, что мы и наши коллеги, мы можем все
Speaker A
вместе разрабатывать параллельно какой-то код. И при этом мы никак не будем влиять на мастерку, которая в этот момент крутится на удалённом сервере. И кодом этой мастер ветки пользуются наши конечные потребители. То есть мы эту мастерку развернули в продакшн-сервере.
Speaker A
И мы можем не бояться сохранять вот эти вот маленькие локальные изменения. Мы можем не бояться между ними как-то переключаться между этими версиями. Мы можем не бояться их сохранять на удалённый сервер, потому что, повторюсь, работа идёт в рамках нашей только ветки.
Speaker A
Ну и, соответственно, коллеги работают в рамках уже своих веток. И это на мастерветку, это на конечного потребителя, на наших клиентов, на наших пользователей. Это никак не влияет.
Speaker A
Хорошо, идём дальше. Вот, допустим, мы там что-то разрабатывали, разрабатывали, разрабатывали. Наши коллеги в этот момент что-то разрабатывали. Ну и, к примеру, вот коллега один первый доразбатывался до какого-то логического завершения своей ветки. То есть он взял и доразбатывался до версии вот 01.2.
Speaker A
пуска это будет наш коллега понял, что работу в рамках фичиосообщения он закончил. То есть ещё раз, наш коллега создал себе свою ветку от мастер ветки и пошёл в ней работать. Работал, работал, тут что-то делал, работал над фичой аудиосообщения и дошёл до
Speaker A
какого-то логического завершения. То есть какую-то версию он вот сделал в рамках лишь только своей ветки и такой говорит: "Слушайте, коллеги, а я вроде как закончил делать аудиосообщения, а давайте я со всеми вами поделюсь этим своим кодом. Вы его посмотрите, и если
Speaker A
вы поймёте, что код хороший, то мы потом этот код, который я разрабатывал в рамках своей ветки, я его возьму и добавлю прямо в наш проект, в основную мастерку. Как это будет происходить? Наш коллега берёт вот эту вот свою версию
Speaker A
ветки, самую последнюю актуальную, и зальёт на удалённый сервер GitHub. После этого вот эта вот его ветка, она будет существовать не только у него локально на компьютере, но ещё и на удалённом сервере GitHub. Опять же, пока что это никак не влияет на мастер ветку. Просто
Speaker A
наш коллега, коллега один, взял и сохранил вот эту вот свою ветку на удалённый сервер, так же, чтобы просто эту ветку не потерять, если что-то случится с его компьютером. После этого мало того, что наш коллега теперь эту ветку не потеряет, так ещё и мы, как его
Speaker A
коллеги, можем смотреть, что же он там в этой ветке написал. И когда наш коллега будет готов уже продемонстрировать нам свои изменения, он может сделать так называемый запрос на слияния. Что это значит? Это значит изменения, которые наш коллега внёс в рамках своей ветки,
Speaker A
он хочет внести в мастер ветку. То есть он делает вот этот вот самый запрос на слияние и говорит: "Ребята, я сделал свою фичу, и я делаю запрос на слияние.
Speaker A
Пожалуйста, придите все и посмотрите, что же я там такого сделал. Значит, вы, ваши другие коллеги, приходят смотреть, чего же там коллега один такого интересного сделал в своей ветке. И вы можете благодаря функциональности гита видеть не только весь его код, вы ещё
Speaker A
можете видеть, что конкретно изменилось в ветке Feature Audio Messages относительно мастер ветки. Вам будет виден так называемый ди. DIV - это изменения, которые произошли в одной версии приложения относительно другой версии. То есть вам с вашими коллегами не придётся просматривать весь код
Speaker A
проекта в ветке Feature Audio Messages. Нет, вы можете для вашего же удобства смотреть только изменения, чтобы оценивать только изменения. Сразу наглядно будет видно, что поменялось, что убралось, какой код стал другим, и вы сможете это удобно оценивать. Но опять же, при желании вы всегда сможете
Speaker A
посмотреть код всего проекта в рамках этой ветки. Ну и при желании опять же сможете видеть только изменения, как хотите. Значит, вы с вашими коллегами смотрите этот код и вдруг вы понимаете, что да, да, это хороший код, и вы
Speaker A
ставите одобрение запроса на слияние. Или же говоря айтишными терминами, вы ставите approve на merge request. То есть merch request - это с английского запрос на слияние, а approve - это, собственно, одобрение. То есть вы ставите одобрение этого запроса на
Speaker A
слияние, там все галочки свои поставили. И после этого ветка вашего коллеги, она вольётся в мастер ветку, и код, который коллега привнёс своей ветке, станет частью мастер ветки. Мастер ветка продвинется дальше, и код F audio Messenges станет частью мастер ветки,
Speaker A
благодаря чему код этой фичи становится уже, так сказать, официально подтверждённой версией. благодаря чему и все остальные ваши коллеги могут уже опираться на этот код как на рабочий, потому что он в мастер ветки, а значит, этот код уже был всеми проверен,
Speaker A
протестирован, и он считается рабочим и валидным. Хорошо. После этого, допустим, мы с вами тоже разрабатывали, разрабатывали, разрабатывали и доразбатывались уже в рамках нашей ветки video messages до версии 01 пускай будет. Мы точно так же можем, когда уже понимаем, что в принципе наша ветка
Speaker A
содержит изменения по фиче, которые удовлетворяют изначальные задачи, ну, пускай у нас вот изначальная задача была это сделать видеосообщение. И мы точно также берём и из своей ветки создаём запрос на слияние в мастерку. И точно так же мы зовём всех наших коллег, типа,
Speaker A
друзья, прибегите, посмотрите, что же я там сделал. Ваши друзья там, коллеги будут смотреть, анализировать ваш код.
Speaker A
Если им что-то не понравится, они могут отклонить ваш запрос на слияние, и вы тогда просто дальше будете в рамках своей ветки там что-то разрабатывать.
Speaker A
Как только вы опять доразбатываетесь до, по вашему мнению, валидного состояния, вы опять выкладываете запрос на слияние, опять зовёте ваших коллег, они смотрят ваш код, и потом, если им всё нравится, опять они ставят апрувы, и вы вливаете код вашей ветки Feature Video Messages в
Speaker A
мастер ветку. Что это даёт в итоге? Это даёт в итоге то, что вы, когда разрабатывали параллельно код с вашим коллегой один, вы смогли, работая вообще на разных компьютерах, вместе в один и тот же проект привнести разную функциональность. Коллега сделал
Speaker A
аудиосообщение, вы сделали видеосообщение, и это всё в конечном итоге стало частью мастер ветки. Вот она, мастер ветка, которая в итоге потом будет задеплоена на удалённый сервер, prodдаction сервер и показано вашим конечным пользователям. Ну и точно так же вот наш второй коллега разрабатывал,
Speaker A
разрабатывал, тоже что-то там себе доразрабатывал, сделал вот голосовые звонки, его вот условная какая-то версия, там 013 пускай будет. И точно также он может сделать этот самый запрос на слияние. Просто берёт вот так вот и в эту самую мастерку делает запрос на
Speaker A
слияние. Точно так же прибегаете вы со своими коллегами, смотрите, что же он там сделал. Если он сделал всё хорошо, то просто ставите ему галочки и его запрос на слияние ветки feature voice call в ветку маер подтверждается и тем
Speaker A
самым код его ветки становится частью основной ветки вашего приложения. Таким образом происходит синхронизация командной разработки. Вы теперь не вынуждены друг с другом, с вашими коллегами что-то там через Telegram, друг другу в личку пересылать там новые версии вашего проекта. Вам теперь не
Speaker A
нужно глазами вглядываться в то, а что же там в его архиве, который мне скинул коллега нового, а как мне эти файлы объединить? Нет, за вас это теперь делает гиit. Вы при помощи удалённого сервера синхронизируете или сохраняете разные версии вашей ветки и потом
Speaker A
создаёте запрос на слияние из вашей ветки в основную ветку. Небольшая сносочка. Иногда запросы на слияние делают не сразу в основную ветку, иногда делают это в так называемую деф-ветку.
Speaker A
То есть деф-веетка - это ветка, в рамках которой производится разработка какой-то фичи. Эта ветка, она происходит от мастер ветки, и потом все запросы на слияние идут уже в эту самую деф-ветку.
Speaker A
И потом уже, если в деф- ветке накопились достаточные изменения с разных веток, потом уже эта самая деф-веетка идёт в мастерветку. Но это уже углублённая тема. Просто небольшую сносочку делаю, чтобы вы понимали, что не всегда ваша разработническая ветка, так сказать, ветка, в которой вы
Speaker A
разрабатываете вашу фичу, не всегда она сразу напрямую идёт в мастер ветку. Иногда это идёт ещё в какие-то промежуточные ветки. Это зависит от того, как именно в команде налажена разработка, какие приняты процессы касательно вот этого взаимодействия с гитом. Где-то сразу из веток
Speaker A
разработчиков заливают изменения сразу в мастерку, где-то в промежуточную ветку. Повторюсь, это очень сильно зависит от того, в какой команде вы будете работать. Итого, при помощи гита, при помощи удалённого сервера GitHub вы с вашими коллегами, во-первых, можете спокойно вести разработку каждый в
Speaker A
рамках своей ветки. Вы можете спокойно сохранять какие-то промежуточные версии ваших изменений на удалённом сервере и при этом не бояться теперь уже после того, как вы сохранили, что эти изменения куда-то потеряются. И потом вы можете создавать запросы на слияние. И в
Speaker A
этом запросе будет прямо конкретно видно, что нового вы привнесли в рамках этой своей ветки, какой новый код вы написали, и будет видно, какой код вы удалили, какой код вы поменяли, какие файлы вы создали, удалили и так далее.
Speaker A
Это всё будет наглядно видно, и ваши коллеги смогут посмотреть лишь на эти изменения и потом поставить одобрение на ваш запрос на слияние. После этого ваша ветка и весь код, все изменения, которые вы вносили в рамках этой ветки, они
Speaker A
станут частью основной ветки. То есть ваш код, ваша вот эта вот версия проекта, она вольётся в основную ветку вашего приложения. Тем самым вы сделаете, так сказать, свой вклад в общий проект. И такими вот маленькими шажочками, собственно, и происходит
Speaker A
командная разработка. Все делают свои ветки, работают потом в рамках этих веток и потом уже делают запросы на слияние, чтобы пришли коллеги другие и посмотрели, что ж там, собственно, за изменения. Если изменения валидные, их подтверждают, и эти самые изменения, они
Speaker A
вливаются в мастер ветку. Если изменения невалидные, их отклоняют и отправляют ветку на доработку. Собственно, вот так вот в конечном счёте, когда ваш первый коллега внёс свои изменения в мастерку, внёс аудиосообщение, потом вы внесли свою ветку, свои изменения с фичой
Speaker A
видеосообщения, потом ваш другой коллега внёс в Фичу голосовые звонки. После этого вы синхронизированно поменяли версию вашего проекта с 0.1 на, допустим, 0.2. И теперь это уже ветка, не фичер никакая, это уже мастер ветка.
Speaker A
Ну вот я её назову main/master. Всё наглядно мы с вами рассмотрели, как при помощи гита и как при помощи удалённого сервера, такого как GitHub, происходит синхронизация командной разработки. Повторюсь, это механизм веток и слияния. Вы создаёте себе ветку, потом в рамках этой ветки работаете,
Speaker A
можете во время работы в рамках ветки какие-то конкретные фиксировать изменения этой ветки и сохранять на удалённом сервере, чтобы не бояться потерять. Потом, когда вы посчитаете, что ваша ветка готова, вы отправляете запрос на слияние. Если запрос принимается, то код вашей ветки
Speaker A
становится частью мастер ветки. И таким образом продолжается разработка. Таким образом продолжается внесение новой функциональности в проект. И проект растёт развивается потихонечку дополняясь новыми фичами. Таким образом, можно наглядно видеть, как при помощи гита мы решаем все три описанные проблемы. Проблема сохранения кода
Speaker A
решается таким образом, что мы фиксируем какую-то конкретную версию нашего приложения или нашей ветки и просто сохраняем это на удалённом репозитории.
Speaker A
Всё. После этого мы можем с любого другого устройства эту самую версию проекта обратно получить. В этом нет никакой проблемы. Вторую проблему, которую решает Git - это проблема версионирования кода. Мы это сейчас рассмотрели в рамках работы в какой-то конкретной ветке. то, что мы в этой
Speaker A
ветке можем какие-то сохранять изменения, уходить в отпуск, например, и потом продолжать разработку уже с этой точки. И, например, потом, если нам нужно будет, сможем откатиться или переключиться, наоборот, вперёд на какую-то версию, которую мы раньше фиксировали. Ну и третья проблема,
Speaker A
которую мы решили при помощи гита - это проблема синхронизации командной разработки, а конкретно при помощи механизма веток и слияний. Мы можем создавать ветки, мы можем в рамках этих веток работать, привносить какие-то изменения и после этого создавать запросы на слияние, тем самым
Speaker A
подконтрольно меняя главную версию нашего приложения. В этом и заключается весь гит. Ну а дальше нам необходимо всё это рассмотреть на практике. Но перед тем, как мы перейдём непосредственно к практике, нам необходимо первым делом установить, собственно, сам гит на наши
Speaker A
компьютеры для того, чтобы мы потом при помощи этого самого гита смогли из наших локальных проектов сделать репозитории на удалённых серверах Гитхаба. Поэтому первым делом устанавливаем сам Git.
Speaker A
Перед тем, как устанавливать Git на Windows, для начала нужно проверить, а вдруг у вас уже установлен гид. Для этого необходимо открыть Power Shell.
Speaker A
это терминал на Windows. И здесь мы просто пишем Git version. Если в выводе мы видим имя Git не найдено, это значит, что Git у вас не установлен, как и у меня, и это значит, нам необходимо установить его вручную.
Speaker A
Для того, чтобы установить Git на Windows, открываем браузер и тут пишем Win Get Install Git. Winget - это пакетный менеджер на современных версиях Windows.
Speaker A
Ну и тут нам неронка от Гугла подсказывает. Либо можем зайти на сайт самого гита. И тут у нас будут различные варианты установки. Можно через установщик это всё сделать, но можно просто одной командой в терминале. Я копирую эту самую команду. Копировать.
Speaker A
Открываю терминал и вставляю эту команду сюда. Нажимаю Enter. Со всем соглашаюсь. Нажимаю да везде, где нужно.
Speaker A
Вот у меня пошёл установщик самого гита уже запускаться. Всё в терминале мне написано успешно установлено. Давайте проверим это. Я напишу Gitсия.
Speaker A
Всё нам пишет успешно установлено. Но если мы сразу после этого попробуем написать Gition, то у нас опять будет написано, что имя Git не найдено. Как это пофиксить? Просто закрываем терминал и перезапускаем его ещё раз. Опять открываем Power Shell и снова пробуем
Speaker A
написать Gitan. Тадам. Всё, у нас установлен Git на Windows, и мы дальше сможем им пользоваться.
Speaker A
Установка гита на MacOS - это довольно простая задача, тем более, что в большинстве версий этой самой MacOS Git идёт уже из коробки. Как это проверить?
Speaker A
Мы с вами открываем терминал и тут просто пишем Git версия. Если у вас высвечивается версия гита какая-то, это значит, что Git у вас уже установлен, и вы можете пропускать этот шаг. Если у вас пишется что-то вроде команда не
Speaker A
найдено, в таком случае вам нужно установить гиit вручную. Как узнать, при помощи чего установить гит? Ну, вообще, все какие-то сторонние пакеты, ну, большинство сторонних пакетов мы на MacOS с вами устанавливаем при помощи пакетного менеджера Brw. Но как узнать,
Speaker A
какая конкретно команда установит нам Git? Да, довольно просто. Мы открываем браузер, в принципе, любой, и в поисковой строке мы можем с вами написать что-то вроде Brew, install Git.
Speaker A
И потом мы с вами переходим на сайт, собственно, непосредственно самого Home Brew. И здесь мы можем видеть нашу команду, которая нам нужна. Всё довольно просто. Brew install git. Копирую команду, вставляю в терминал, нажимаю Enter.
Speaker A
Ну, у меня гиit уже был установлен, поэтому у меня просто скачались некоторые файлы. И потом брю, пакетный менеджер, уже увидел, что у меня на данный момент уже есть гиit, поэтому он не стал его мне заново устанавливать. Но у вас, если гита не было на macOS, то
Speaker A
эта команда этот самый Git установит. Ну и всё. Просто пишем Gitan. И мы видим версию гита, который мы с вами, собственно, установили. Перед тем, как устанавливать Git на Linux, в частности на Убунту, нужно проверить, не установлен ли у вас Git уже, потому что
Speaker A
Git обычно идёт в поставке с большинством других пакетов. Поэтому, если вы уже скачивали что-то стороннее, то с некоторой вероятностью у вас гиit уже установлен, хоть вы об этом ещё и не знаете. Как это проверить?
Speaker A
Просто-напросто открываем терминал и тут пишем Git версия. Вот мне пишется, что команда Git не найдена. Это значит, что гита у меня ещё нет. Но тут же сразу же мне заботливо пишется команда, при помощи которой я этот самый гит могу
Speaker A
установить. Если у вас давно на Линуксе не происходило никаких установок, то хорошо бы сначала сделать следующую вещь. Сюда upt update и суда upt upgrade.
Speaker A
Для чего это делать? Для того, чтобы первая команда у нас подтягивает информацию о новых пакетах с удалённых серверов, а вторая команда, собственно, устанавливает эти новые пакеты. Иногда это важно, и давайте я это и сделаю.
Speaker A
Ввожу пароль. То есть это к установке непосредственно гита не имеет никакого отношения. Это именно обновление пакетов самой в данном случае у бунты для того, чтобы у нас были актуальные версии приложений. Ну а теперь можно, собственно, установить самгиit при
Speaker A
помощи той команды, которую нам сама же у бунта и подсказала. Сud install git. Давайте я эту команду и введу. Сud install git. Enter. Нажимаю. Да. Всё, у меня теперь git установлен. могу это проверить при помощи команды Git version.
Speaker A
Всё, у меня высвечивается версия гита, которая у меня установлена в данный момент. А значит, я установил себе гиit на Linux. Ну, опять же, в нашем случае на Убунту. И теперь я могу этим гитом пользоваться.
Speaker A
Всё, теперь мы с вами взяли и установили Git на наш компьютер. Теперь давайте создадим какой-нибудь минимальный проектик, чтобы потом этот проектик мы могли с вами загрузить на удалённый сервер GitHub, тем самым сделав из нашего проекта Gitпозиторий и уже начав
Speaker A
полноценную работу с гитом. Ну и первым делом мне необходимо создать какую-то директорию, то есть какую-то папку, в которой я размещу свой проект. Создам директорию я прямо из терминала при помощи команды MKdir. MKdir переводится как makearр, то есть создай директорию,
Speaker A
но непосредственно сама команда выглядит именно как MKIR. И через пробел нужно передать название директории, которую мы хотим создать. Это stud.
Speaker A
Всё. Теперь мы видим, вот она созданная директория стади. Чтобы открыть какую-то папку сразу в вс-коде, можно воспользоваться следующим лайфхаком. Мы просто в терминале пишем слово код.
Speaker A
И после этого полный или же относительный путь до директории, которую мы хотим открыть в эс-коде. Я нахожусь в той папке, в которой непосредственно сразу же есть директория стадия, поэтому я могу просто написать относительный путь до стади, а это,
Speaker A
собственно, само стади. Нажимаю Enter. Оп, и я открыл свою вот эту вот директорию стадии сразу в С-коде. Всё, поздравляю. Теперь я могу открыть терминал, ещё раз написать Git-весия, чтобы удостовериться, что Git доступен у меня из терминала VS-кода. Ну и
Speaker A
действительно, так оно и есть. После этого давайте я накидаю минимальную какую-то программку, у которой будет main.gфайл, главный пакет, главная функция и тут вывод на экран Hello Git.
Speaker A
Также я хочу создать дополнительный пакет, который я назову Feature 1. В этом пакете я создам файл feature 1.go.
Speaker A
Package Feature 1. Конечно же, объявляем пакет. И всё, что у меня будет в этом пакете - это публичная, ну, экспортируемая функция, которая называется с большой буквы 1.
Speaker A
Что она будет делать? Так это просто выводить на экран I feature 1. Всё. После этого я перехожу в main, но я не смогу сразу же эту функцию импортировать никак. Видите, у меня её нету ни пакета, ни функции. Почему?
Speaker A
Потому что вспоминаем, для того чтобы работать мне со сторонними пакетами, мне необходимо самому быть модулем. А для этого я пишу go mod инит и название модуля. Ну, в принципе, назову его просто stud. Всё, я создал модуль.
Speaker A
Модуль stud. И теперь я смогу спокойно из мейна вызывать функции, находящиеся в других пакетах в рамках моего модуля. А это у меня пакет Feature 1 и в нём публичная функция feature 1. Всё, теперь можно для проверки запустить эту всю
Speaker A
нашу программу goun main.go. Ну и как мы видим, всё работает. У меня сначала в мейне выводится Hello Git, а потом из пакета Feature 1 вызывается функция feature 1, и всё, что она делает - это вывод на экран IM Feature 1.
Speaker A
Хорошо. Значит, что мы делаем дальше? То есть сейчас мы с вами вообще на каком этапе? Сейчас мы с вами просто установили гиitд себе на компьютер. И на этом компьютере мы создали локальный проект. Пока никакого отношения к удалённым серверам Гитхаба мы с вами не
Speaker A
имеем. Да и гиit мы пока вообще никак не использовали. Теперь давайте начнём использовать Git и удалённые сервера GitHub. Сейчас для того, чтобы нам всё это сделать, первым делом необходимо зарегистрироваться на сайте GitHub.
Speaker A
Давайте мы с вами это и сделаем. Для этого мы просто открываем браузер и в поисковой строке пишем github.com.
Speaker A
Нажимаем здесь на кнопочку signup, то есть зарегистрироваться. Ну и, собственно, вводим данные, которые необходимы для регистрации. Ну, вводим свой email.
Speaker A
После этого придумываем какой-нибудь пароль. После этого придумываем какой-нибудь usернеame. После этого выбираем страну или регион.
Speaker A
Нажимаем создать аккаунт. И нам на почту, которую мы указали, прислали код подтверждения регистрации. Давайте я сейчас его найду и введу.
Speaker A
Всё, я успешно создал себе аккаунт на гитхабе. Теперь я могу войти в него. Нажимаю войти.
Speaker A
Та-дам. Всё, у меня есть созданный аккаунт на гитхабе, и я в него вошёл. Вот так вот он выглядит. Всё, теперь всё, что нам осталось сделать, чтобы у нас с локального компьютера была возможность выгрузить на вот этот вот GitHub свой проект, нам нужно, чтобы
Speaker A
GitHub знал наш локальный компьютер, чтобы он доверял тому коду, который мы с него загружаем. Как это сделать? В правом верхнем углу нажимаем на кружок, заходим в настройки и здесь слева видим SSH ключи. Нажимаем, нажимаем New SSHK или же добавить SSH ключ. И тут нам
Speaker A
нужно какой-то ключ добавить. Какой ключ? Откуда нам его взять? Для этого нам необходимо с вами открыть терминал и в этом терминали написать SSH тире kgen T RSA. Нажимаем Enter и нажимаем ещё раз Enter. Ещё раз Enter. Ещё раз Enter.
Speaker A
Всё, у нас с вами сгенерировались SSH-ключи. И как мы видим, нам указывается конкретный путь, куда эти самые ключи у нас сгенерировались. Нас интересует вот этот вот ключ с приставкой тоpub. То есть это публичный ключ, который мы должны, собственно,
Speaker A
добавить вот сюда вот на гитхабе. Давайте я открою этот файл. Можете открыть его как угодно, как вы умеете открывать файлы в вашей операционной системе. Я открою через Vim, собственно говоря, этот самый файл. Открываю. И вот он мой ключ. Я его копирую
Speaker A
и вставляю у себя его вот сюда вот в гитхабе. Всё. какой-нибудь title я могу ему задать. Ну, например, my study K.
Speaker A
Что угодно, можете сюда написать. И добавляем add sshk. Ну или же добавить ss ключ. Всё, теперь мы с вами подружили наш локальный компьютер с нашим удалённым аккаунтом в гитхабе. И теперь мы сможем без ввода паролей постоянных, без постоянных авторизаций просто с
Speaker A
нашего локального компьютера брать и взаимодействовать с этим самым удалённым сервером Гитхаба. Плюс ещё необходимо для самого гита указать автора тех изменений в коде, которые будут выгружаться на удалённый репозиторий.
Speaker A
Как это выглядит? Пишем в терминале Git config тире тире global. После этого user.email. После этого в двойных кавычках мы пишем какой-нибудь на самом-то деле произвольный email пользователя, который будет отображаться в тех изменениях, которые вы будете добавлять со своего
Speaker A
компьютера. Ну, я тут введу, который привязан к моему GitHub аккаунту. Нажимаю Enter. После этого нам нужно задать ещё username. То же самое git config global user.name. И тут также в кавычках имя пользователя, которым будут подписаны изменения, вносимые на
Speaker A
удалённый репозиторий. Ну давайте я тут напишу Патрик котик. Нажимаю Enter. После этого я могу прописать git config тире тире list. И тут я увижу user email, который мы задали, и username.
Speaker A
Что это за user email и username? В гитте обычно каждая строчка, она принадлежит какому-то автору. То есть тот, кто написал какой-то конкретный код, какую-то конкретную строчку, за ним закрепляется авторство этой строчки.
Speaker A
Соответственно, тот email и тот usернейм, который мы вот здесь вот в Gitконфиге указали, они будут указаны в авторстве тех строчек, которые вы будете загружать на удалённой репозитории Гитхаба. Далеко не обязательно, чтобы этот email и этот usернейм совпадали с
Speaker A
тем, что у вас указано в аккаунте Гитхаба. Просто нужно понимать, что этот email и этот usernрнейм будут указаны в авторстве тех строчек кода, которые вы загружаете на удалённый репозиторий.
Speaker A
Значит, чего мы добились тем, что мы сгенерировали у себя на компьютере какой-то там ключ и потом ещё добавили этот ключ на удалённый сервер GitHub. А мы добились того, что мы теперь подружили наш компьютер с этим самым удалённым сервером GitHub. И теперь мы
Speaker A
можем получать доступ к этому удалённому серверу без всякой авторизации, которая на самом деле бывает иногда довольно сложной и крайне неудобной, когда нам каждый раз на каждое наше действие нужно заново авторизироваться. Нет, мы создали ключ у себя на локальном компьютере и
Speaker A
добавили этот ключ на удалённый сервер GitHub. Создали аккаунт на этом сервере GitHub. И теперь у нас мало того, что есть аккаунт GitHub, так, теперь этот аккаунт точно знает, с какого компьютера мы с ним работаем, и предоставляет нам полный доступ ко всей необходимой
Speaker A
функциональности. Теперь предлагаю вернуться к нашему проекту, который мы пока создали лишь локально у себя на компьютере и никак не взаимодействовали с этим проектом через Git и через GitHub, соответственно, тоже. Значит, вернулись мы в этот наш проект, и теперь
Speaker A
вопрос. Вот у нас, допустим, есть какой-то код. Мы хотим этот код, код этого проекта, код всей папки Stady загрузить на вот этот вот удалённый сервер GitHub. Как это делается? Первым делом нам нужно с вами зайти на этот удалённый сервер GitHub в свой аккаунт и
Speaker A
инициализировать там пустой репозиторий. Давайте я покажу, как это делается. Для личного удобства я перешёл на свой основной GitHub аккаунт. Что теперь нам с вами необходимо сделать? В правом верхнем углу нажимаем на этот кружок и тут нажимаем на репозитории. Здесь опять
Speaker A
в правом верхнем углу нажимаем на зелёную кнопочку Создать репозиторий. У нас открывается такое вот окно создания репозитория. И первым делом нам нужно выбрать название репозитория. Ну, я назову его стадия репозитории.
Speaker A
В принципе, всё. Дальше по желанию можно ввести описание его, я пока этого делать не буду. Можно добавить Redmi file, пока мы даже, наверное, не знаем с вами, да, что такое Redmi File. Можно добавить Gitignor, пока мы тоже не знаем, что
Speaker A
такое Git и Ignore. И можно выбрать отображение этого репозитория, будет он публичный или же приватный. В чём разница? К приватному репозиторию будет доступ только у вас. Доступ имеется в виду возможность просматривать исходный код только у вас либо у людей, которых
Speaker A
вы туда добавите собственными руками. Если репозиторий у вас будет публичный, то, в принципе, любой человек в интернете может случайно наткнуться на ваш репозиторий. Но тем не менее, даже если у вас публичный репозиторий, всё равно редактировать исходный код сможете
Speaker A
только вы либо люди с вашего позволения. Поэтому можно не бояться особо сделать репозиторий публичным. Всё равно редактировать исходные коды сможете только вы сами. Единственное, что если репозиторий публичный, то наткнуться на исходные коды, которые лежат в этом репозитории в интернете, сможет, в
Speaker A
принципе, любой человек. И просмотреть этот репозиторий тоже сможет любой человек. Но именно редактировать код сможете только вы либо люди с вашего позволения. Как бы что из этого выбрать, вы решайте сами. Я сделаю свой репозиторий приватным, просто потому, что я сейчас буду сюда добавлять разный
Speaker A
странный код, а это мой основной аккаунт, на который ссылаются, в том числе, мои курсы на Ютубе. И поэтому люди будут удивляться, что же за новый код я сюда добавляю. И вот, чтобы их просто не беспокоить, я себе сделаю
Speaker A
приватный репозиторий. Пока что вы, в принципе, можете выбрать публичный, повторяюсь, как хотите. И нажимаем создать репозиторий. Всё, у нас создан наш стади репозиторий в нашем аккаунте.
Speaker A
И теперь мы можем посмотреть, что тут по сути ничего нету, он пустой, много непонятных слов. Но здесь нам пишутся советы, как мы можем связать наш локальный проект. То есть вот он, вот этот вот наш локальный проект с этим
Speaker A
удалённым репозиторием. Ну давайте обо всём по порядку. Значит, теперь чего мы добились? Сейчас мы добились того, что мы создали репозиторий на удалённом сервере. Как это выглядит? У нас там сейчас пустой репозиторий.
Speaker A
Всё, мы создали пустой репозиторий. Пока что этот репозиторий вообще никак не связан с нашим проектом локальным.
Speaker A
Никакой связи сейчас нету. Мы просто добавили себе ключик и отдельно создали какой-то пустой репозиторий. Вот сейчас мы на таком этапе находимся. Ну давайте продолжим. Возвращаемся в браузер. И тут, как мы видим, повторюсь, это окно быстрой установки. Ну, quick setup.
Speaker A
Здесь мы можем выбрать, через что мы будем делать эту быструю установку. Так как мы с вами добавили вот эти вот SSH-ключи, при помощи SSH-ключа мы связали наш локальный компьютер и удалённый GitHub репозиторий, мы тут вот можем выбрать SSH. Всё. Теперь смотрим,
Speaker A
что нам тут советуют. Какие-то разные команды. Но давайте, пока мы не будем на них обращать внимание, я от себя расскажу, что мы сейчас будем делать.
Speaker A
Переходим обратно в наш проект, открываем терминал, и тут мы должны инициализировать локальный Gitрепозиторий. Сейчас у нас локально это не Gitрепозиторий, это просто наш самый обычный проект. То есть это сейчас самый обычный проект. Никакой привязки к Гиту он не имеет. ГиIT у нас просто
Speaker A
установлен на компьютере. Но это не значит, что у нас теперь все директории на компьютере - это гиitпозитории. Нет, сейчас у нас просто это самый обычный проект. Как этот проект превратить в локальный репозиторий?
Speaker A
Как это сделать? А очень просто. Мы берём и в корне этого проекта пишем Git пробел и всё. Теперь нам написано, что мы с вами инициализировали пустой гит репозиторий. Теперь у нас этот проект наш локальный, это уже, ну, собственно,
Speaker A
и проект, это всё ещё наш проект, но помимо этого это уже теперь и локальный репозиторий.
Speaker A
Хорошо. И теперь уже непосредственно мы начали работу с гитом. Теперь это локальный репозиторий, который у нас работает как бы при помощи гита. Но сейчас у нас по-прежнему нет никакой связи нашего локального репозитория и удалённого репозитория. Давайте я тут
Speaker A
также напишу. Это всё ещё проект наш, но это уже удалённый репозиторий. Ну и подразумевается, что этот самый удалённый репозиторий - это будет та самая копия нашего проекта локального просто на удалённом сервере GitHub.
Speaker A
Хорошо, мы сделали наш локальный проект локальным репозиторием, но пока ещё мы никак не связали локальные репозиторий и удалённые репозиторий, пока этой связи нету. Как эту связь сделать? Переходим обратно в браузер. И тут есть такая вот команда Git remote origin-ла.
Speaker A
Это команда, которая как раз-таки возьмёт и создаст нам вот эту вот связь между локальным репозиторием и удалённым репозиторием. Давайте я эту команду скопирую, перейду в VS-код, вставлю эту команду и нажму Enter. Всё, теперь мы с вами взяли и связали одной вот этой вот командой
Speaker A
наш локальный репозиторий и удалённый репозиторий. Теперь между ними есть связь. Давайте дальше смотреть, как эта связь будет проявляться. Теперь, что мы вообще хотим сделать? Мы хотим взять все файлы нашего локального репозитория и загрузить их на удалённый репозиторий.
Speaker A
Ну, чтобы, во-первых, как минимум, сохранить наш код и чтобы он не потерялся, если у нас там какие-то проблемы с компьютером вдруг возникнут.
Speaker A
Этот код у нас всегда останется на удалённом репозитории. Давайте смотреть, как это делать. Переходим в VS-код. И тут мы можем написать такую команду в корне нашего репозитория, как Git Status. Эта команда нам показывает такую общую сводку по нашему локальному
Speaker A
репозиторию, что в нём вообще есть. Как мы видим, что мы on branch main, то есть мы сейчас на главной ветке. Та самая main, я вот её подписывал main или же где-то это называется мастер ветка. Мы сейчас вот на этой самой главной
Speaker A
основной ветке нашего проекта. Почему мы сейчас на этой самой главной ветке нашего проекта? Да потому что мы только создали наш гирепозиторий, инициализировали локальный гит репозиторий. Поэтому у нас только лишь одна ветка по умолчанию создалась. Самая главная ветка, вот эта вот мастер ветка.
Speaker A
Вот я схематично изобразил, что у нас у нашего локального репозитория есть только вот эта вот мей мастер ветка.
Speaker A
Почему так? Да, потому что мы свой локальный репозиторий только-только инициализировали при помощи команды Git IT инит. Поэтому у нас нету ещё никаких веток, мы их не создавали. По умолчанию всегда создаётся вот это вот main или же повторюсь где-то называется мастер
Speaker A
ветка. Хорошо, но по-прежнему на удалённом репозитории у нас ещё ничего нету. Мы просто связали локальный репозиторий и удалённый, но мы ещё ничего на этот самый удалённый репозиторий с вами не загружали. По умолчанию у нас есть вот эта вот main
Speaker A
мастер ветка, на которой мы автоматически оказываемся, как только инициализируем свой локальный репозиторий. Хорошо. И вот мы видим, что мы на ветке main. Дальше мы смотрим на Comit. Что такое коit? Коit - это как раз-таки та самая фиксация какой-то
Speaker A
определённой версии нашего проекта. То есть, если я сейчас дальше захочу какую-то версию моего проекта взять и сохранить, то это и будет тот самый комит. Коit - это те самые версии, о которых мы с вами говорили. Те самые версии нашего проекта, которые мы можем
Speaker A
создавать и между которыми при необходимости мы можем переключаться, как хотим. Вот эти вот самые версии нашего проекта - это и есть комиты.
Speaker A
Иногда в некоторых компаниях какие-то конкретные комиты называют более официально какой-то версией. Например, версия там 0,1, допустим, да, версия 0,2 и версия 0,3.
Speaker A
И иногда между этими глобальными комитами создают ещё какие-то небольшие комиты при необходимости, но уже на них не вешают никакие конкретные версии. Но это не значит, что у нас нету возможности на этот самый комит взять, да, и переключиться. То есть при желании
Speaker A
мы можем, так сказать, с мажорной версии легко переключиться на любую минорную версию и с минорной там, да, на любую мажорную. То есть именно сами комиты, они для гита являются этими самыми версиями проекта, но при желании мы можем на какие-то значимые комиты вешать
Speaker A
прямо целые версии, прямо обозначения вешать. Но тем не менее, ещё раз повторюсь, что с точки зрения гита все эти комиты - это конкретные отдельные версии нашего проекта, на которые мы можем спокойно переключаться при желании. Так вот, сейчас у нас никаких
Speaker A
этих версий вообще нету. У нас есть только мастерка, но никаких версий мы с вами ещё не создавали, не фиксировали, поэтому он нам об этом и пишет, что сейчас нет никаких версий. У нас есть просто вот эта вот мастер ветка, но
Speaker A
никакой версии кода мы нигде не фиксировали вообще. Хорошо. Дальше он нам показывает untracked files, то есть файлы, которые мы ещё не отслеживаем как файлы нашего репозитория. И что тут есть? Папкачер 1. Вот она.
Speaker A
Gotфайл. Вот он. И main. Файл. Ну и дальше он пишет, что ничего не было добавлено в коit, но есть ещё какие-то неотслежиемые файлы. Что значит ничего не было добавлено в комит? А это значит, что при работе с нашим локальным
Speaker A
репозиторием у нас в нашем проекте может быть некоторое множество файлов. И мы далеко не все эти файлы захотим добавлять в какую-то очередную фиксацию нашего проекта, да? То есть вот эти все наши файлы. И я могу захотеть все эти
Speaker A
файлы добавить в очередной комит, а могу захотеть только некоторые файлы добавить в очередной комит, а остальные файлы, чтобы остались вне поля зрения гита, чтобы эти файлы я не загружал на удалённый репозиторий. Я хочу, например, лишь только некоторые файлы добавить в
Speaker A
этот комит и потом лишь эти некоторые файлы, соответственно, добавятся на удалённой репозитории. То есть можно делать и так, но в нашем случае нету никаких файлов, которые я бы хотел сокрыть от гита и не добавлять их в комит и не загружать их на удалённый
Speaker A
репозиторий. Сейчас нету у нас таких файлов, потому что я все файлы хочу добавить в свой комит. Поэтому у нас по сути все файлы, которые сейчас untracked, которые сейчас не отслеживаются гитом, мы должны их будем добавить в наш комит. То есть у нас
Speaker A
будет вот следующая ситуация, когда мы вообще все файлы на нашем проекте добавим в этот самый комит. Как добавить файлы в комит? То есть, повторюсь, изначально у нас нет не то что комита никакого, так у нас ещё и никакие файлы
Speaker A
в этот самый комит не добавлены. Давайте это исправим. Мы можем добавлять как какие-то конкретные файлы в commit. Вот, например, я хочу добавить main.go. Я пишу git ed, то есть git добавить main.go. Ещё раз могу написать git status. И как я вижу, что теперь у меня
Speaker A
files остались только папка Ferr 1 и go mode file. А main.go. Уже написано, что changes to be committed, то есть изменения в файлах, которые будут добавлены в commit. То есть я взял вот этот вот main. Файл, пускай вот это вот
Speaker A
main. И я его добавил в предстоящий комит. Я ещё не закоммитил ничего, но я добавил в предстоящий комит. Хорошо.
Speaker A
Теперь я могу отдельно также добавить goфайл и директорию 1. Но я могу просто взять и если я знаю, что я хочу добавить вообще все файлы, которые у меня есть в проекте, написать git ed то. Всё. И теперь я ещё раз напишу gitстаatus, и я
Speaker A
увижу, что я добавил вообще все файлы, которые могли бы быть добавлены в комит, находящиеся в моём проекте стади. Всё, я пишут статус. Я вижу, что всё ещё нет никакого комита у меня. Я всё ещё на главной ветке. И у меня уже в
Speaker A
предстоящий комит потенциальный добавлены вот три файла. Feature1.go из директории Feature 1, goфайл и main.
Speaker A
File. На схеме это выглядит так, что я все файлы из своего проекта взял и добавил в предстоящий комит. Теперь я хочу сделать, собственно, сам комит. То есть я хочу взять и текущее состояние своего проекта зафиксировать в конкретной версии для того, чтобы я
Speaker A
потом мог брать и на эту версию при желании и при необходимости переключаться. То есть я сейчас хочу взять и все те файлы, которые я добавил в комит, собственно, закоммитить, чтобы моё приложение приобрело какую-то версию. И повторюсь, что это мне даст?
Speaker A
То, что я смогу потом при желании дальше писать код, и если я пойму, что я там что-то там напортачил, я всегда смогу вернуться на эту закомиченную версию своего проекта. Как это делается? Файлы для предстоящего комита я уже добавил.
Speaker A
То есть я сказал, какие файлы я хочу, чтобы были в моём предстоящем комите, или же какие изменения в моём проекте я хочу, чтобы были в моём предстоящем комите. Это я сделал. Но самого комита у меня по-прежнему нету. То есть я просто
Speaker A
добавил какие-то файлы в потенциальный предстоящий комит. Я сказал, что я вот именно эти файлы хочу, чтобы были в следующем комите, но пока комита я ещё не сделал. Как сделать этот самый кот? Я пишу git commit - m. И здесь вот я могу
Speaker A
какое-то название для своего коммита передать или какую-то информацию в этом коммите. Но я просто напишу, что это у меня first коit, то есть самый первый коit в моём проекте. Нажимаю Enter. Как мы видим, у нас добавились эти файлы в
Speaker A
коit. И я могу написать ещё раз gitстатус. Теперь я вижу, что я на главной ветке, нечего комитить, то есть я как бы уже закоммитил изменения. Что это значит? Это значит, что я взял просто-напросто все эти файлы, которые у
Speaker A
меня лежали в предстоящем комите, я их взял и, собственно, создал этот самый комит. Взял его вот так вот и создал.
Speaker A
Всё. Теперь у меня в главной ветке есть вот этот вот комит. Теперь я могу взять и написать git log. У меня откроется следующее окно. И тут я увижу, что я создал комит, дата комита, название комита, ну или же комментарий к коммиту
Speaker A
и его хэш-номер. То есть это та самая уникальная версия нашего приложения, которую мы с вами только что зафиксировали. Эти хэши генерируются случайным образом. Ну вот, давайте я вот так вот первые циферки скопирую для наглядности. То есть вот это вот я
Speaker A
зафиксировал нашу версию очередную приложения. Вот так вот это выглядит. Всё, теперь я могу дальше начать писать какой-то код. Представим, что я здесь дополнил, например, FMT printl main and.
Speaker A
Сохраняю файл. И, как я вижу, во-первых, VS-код мне уже зелёным подсвечивает, где я добавил новые строчки. Я теперь прямо в файле смогу видеть, где я чего добавил нового. Во-вторых, я тут слева могу нажать на вот эту вот веточку,
Speaker A
и у меня во вкладке Changes будут изменения по моему проекту. Я смогу буквально нажать на какой-то файл, и я увижу, что нового было добавлено. Это и есть так называемый Git div, то есть это изменения, которые мы привнесли после
Speaker A
предыдущего комита. То есть вот мы с вами здесь закомитились, да? Вот мы создали комит, а после этого мы там что-то там ещё там пописали какого-то своего кода. И как раз-таки вот этот вот новый написанный какой-то код, это и
Speaker A
есть git div. И вот мы здесь можем его посмотреть, то, что я добавил FMT Printl main end. И слева мне как раз-таки видно, какие файлы были добавлены, изменены или же удалены. Хорошо. И как раз-таки, как вот мне потом откатить
Speaker A
себя до вот этого вот последнего комита? То есть убрать вот эту вот фигню, которую я написал. Если она мне не нравится, я хочу откатиться до предыдущего комита. Ну, вс-коде я могу это сделать следующим образом. Я могу вот здесь вот навестись на changes. И
Speaker A
здесь есть такая вот стрелочка, которая обозначает, вот если я наведу discard all changes, то есть отменить все изменения. Ну давайте я её нажму, подтверждаю.
Speaker A
Оп, всё, и у меня отменились все изменения, которые я сделал после комита и не закомитил в новый комит, собственно. То есть я вот их сделал эти изменения, я их отменил. Всё, у меня опять мой чистый проект, который находится вот в этом состоянии вот этого
Speaker A
вот комита. Хорошо, но как мне сделать это же самое действие? Как мне откатить изменения до комита? Но если у меня не VS-код, и я хочу это сделать из терминала. Ну, давайте посмотрим. Вот я тут опять напишу main end, сохраню,
Speaker A
опять захожу в getди div, вижу, что у меня действительно добавилось вот это вот изменение по сравнению с вот этим вот моим последним комитом. То есть опять я тут что-то тут написал непонятное. И я могу просто зайти в терминал, написать gitстатус. Я увижу,
Speaker A
что я всё ещё на главной ветке, но тут мне уже подсвечивают, что у меня modified main.go, то есть у меня файл main. Блён. Также я прямо из консоли могу написать git div. Опа. И посмотрите, мне прямо в консоли
Speaker A
покажется, что было, собственно, добавлено в этом коммите. Мне пишут, что у меня в файле main. Вот два зелёных плюсика, то есть две строчки было добавлена. пустая строчка и строчка с выводом на экран main. Нажимаю Q, чтобы выйти отсюда. Ещё раз напишу git status.
Speaker A
Ну и тут он мне, в принципе, сам будет советовать, как я могу отменить изменения в текущей директории. Для этого я должен написать Git Restore. Git Restore. Ну и после этого название файла, который я хочу откатить до последнего зафиксированного комита. Ну
Speaker A
или если я поставлю точку, то я все файлы в текущем проекте откачу до последнего комита. Ну, давайте я это и сделаю. Git restore точка. Нажимаю Enter. Опа. И как мы видим, у меня main.
Speaker A
Файл опять восстановился в то состояние, которое было у меня на момент вот этого вот комита. Ну, смотрим дальше. Сделали мы тут вот комит, но это у нас всё происходит в рамках нашего локального компьютера и нашего локального репозитория. Удалённый сервер у нас пока
Speaker A
что вообще ничего не знает про наши файлы, про наш проект. Всё, что сейчас удалённый сервер знает - это то, что мы на нём создали свой аккаунт и привязали SSH-ключ. Но про наш проект локальный вообще ничего удалённый сервер не знает.
Speaker A
Как нам эту ситуацию поправить? Мы опять заходим на вот этот вот самый GitHub в наш репозиторий пустой. То есть вот он наш вот этот вот удалённый репозиторий пустой. Хочу уточнить, что удалённый репозиторий - это не значит, что его
Speaker A
удалили. Это значит, что он находится на сервере. То есть удалённый не от слова удалить, а от слова даль. То есть это удалённый репозиторий от слова даль. Это значит, что он находится на каком-то сервере. Но это не значит, что мы там
Speaker A
что-то там удаляли с вами. Ну, это я [откашливается] так на всякий случай. Хорошо. Заходим в этот наш пустой удалённый репозиторий. И тут мы смотрим, что нам вообще советуется. Мы с вами уже инициализировали наш репозиторий локальный. Мы с вами уже добавили туда
Speaker A
файлы. Мы с вами создали комит. И после этого нам пишут, что мы должны написать gitp. Что значит gitp? Gitp значит, что мы берём ветку текущую, на которой мы работаем локально. В нашем случае это меain ветка. Мы берём эту самую меain
Speaker A
ветку, все её комиты, которые мы создали, и отправляем на удалённый репозиторий. Таким вот образом, тем самым отправив комит локальный на удалённый репозиторий, мы отправляем и все файлы, которые были в этом самом комите. То есть, по сути, в нашем случае
Speaker A
мы весь проект берём и отправляем на удалённый репозиторий, тем самым синхронизируя состояние удалённого репозитория и локального репозитория. И получается, что удалённый репозиторий и локальный репозиторий у нас становятся синхронизированы именно на ветке Mainmas. Мы сначала локально создали у себя комит, а потом написали gitpush и
Speaker A
отправили этот комит на удалённый репозиторий. Хорошо, давайте мы сейчас с вами это и сделаем. Я открываю VS-код, открываю терминал, пишу опять gitстаatus. Убеждаюсь, что я на главной ветке, то, что я уже всё добавил в комит. Могу написать git log. Увижу, что
Speaker A
да, действительно, у меня есть вот этот вот мой коit. И теперь я могу написать git push. Но так как я ещё на удалённом репозитории не создал никаких веток, ничего тут нету, для этого я должен написать Gitpush setupstream Origin
Speaker A
main. Ну, эта подсказка, она автоматически в гитет. Копирую, вставляю и тем самым я не только этот комит, который я сделал локально, загружу на удалённый репозиторий, я ещё и предварительно там создам такую же ветку Mainmaster. Это всегда нужно делать,
Speaker A
когда в новых ветках вы делаете первый пуш, потому что удалённый репозиторий пока ещё не знает про то, что у нас локально вообще есть какие-то там ветки.
Speaker A
То есть он про это вообще ничего не знает. Для этого при первом пуше, при пуше первого комита с новой ветки мы должны указать вот этот вот set upstream origin main для того, чтобы создать предварительно новую ветку на удалённом
Speaker A
репозитории. Вот так вот она создастся. А потом уже туда сразу же закинется вот этот вот наш новый комит. Ну давайте, собственно, вот я просто нажму Enter.
Speaker A
Ну и как мы видим, всё произошло успешно. Что мы сделали вот этой вот командой? Повторюсь, мы этой командой, во-первых, при помощи вот этой вот приставки set upstream Origin Main, мы создали с вами на удалённом сервере нашу ветку. То есть
Speaker A
мы её просто проинициализировали, но не добавили туда коit. Комиit добавляется просто gitp. То есть мы берём и с локальной ветки добавляем комит на удалённую. Всё, теперь мы с вами наконец-таки взяли и загрузили наш локальный репозиторий на удалённый сервер. И теперь это у нас удалённый
Speaker A
репозиторий. Благодаря чему мы теперь, даже если локально возьмём и удалим вообще все файлы нашего репозитория, мы в любой момент сможем взять и обратно склонировать или же скопировать удалённый репозиторий. себе на компьютер и продолжить с ним работу. Всё. А теперь
Speaker A
давайте обратно перейдём на GitHub. Можно перезагрузить страницу. И теперь вы увидите, что мы все файлы, которые были у нас локально в проекте, все файлы, которые мы добавили руками в комит при помощи команды Gitd и дальше название файла. Все эти файлы теперь у
Speaker A
нас добавлены в наш удалённый репозиторий. Мы видим, у нас тут есть одна лишь ветка. Это main ветка. И мы видим наш этот самый комит. Вот он. Вот он наш самый первый комит. Можем посмотреть, что же мы в нём добавили. В
Speaker A
нём мы, собственно, добавили три наших файла. Первый файл feature 1.go. Второй файл - это и третий файл - это наш самый главный файл сйфункцией.
Speaker A
Таким образом, мы с вами взяли и перенесли локальный репозиторий на удалённый сервер, тем самым создав удалённый репозиторий. То есть при помощи гита мы с вами уже решили две проблемы из трёх. Мы с вами решили проблему сохранения кода тем самым, что
Speaker A
мы создали удалённый репозиторий, который будет сохраняться даже если мы потеряем свой локальный репозиторий. И мы решили проблему версионирования кода тем, что мы с вами создали комит и наглядно мы с вами увидели, что будет, если мы написали какой-то код и вдруг
Speaker A
захотим вернуться на комит. Мы просто пишем git reset и мы вернёмся обратно на этот комит. Тем самым мы можем не бояться написать чего-то лишнего. И тем самым мы можем не бояться создавать новые комиты. То есть нам ничто не
Speaker A
мешает потом взять и создать ещё один комит. У него там будет какой-то другой уже хэш-код. И мы сможем потом с этого комита обратно переключаться на предыдущий, а с предыдущего на следующий. Тем самым мы, повторюсь, решили проблему версионирования кода.
Speaker A
Теперь давайте посмотрим, как при помощи гита наглядно решается проблема синхронизации командной разработки. Давайте представим, что мы - это какой-то там ваш коллега, и нам пришла задача сделать фичер 2. То есть вот у нас есть какие-то абстрактные фичи. Уже
Speaker A
есть фича один, нам нужно сделать фичу два. Что мы можем сделать? Мы можем сразу же взять и начать писать в этой самой мастерке. То есть локально себе мы клонируем этот репозиторий, да? То есть ваш какой-то коллега, он возьмёт себе и
Speaker A
склонирует этот репозиторий в локальный репозиторий, и у него появится локально точно такой же репозиторий, как сейчас есть у нас. И коллега сможет выбрать, что он захочет делать. Возможно, он возьмёт и начнёт прямо в мастер ветке добавлять какие-то свои комиты. Но в чём
Speaker A
проблема? Проблема в том, что как только коллега напишет Ph, то он возьмёт вот этот вот свой комит, не проверенный никем, и закинет вот так вот мастер ветку. А в чём проблема? Проблема в том, что на эту мастерку мы завязаны с вами.
Speaker A
Как бы и все коллеги другие тоже завязаны именно на мастерку. Поэтому, если этот человечек, этот тюбик вот так вот возьмёт и закинет просто какой-то непроверенный комит сразу в мастерветку, то мы потом все вместе получим этот самый непроверенный комит. Чем это
Speaker A
чревато? тем, что у нас проект просто ляжет, потому что там непонятный код в непонятном состоянии. Как этого избежать? Избегается это тем, что наш коллега должен создать себе ветку. Наш коллега берёт и создаёт себе ветку.
Speaker A
Ветка это будет называться каким-то образом. Пускай в нашем случае она называется feature/1. И теперь наш коллега свои новые комиты будет делать уже в рамках этой ветки. И если он напишет git push, то он не испортит эту мастер ветку. коллега тем,
Speaker A
что напишет Gitpush, просто создаст свою ветку на удалённом репозитории вот таким вот образом. Но мастер ветку, от которой зависят все остальные, он не тронет и он не испортит своими вот этими вот непроверенными комитами. Потом, когда наш коллега поймёт, что он уже создал
Speaker A
какую-то конкретную валидную версию своей фичи, он просто актуализирует удалённую ветку новыми комитами и после этого возьмёт, да и создаст запрос на слияние. После этого прибегут все коллеги, мы прибежим, будем смотреть этот запрос на слияние, оценивать его. И если этот запрос на слияние мы
Speaker A
посчитаем, что он валидный, то наш коллега возьмёт его и просто вольёт в мастер ветку. Тем самым разработку он вёл в своей ветке, но потом комит финальный он добавился в мастер ветку, и у нас тут уже будет какой-то новый
Speaker A
комит. И после этого мы все, как его коллеги, сможем от этой мастер ветки уже от нового комита начать зависеть. Таким вот образом это всё будет выглядеть. Ну теперь давайте от схемы перейдём к практике, посмотрим, как это всё выглядит вживую. Давайте я тут удалю
Speaker A
всё, что я нарисовал, чтобы заново это всё мы с вами могли лицезреть. Напоминаю, вот наш коллега взял и склонировал себе этот удалённый репозиторий на компьютер. И у него получился локальный репозиторий. Сейчас там всё, что есть - это мастерка с одним
Speaker A
комитом. Ну давайте это исправлять, потому что, повторюсь, новую функциональность мы разрабатываем в отдельной ветке. Почему? Я уже объяснил несколько раз. Значит, переходим в Visual Studio CД. И теперь мы в лице этого нашего коллеги должны каким-то образом создать новую ветку. Как это
Speaker A
делается? Это делается довольно просто. Мы пишем git пробел бренch, то есть бренch - это с английского ветка. И тут мы пишем название ветки, которую хотим создать. Ну, в нашем случае это будет feature/1, ну или feature 2. То есть
Speaker A
feature о у нас уже есть. Мы будем делать фичу 2. Соответственно, я называю свою ветку feature/2. Нажимаю Enter. И теперь я могу написать git branch. Я увижу, что у меня есть две ветки. Первая main ветка и вторая ветка Feature 2. Что
Speaker A
сейчас произошло? Я сейчас всего лишь-навсего взял и создал у себя в локальном репозитории ветку и назвал эту ветку Feature 2. Но работать я всё ещё продолжаю в мастер ветке. Как мы видим, здесь мей ветка, у неё звёздочка.
Speaker A
Звёздочка означает, что сейчас я всё ещё нахожусь вот в этой мастерке. Что это значит? Это значит, что если я буду менять какие-то файлы и создавать новые комиты, то они всё ещё будут создаваться в этой самой мастер ветке. То есть я
Speaker A
тем, что написал команду GitBchature 2, я всего лишь создал ветку, но работать я продолжаю в мастер ветке. Для того чтобы мне переключиться на вот эту вот feature 2 ветку, я должен написать git checkout feature/2, то есть Git checkout
Speaker A
и название ветки, на которую я хочу переключиться. Ну, в данном случае это Feature 2. Нажимаю Enter, мне пишет, что я переключился на ветку Feature 2.
Speaker A
Теперь я могу опять написать Git Branch. И я увижу, что теперь я переключился на ветку Feature 2, потому что она подсвечена зелёным. И тут ещё и звёздочка. Дополнительно я могу проверить, что я нахожусь на ветке Feature 2 тем, что я напишу Gitстатус.
Speaker A
Всё, мне написано, что я на ветке Feature 2. Теперь давайте немножечко уточним нашу схему. Сейчас на мастер ветке у нас есть только один комит. Но когда мы с вами создали ветку, мы на самом деле создали её не из
Speaker A
произвольного места ветки мастер, а конкретно с того комита, на котором мы были, находясь на ветке матер. Давайте я обратно переключусь на ветку master, ну, в нашем случае это main называется она, и напишу gitlog. То есть у нас тут один
Speaker A
комит, и мы именно на нём и находились, когда мы писали с вами git branch feature 2. Что это значит? Это значит, что эта ветка Feature 2, она создалась не просто где-то там в непонятном месте, а конкретно вот от этого вот места.
Speaker A
Именно от этого вот места мы с вами создали свою ветку. Именно от этого комита. Если после этого, как мы создали ветку, мы в мастер ветки будем писать новые комиты, создавать, то наша вот эта вот ветка Feature 2, она всё ещё будет
Speaker A
базироваться на вот этом вот старом комите. И нам придётся, если мы захотим делать запрос на слияние, актуализировать эту самуючер 2 ветку, перенося её на последний комит, актуальный на мастере. Если что, это называется Git Rebase. Но это уже такие
Speaker A
немножко более продвинутые темы. Сейчас нам важно понимать, что когда мы с вами создаём какую-то ветку, мы её всегда создаём от какой-то родительской ветки.
Speaker A
Всегда у нас есть родительская ветка и ветка, которую мы, собственно, создаём. И эта ветка, которую мы создаём, она создаётся от того комита, на котором была ветка, от которой мы создавали дочерню. То есть мы от родительской ветки создали дочерню, и она создалась
Speaker A
именно вот от этого комита, а не в каком-то там произвольном месте. Хорошо, этот факт мы пока запомнили. Всё, мы с вами создали ветку. Вот она у нас 2.
Speaker A
Обратно на неё переключусь. Git checkout feature 2. Вот я на ней пишу gitстатус. Ну, собственно, вот я на этой ветке Fature 2. Теперь всё, что мне нужно сделать - это, собственно, реализовать мою функциональность, которая у меня дана по задаче. А это у меня реализовать
Speaker A
feure 2. Ну, давайте я это сделаю. Создаю пакет Feature 2. Тут файл fature 2.go, пакет Feature 2. Ну и давайте я вот тут, собственно, сделаю тоже какую-то имитацию новой фичи. Вот она у нас Feature 2. Всё, что она будет
Speaker A
делать, это точно также выводить на экран I'm feature 2. Собственно, всё. Теперь перехожу в main. И тут после 1 я буду вызывать feature 2. Таким вот образом опять могу перейти в getdiv и в changes, то есть в изменениях,
Speaker A
посмотреть, а что же я, собственно, изменил в моём проекте. Что же я в рамках этой фичи 2 после того, как я забазировался на каком-то комите, что же я привнёс в этой ветке нового? Что же я тут написал такого? Написал я тут
Speaker A
следующее. В main. Файле я добавил новый импорт и вызов функции feature 2 из пакета Feature 2. И ещё я, собственно, создал файл Feature 2. Вот это то, что у меня нового. Ну и если я просто перейду через проводник в main. Файл, тут я
Speaker A
увижу зелёные вот эти вот плашечки, что означает новый добавленный код. Хорошо, я сделал какие-то изменения, создал какие-то файлы в новой ветке. Теперь я хочу зафиксировать эти изменения. Потому что если я изменения не зафиксирую, если я изменения не закомичу, говоря другим
Speaker A
языком, я их не смогу потом отправить на удалённый сервер. Почему? Да потому что я их не зафиксировал. Я должен сначала зафиксировать изменения, создать какую-то версию проекта, пускай даже эта версия существует только в рамках моей ветки, и потом загрузить это всё на
Speaker A
удалённый репозиторий. Хорошо, давайте это и сделаем. Я перехожу опять в терминал, пишу gitстаatus, вижу, что я в своей рабочей ветке и вижу, что у меня есть два изменения. Первое - это изменение файла main. И второе - это новые файлы. Новые файлы - это
Speaker A
директория Feature 2, ну и, собственно, файл в ней Feature 2. Я могу, опять же, добавлять эти файлы отдельно, какие захочу я добавить в новый комит.
Speaker A
Например, gitmain. Я могу сделать. И теперь опять пишу gitстаatus. И я вижу, что main. у меня уже добавлен в предстоящий комит, ачер 2 нет. Ну давайте я, в принципе, теперь буду добавлять все новые файлы при помощи git edd, чтобы каждый отдельный файл не
Speaker A
выписывать. Git add точка. Теперь gitстаatus. И я вижу, что все изменения, которые у меня были в рамках моей ветки, я добавил в предстоящий комит. Теперь я, собственно, должен взять и создать этот комит. То есть ещё раз, что сейчас у нас
Speaker A
произошло. У нас в нашей новой ветке произошли какие-то изменения в файлах. Но для того, чтобы мы смогли с вами сделать из этих изменений комит и загрузить его на удалённый репозиторий, мы сначала должны в этот предстоящий комит добавить, собственно файлы
Speaker A
изменённые. Для этого я беру и пишу git ed. Ну и, собственно, я взял и добавил все изменённые файлы, все изменения, созданные либо же потенциально удалённые файлы в предстоящий комит. Но комита ещё никакого не сделал, я только добавил их
Speaker A
в предстоящий комит. Хорошо. Теперь я напишу git commit - m и в скобочках я передам то, что я в этом комите сделал.
Speaker A
Ну, кратенько максимально. Что я в этом коммите сделал? Я в этом комите добавил feature 2. Ну, я так и напишу: AD feature 2. Нажимаю Enter. Всё, я создал комит. Но этот комит я создал лишь у себя на ветке, которая у меня существует
Speaker A
только локально. И этот комит у меня тоже существует только на локальной ветке. Сейчас у нас удалённый репозиторий не знает ни про ветку Feature 2, ни про вот этот вот новый комит. Давайте я напишу Gitlog.
Speaker A
И тут я вижу, что в моей ветке есть как комит, от которого я забазировался.
Speaker A
Это вот этот вот комит, так и новый комит. Вот AD Feature 2. Давайте я скопирую тоже первые буквы этого хэш-кода, чтобы просто у нас было какое-то идентификация какая-то этого комита. Вот он. Но существует он, повторюсь, только у нас локально на
Speaker A
удалённом репозитории. Либо у наших коллег нету этого комита, это ветки. Теперь, допустим, мы действительно уверены, что вот это вот состояние нашей ветки на вот этом вот комите - это то, что мы хотим показать нашим коллегам. Мы выкатим запрос на слияние или же мрест,
Speaker A
и наши коллеги посмотрят, что же мы там такого сделали. Для этого мы должны с вами взять и вот эти вот изменения, которые мы с вами произвели локально, создание ветки, создание комита в рамках этой ветки. загрузить на удалённый репозиторий. Это сперва. Ну давайте,
Speaker A
собственно, это и сделаем. Я просто беру и пишу Git Push. Но опять у меня вот это предупреждение, почему? Потому что у меня ветки Feature 2 не существует на удалённом репозитории. Поэтому я просто вот это вот скопирую и вставлю.
Speaker A
Всё, теперь я загрузил эту ветку и изменения в рамках этой ветки на удалённый репозиторий. Давайте зайдём опять в наш репозиторий.
Speaker A
И теперь, как мы видим, нам тут даже подсвечивается, что мы с вами создали ветку Feature 2. То есть мы сейчас просто взяли и вот нашу ветку и созданный на ней комит мы загрузили на удалённый репозитории. Таким вот образом. Повторюсь, мастер ветка вообще
Speaker A
сейчас не трогается, она в безопасности. Мы работаем в рамках своей ветки. Ну, я даже могу нажать вот сюда и увидеть, что действительно у меня теперь две ветки.
Speaker A
main ветка и Feature 2 ветка, которую я только что создал и загрузил на удалённый репозиторий вместе со всеми её комитами. Хорошо, теперь я могу вот сюда вот нажать Pull requests. Ну, Pool Requests - это то же самое, что и merch
Speaker A
request. Почему-то просто в разных местах называется по-разному. Всё это значит запрос на слияние. Это одно и то же, что request, что request, что запрос на слияние - это всё одно и то же, просто названное разными словами. В айтишных кругах всё-таки принято
Speaker A
называть это либо мёжреквестом, либо пулреквестом. Запрос на слияние вот так вот по-русски никто не называет. Ну, я буду называть это пулреквестом. Мне вот привычнее называть это пулреквестом.
Speaker A
Здесь мы с вами можем нажать New Request. То есть я хочу создать новый запрос на слияние. Тут слева мы выбираем ветку, в которую мы будем проситься на слияние. Проситься на слияние мы с вами будем в мейн ветку. Вот она. То есть вот
Speaker A
она наша main, ну или же мастер ветка, и мы хотим в неё проситься на слияние.
Speaker A
Хорошо. А тут мы выбираем ветку, которая просится на слияние. Просится у нас 2. И теперь я могу создать полуреквест.
Speaker A
Нажимаю создать полуреквест. Создать полуреквест. Опять же описание могу какое-то добавить для него. И я создал свой лрекст. Что тут вообще есть? Тут есть commits. Это комиты, которые я хочу привнести своим полуреквестом и file changes, то есть изменение файлов. И тут
Speaker A
мы или наши коллеги уже смогут спокойно зайти на этот ваш пулреквест и увидеть, чего же там нового вы сделали. Вот здесь вот слева какие файлы были изменены, а справа, собственно, сами изменения в этих файлах. Вот я тут могу нажать main.
Speaker A
И вот изменение в файле main. Либо могу посмотреть feature2.go. Вот он как бы созданный новый файл. Допустим, я ваш коллега. Я такой посмотрел такой: "Ага, ну, в принципе, хорошо, но только вот здесь вот фичер почему-то капслоком написано". А я бы хотел, чтобы просто с
Speaker A
большой буквы, но дальше буквы были нижнего регистра. И я тут могу нажать плюсик и написать комментарий. Можешь, пожалуйста поправить фичер на фичер. Всё, нажимаю это single comment либо start review. Тут уже, в принципе, что хотите. Я нажимаю стар и,
Speaker A
собственно, вот теперь как будет выглядеть ваш пулреквест. Ваш пулреквест теперь будет выглядеть таким образом, что все комментарии, которые на него будут оставлены, они будут отображаться у вас прямо на странице этого пулреквеста. и будет показано, к какой строке этот самый комментарий был дан.
Speaker A
То есть у меня комментарии: "Можешь, пожалуйста, поправить фичер на фичер". Да, то есть убрать капsлоog и дана строка, к которой это всё будет применяться. Хорошо, я это увидел. То есть мне как бы не поставили галочку, мне не поставили апрув, мне сказали, что
Speaker A
нужно что-то там подправить. Хорошо, это не проблема. Я захожу обратно к себе в проект, смотрю, что я на своей ветке.
Speaker A
Тстатус, да, я на этой ветке, в которой я работаю. И тут я могу зайти туда, где меня просят поправить. Это файл feature2/feature2.go.
Speaker A
Вот он. И тут меня просят поправить капsog на обычную надпись. Хорошо, я это сделаю фичер. Сохраняю. Всё, я как бы поправил все замечания на пулреквесте.
Speaker A
Хорошо. Опять захожу смотреть div. Вижу, что я поправил. Попрошу обратить внимание, у меня будет смотреться не относительно вот этого комита, от которого я забазировал свою ветку, а DIV у меня уже будет смотреться относительно моего комита последнего, а это уже
Speaker A
другой комит. Прошу обратить внимание, относительно этого комита. Всё, что мы с вами поменяли, это вот capslock feature на обычное написание. Ну хорошо, меня это устраивает. Я опять могу написать gitстатус. Ага, вижу, что я изменил один файл. Добавляю этот файл в предстоящий
Speaker A
коми точка. Опять напишу gitстаatus. Действительно, я этот файл в предстоящий коми добавил. И пишу git commit ми. И здесь я напишу fixed capslock.
Speaker A
Всё. Нажимаю Enter. Я локально у себя создал новый комит. Напишу git log. Вот он хэш этого самого комита. И как мы видим, вся история комитов, она сохраняется. И я в любой момент смогу переключаться между этими версиями. И сейчас я вам покажу, как это делается.
Speaker A
Ну вот, я тут создал, значит, очередной комит, но повторюсь, он у меня сейчас только лишь локально существует. Для того, чтобы этот комит начал существовать ещё и на удалённом репозитории, его видели люди, которые оставляли нам комментарии на полурекреквесте, я должен этот комит
Speaker A
запушить на удалённый репозиторий. А это делается следующим образом. Я пишу git p. Всё, теперь мне не нужно писать там что-то setup stream origin. Нет, не надо. У меня уже создана ветка локальная на удалённом репозитории, и теперь мне не нужно её заново пересоздавать.
Speaker A
Поэтому я просто пишу Gitpush, чтобы все новые локальные комиты отправить на удалённую ветку. Ну, давайте пишу Enter.
Speaker A
Всё, я отправил все локальные новые комиты на удалённую ветку. И теперь это выглядит следующим образом. Я вот этот комит взял и вот так вот отправил его сюда. Соответственно, все наши коллеги теперь, когда будут переходить на наш полуреквест, они будут видеть вот здесь
Speaker A
вот подпись outdated, ну или типа устаревшая. Что это значит? Это значит, что я поправил вот эту вот строчку, которой был данрий. Теперь наши коллеги смогут увидеть, что у нас уже два комита. Как мы видим, наш первый комит, наш второй комит и изменения в файлах.
Speaker A
Изменения в файлах можно просматривать либо глобально на весь пулреквест, да, то есть у нас сейчас идёт уже запрос на слияние, мы его уже создали, когда мы с вами создали полуреквест. Повторюсь, полуреквест - это и есть запрос на слияние. Вот мы с вами создали. И вот
Speaker A
эти вот самые изменения в файлах можно смотреть либо глобально от всей ветки, да, то есть от ветки Feature 2 до ветки Mainmas, либо можно изменения в файлах смотреть только лишь между вот этими двумя комитами. Для чего это сделано?
Speaker A
для того, чтобы я там человеку вот на вот этом вот комите понаставлял всяких комментариев, а потом он что-то поправил, и я хочу посмотреть именно вот эти вот изменения, типа, а что же он поправил в новом комите, но не смотреть
Speaker A
вообще все изменения между вот этими двумя ветками. Нет, меня интересуют изменения только между вот этими двумя комитами. Хорошо, сейчас я вижу изменения глобально между двумя ветками, но чтобы посмотреть конкретный комит, я вот могу здесь вот выбрать конкретный комит. Вот он самый последний fixed cups
Speaker A
и я увижу, что действительно всё, что было в последнем комите сделано- это по fictшion cslock на обычное написание.
Speaker A
Хорошо. И обратно могу переключиться на show all changes, то есть смотреть все изменения. Хорошо. После этого я такой смотрю, думаю: "Ну, в целом выглядит хорошо. В целом выглядит хорошо. Ваши коллеги там поставят вам галочки".
Speaker A
Короче, где-то в крупных компаниях даже сделано так, что вы не сможете влить свой запрос на слияние без одобрения. То есть там прямо у вас кнопки даже не будет, но так как сейчас мы работаем одни, то у нас эта кнопка есть всегда.
Speaker A
Это можно, конечно, дополнительно настроить, но это уже углублённая тема. Хорошо, предположим, нам там наши коллеги написали хороший лреквест, можешь заливать.
Speaker A
Всё, нам оставили этот коммент, мы там можем тоже лайк ему в ответ поставить. Всё, а теперь мы нажимаем на, собственно, само слияние. Нажимаю. Тут можно какие-то комменты дать к этому, но я не буду делать. Тут автоматически всё формируется. Нажимаю confirm merch, то
Speaker A
есть подтвердить слияние. Всё, мне написано, что этот полуреквест был успешно замёрджен и был закрыт. Давайте посмотрим, что же случилось. А случилось следующее, когда этот запрос на слияние был одобрен, вот эти вот комиты, которые были у нас на фичатке, они после
Speaker A
одобрения запроса на слияние взяли и влились в мастерку. Всё. Теперь вот эти вот изменения, они стали частью полноправной мастер ветки. То есть это уже не какие-то там временные изменения или что-то такое. Это полноценные два комита, которые вливаются в мастерку.
Speaker A
Всё. Теперь мы это с вами можем наглядно увидеть, перейдя в наш репозиторий и на мастерветке нажав Comits. Всё, мы увидим, что тут вот эти вот два наших комита, они были добавлены прямо в мастерку. Ну и тут ещё один комит был
Speaker A
инициализирован автоматически гитхабом, который означает именно одобрение запроса на слияние. Но именно комиты, которые мы с вами привнесли, вот они: раз комит и два комит. И тут есть ещё вот некоторая такая проблемка, что если во время разработки человек в своей же
Speaker A
ветке делал очень много комитов, да, там каждую мелочь сохранял, раз комит сделал, два, там, может быть, у него ещё какие-то комиты были, их, может быть, действительно много этих комитов.
Speaker A
постоянно там что-то подправлял, что-то там подфикшивал. И потом, когда этот его запрос на слияние будет одобрен, то все эти комиты они вот так вот возьмут и зальются в мастер ветку, что может быть иногда очень неудобно, потому что зачем
Speaker A
мне вот эти вот 7 млн мелких версий, мне в них потом нужно будет ориентироваться, как-то между ними переключаться, но это неудобно. Я бы хотел, чтобы весь его запрос на слияние, он просто в итоге в мастер ветку попал в виде одной какой-то
Speaker A
конкретной версии. И всё, чтобы я мог спокойно между этими двумя версиями туда-сюда переключаться. Да, действительно, такая проблема есть.
Speaker A
Иногда автоматически настроены некоторые политики на облачных хранилищах кода в некоторых компаниях, чтобы все вот эти вот множества комитов, они при запросе на слияние, при одобрении запроса на слияние, они попадали в мастер ветку в виде одного комита. Но иногда этого
Speaker A
нету, и весь этот мусор он берёт и вот так вот попадает в мастерку. Зачем, непонятно. Это уже тема для углублённого изучения. Там нету глобально ничего сложного, но вам самим нужно будет потратить время когда-нибудь потом, в будущем, на то, чтобы научиться все вот
Speaker A
эти вот комиты просто преобразовывать в один комит у себя на ветке. И потом уже при запросе на слияние, при одобрении этого запроса мастер ветку будет попадать только вот один ваш комит и всё. И не будет вот этого мусора. Если
Speaker A
что, это называется git сквош. Но сейчас я вам просто говорю об этом в качестве общего развития. Пока не нужно пытаться это бежать там практиковать. Сейчас ваша задача научиться базово работать с гетом, а это создавать локальный репозиторий, создавать комиты в мастер
Speaker A
ветки и пушить их, создавать ветки для разработки новых фичей, синхронизировать эти ветки с удалённым репозиторием, переключаться между ветками. Значит, мы с вами не обсудили ещё парочку моментов.
Speaker A
Давайте вот для точности. У нас на ветке было вот эти вот два комита. Мы эту ветку синхронизировали с удалённым репозиторием и потом нам одобрили наш запрос на слияние. И мы, собственно, влили эти два комита в мастер ветку. Вот
Speaker A
первый комит у нас. Вот второй комит. Хорошо. Но в чём прикол? Прикол в том, что мы вот этот вот запрос на слияние, мы его одобрили только на удалённом репозитории. То есть на удалённом репозитории мастер ветка теперь имеет актуальную версию не вот эту, а уже аж
Speaker A
вот эту. Но локально у нас мастерка всё ещё имеет актуальную версию, вот такую, потому что наша локальная мастерка ничего не знает о том, что произошло на удалённом репозитории. И чтобы вот эти вот две новые версии мастер ветки у нас
Speaker A
попали в локальной репозитории, чтобы у нас была актуальная версия нашей мастер ветки, нам нужно сделать следующее. Мы берём, переключаемся на мастер ветку, напоминаю, это Git checkout main. И здесь я должен написать git p. Что значит git p? Это значит, что git,
Speaker A
пожалуйста, посмотри на удалённом репозитории, нет ли там каких-то новых комитов относительно того, что у меня локально. И если эти новые комиты есть, то, пожалуйста, привнеси их мне на локальную мастерку, чтобы у меня было актуальное состояние этой самой мастерки. чтобы, если я потом когда-то
Speaker A
создавал себе новые ветки для разработки новых фичей, то я уже делал эти ветки не от устаревшей версии мастера, а от актуальной версии мастера, а это иногда очень важно. Хорошо, я беру и пишу Gitpol. Всё, как мы видим, я добавил
Speaker A
новые изменения в мастер ветки, которые у меня оказались на удалённом репозитории. Теперь, если я зайду и, например, gitlog напишу, и я увижу все комиты, которые были на удалённой мастерветке, я их себе просто локально получил. Вот они все эти комиты. Add
Speaker A
Feature 2, Fixed Capslock и вот Merch Pull Request. То есть ещё один комит, который означает, что было одобрено слияние какой-то там ветки. Ну, повторюсь, это такой больше служебный комит. Именно рабочие комиты, в которых были реальные изменения. Вот они, два
Speaker A
этих комита. Хорошо. И теперь давайте, собственно, посмотрим на то самое переключение между двумя разными версиями. Вот сейчас мы с вами находимся Gitстатус. Мы с вами находимся на самой последней актуальной версии мастер ветки. У нас тут есть все наши фичи.
Speaker A
Фичер 1, Фичер 2, но, допустим, я хочу переключиться на самую изначальную версию мастер ветки. Вот на вот эту вот, где у нас была только лишь один. Ну давайте я это сделаю. Я пишу Gitlog. Я смотрю, вот он first commit, наш самый
Speaker A
первый коit. Я копирую его хэш-код полностью, нажимаю Q, чтобы выйти, и пишу git checkout. И вставляю хэш-код этого комта. Нажимаю Enter. И я вижу, что я переключился на ту версию нашего кода, где у нас была только фичер один.
Speaker A
То есть я взял и у себя локально переключился вот с этой вот последней актуальной версии мастер ветки на вот эту вот версию устаревшую мастер ветки.
Speaker A
Это мне может понадобиться вообще в различных ситуациях. Ну, например, если вот в этой вот последней версии какие-то изменения вдруг попали в мастерку случайно, которые ломают её, и мне, чтобы продолжать работу, мне нужно откатиться на более стабильную версию.
Speaker A
Вот я это только что и сделал. Потом у нас, допустим, самая свежая версия на мастер ветке. Она восстановилась, она стала стабильной, и я хочу обратно на неё переключиться. Ну и для того, чтобы мне вот с этого комита какого-то
Speaker A
отдельного обратно переключиться на целую мастерку, я просто возьму, напишу Git Branch. Вот она наша main ветка.
Speaker A
Скопирую это и git checkout main. Enter. Всё, я опять переключился на самый актуальный комит мастер ветки по мнению моего локального репозитория. То есть пока мы что-то там делали, открою вам секрет, новые комиты в мастер ветке, особенно если это большая команда, могли
Speaker A
попасть. Поэтому, чтобы быть всегда уверенным, что вы работаете на актуальной версии, если для вас это важно, нужно написать Gitpol.
Speaker A
Ну, всё, нам пишет, что как бы всё уже и так обновлено. Всё, теперь мы с вами научились создавать аккаунт на гитхабе.
Speaker A
Мы научились создавать SSH ключи и привязывать их к удалённому GitHub серверу. Мы научились с вами инициализировать локальные репозитории из наших проектов. Мы научились добавлять файлы в определённый комит в рамках нашего локального репозитория. И мы научились делать комиты для локальных
Speaker A
репозиториев. После этого мы с вами научились выгружать наши локальные репозитории, наши локальные ветки на удалённый репозиторий. После этого мы научились создавать уже свои ветки для разработки, делать комиты в рамках этих веток и потом пушать эти фичатки на удалённый репозиторий, чтобы потом у
Speaker A
нас, во-первых, была возможность сохранить все эти наши комиты, а впоследствии мы могли создать запрос на слияние. После этого мы посмотрели, как примерно отдалённо происходит процесс кодрев. А то, когда мы просматривали запрос на слияние, оставляли комментарии, это и есть кодрев. То есть
Speaker A
вы приходите с какими-то изменениями, говорите вашим коллегам: "Я вот сделал то-то, то-то". И ваши коллеги приходят и с умным видом начинают вам там комменты расставлять, где что нужно поправить, где что нужно сделать. И всё. После того, как ваш запрос на слияние был
Speaker A
одобрен, ваши комиты в том виде, котором они были на ветке, переносятся в мастер ветку. Тем самым вы сделали свой вклад или же по-английски говорятбьт, вы законтрибьютили какую-то часть своего кода в мастер ветку на удалённом репозитории. После этого выборочно, как
Speaker A
это происходит в крупных компаниях, берётся какой-то комит произвольный, их тут, повторюсь, может быть большое множество, какой-то произвольный комит, который, например, ваш Teamлит посчитает нужным и стабильным, и на этот комит уже повесится так называемый тег. То есть тег - это обычно какая-то конкретная
Speaker A
версия. Вот так вот вешается версия на ваш комит или не на ваш, может, на какой-то другой. И после этого эта самая версия, она попадает в сборку, которая попадает на удалённый продовый сервер, на котором уже крутится продовая версия вашего приложения, к которому имеют
Speaker A
доступ ваши клиенты, ну или там пользователи вашей соцсети, маркетплейс или чего бы то ни было ещё. Именно поэтому держать мастер ветку в стабильном, актуальном состоянии - это очень важно, потому что может так произойти, что какой-то из этих комитов
Speaker A
попадёт на продовый облачный сервер, на котором уже крутится настоящая версия вашего приложения, которая имеет доступ пользователи ваши. Именно поэтому всю разработку мы производим в рамках какой-то фичветки. И все новые комиты мы создаём в этой фичатке. И если мы эту
Speaker A
фичоветку даже запушим в удалённый репозиторий, то все эти изменения, они окажутся лишь в рамках этой фичатки, и никак на мастер ветку они плохо не повлияют. И какой-то рандомный комит вообще, да, какой-то случайный, он не попадёт вот сюда вот на продовый сервер.
Speaker A
Ну, я думаю, логика понятна. То есть все комиты, которые попадают в мастер ветку, они проходят кодрев, то есть они проходят просмотр и оценку качества. Ну, таким образом мы с вами наглядно рассмотрели, как при помощи гита и как при помощи облачных хранилищ исходного
Speaker A
кода происходит решение проблемы синхронизации командной разработки. Мы это всё с вами пощупали руками, как происходит эта самая разработка, как создаются ветки, как в рамках этих веток создаются комиты и как потом ветки с их комитами загружаются на удалённый репозиторий и потом создаётся запрос на
Speaker A
слияние. После этого процесс cдрев и если всё хорошо с вашей веткой, тогда она заливается, точнее, её комиты заливаются в мастер ветку. Вот так и происходит работа с гитом и удалёнными хранилищами кода. Повторюсь, эти хранилища могут быть как GitHub, так и
Speaker A
GitLab, так и BitBcket, так и ещё там что-то может быть. Но вот GitHub - это просто самое популярное бесплатное.
Speaker A
Также среди больших компаний очень распространён GitLab и BitBcket, но повторюсь, там интерфейс очень похожий, и логика работы касаемо комитов, касаемо веток, касаемо пушей, пулов, запросов на слияние, всё одно и то же абсолютно.
Speaker A
Поэтому тут можно не переживать. Таким образом, мы с вами обсудили, что такое система контроля версий. Мы с вами обсудили и рассмотрели на конкретном примере гит и как он используется в связке с гитхабом. пощупали всё это руками и все дальнейшие наши фичи, все
Speaker A
дальнейшие темы, рассматриваемые, мы будем с вами писать в рамках какой-то отдельной ветки, добавлять туда комиты, а потом создавать запросы на слияние в мастер ветку, проводить кодревью. И если всё хорошо, то есть мы сами себя будем кодревюить, это тоже хорошая практика.
Speaker A
Если всё хорошо, то вот эти вот комиты, они будут попадать в мастер ветку. И мы будем уверены, что у нас мастер ветки актуальная версия стабильная нашего приложения. Ну и в конце я хотел бы ещё показать, как можно какой-то любой
Speaker A
репозиторий на Гитхабе, к которому у вас есть доступ, скопировать себе на локальную машину. Мы переходим на страницу с репозиторием. Неважно, это ваш репозиторий или не ваш, главное, чтобы к нему был доступ. После этого нажимаем здесь на зелёную кнопочку. И
Speaker A
тут есть несколько способов, как мы можем это сделать. Как мы можем скопировать этот репозиторий с удалённого сервера GitHub себе на локальную машину. Первый способ, мы можем скачать zip архив этого репозитория. И второй способ, наиболее популярный, наиболее часто используемый
Speaker A
в разработке - это мы можем скопировать либо HTTP ссылку, либо SSH-ссылку. Если у нас настроены SSH-ключи, а у нас они с вами настроены, то лучше выбрать именно SSH-версию и скопировать эту ссылочку.
Speaker A
Нажимаем здесь копировать. После этого переходим в терминал. После этого в терминале мы пишем git clon и вставляем скопированную ссылку. Нажимаем Enter. И после этого удалённый репозиторий с гитхаба начинает скачиваться нам на локальную машину. Ну, соответственно, чем больше репозиторий, тем больше он
Speaker A
будет скачиваться. Вот это вот скачивание, вот это вот копирование удалённого репозитория себе на локальную машину называется клонирование. То есть говорят склонировать репозиторий. Мы сейчас клонируем удалённый репозиторий себе на локальную машину. Всё, поздравляю. Мы с вами склонировали в данном случае репозиторий с
Speaker A
Кубернетисом. Kubernetis - это такая популярная утилита, которая активно используется в IT-сфере. Соответственно, мы можем посмотреть, что действительно вот этот репозиторий у нас появился на локальном компьютере. можем в него перейти. И здесь увидим, в принципе, все те же самые файлы, которые находились на
Speaker A
удалённом репозитории. Таким образом, благодаря команде Git Clone, мы можем копировать или же клонировать удалённые репозитории себе на локальную машину.
Speaker A
Это крайне часто используется, когда вы, например, пришли на новое рабочее место и вам нужно склонировать удалённый репозиторий с теми сервисами, с которыми вы будете, собственно, работать. Ну, регистрируете себе SSH-ключик в аккаунте, который вам выдадут. После этого переходите на страницу с теми
Speaker A
репозиториями, с которыми у вас планируется работа, и потом копируете здесь вот эту вот SSH-ссылочку, переходите в терминал и пишете gitклон.
Speaker A
Вставляете ссылку, и у вас удалённый репозиторий клонируется прямо вам на компьютер. Следующая тема, которую мы с вами рассмотрим - это базы данных, а также познакомимся с наиболее популярной базой данных Postgress QL. Ну, иногда её ещё называют просто постгрес. Как обычно,
Speaker A
первым делом нам нужно понять, какую проблему для нас будут решать базы данных. Для чего нам, как разработчикам, вообще эти самые базы данных использовать? Наиболее большая проблема, которую решают различные базы данных, в том числе Postg SQL - это проблема
Speaker A
надёжности хранения данных. Давайте я сейчас вам объясню, что я имею в виду. Посмотрим на следующую схему.
Speaker A
Представим, что вот эта вот пунктирная линия - это обозначение какого-то хоста. Хостом может быть как какой-то удалённый сервер, так и наш с вами локальный компьютер, на котором мы запустили какое-то приложение.
Speaker A
приложение пускай из себя представляет то самое RP список дел, которые мы с вами делали в предыдущей части. Как вы можете помнить, мы с вами дела или же задачи, которые в наше приложение добавляются, мы эти самые задачи клали просто в переменную. То есть они все у
Speaker A
нас хранились внутри какой-то переменной, которая существовала только лишь внутри этого самогонд приложения. Но в чём проблема такого подхода?
Speaker A
Проблема такого подхода в том, что если, не дай бог, у нас по каким-то причинам этих причин может быть огромное множество, наше приложение завершится или же просто вылетит с ошибкой, то мы потеряем весь список дел, который пользователь добавлял через наше
Speaker A
приложение. Почему? Потому что этот список дел хранится внутри переменной просто-напросто. Но эта переменная существует только в рамках этого самогонд приложения. Получается, если у нас теряется эээнд приложение, тогда мы теряем весь этот список дел, потому что, повторюсь, он хранился внутри
Speaker A
переменной. Если у нас программа останавливает своё выполнение, то вся её занимаемая оперативная память, она просто вычищается. Ну а, соответственно, у нас все переменные, напомню, из первой части информацию, хранятся внутри оперативной памяти. Это значит, что если у нас приложение завершается выполнение,
Speaker A
то и все его переменные тоже просто вычищаются и удаляются. Получается, имеем проблему, что у нас сохранность данных, а конкретно в нашем примере сохранность списка дел, сохранность наших задач, которые пользователь добавил через наше приложение, эта самая сохранность находится под большим
Speaker A
вопросом. Надёжность в хранении этих данных под большим вопросом. То есть мы вообще не можем ничего гарантировать, что мы хоть сколько-нибудь надёжно можем сохранить этот самый список дел. Почему ещё? Это огромная проблема. Даже если мы с вами напишем идеальное приложение,
Speaker A
которое никогда не завершается и никогда не падает с ошибкой, да, потому что очень часто случается необходимость выкатки новой версии приложения.
Speaker A
Представим, что это у нас Backend Application версия 1, а потом мы в него накинули новых фичей, и мы хотим выкатить Backend Application версии 2.
Speaker A
Как это вообще обычно происходит? Как обычно происходит выкатка новой версии приложения? Сначала берётся и создаётся новая версия приложения. Вот она создаётся, но она ещё не запустилась.
Speaker A
Все запросы, которые шли в старую версию приложения, они дообрабатываются до конца, и потом старая версия приложения просто выключается. Ну, потому что зачем нам держать, как бы, в памяти старую версию приложения вот таким вот образом.
Speaker A
А что это значит для того списка дел, для тех задач, которые лежали в переменной в рамках приложения Backend Application версия 1? Это значит, что мы их просто все потеряем. Просто-напросто.
Speaker A
Даже если мы с вами написали идеальное приложение и просто мы столкнулись с необходимостью выкатки новой версии этого приложения, то мы будем вынуждены потерять весь этот список дел, потому что он у нас хранился в переменной, а мы не можем бесконечно держать вот эту вот
Speaker A
backend Application версия 1. Но это во-первых. А во-вторых, если весь трафик мы направим на Backend Application версия 2, то это самое Backend Application версия 2, оно не сможет получить доступ к переменной, которая находится в рамках, по сути, другой
Speaker A
программы, запущенной. Это невозможно. Поэтому в любом случае, когда мы будем выкатывать новую версию нашего приложения, что мы будем останавливать в старую версию, что не будем, в любом случае доступ, сохранность и вообще надёжность вот этих вот данных, она у
Speaker A
нас очень сомнительна, и мы их просто-напросто будем в любом случае терять. Вот в этом и заключается проблема надёжности хранения данных.
Speaker A
Когда мы храним эти самые данные просто внутри какой-то переменной, это очень ненадёжно. Приложение может само случайно завершиться, упасть с ошибкой.
Speaker A
Мы тогда потеряем все эти данные. Мы можем выкатить новую версию нашего приложения. Точно также все данные в старой версии, они сразу потеряют вообще какой-либо смысл. Ну и миллион других причин. Вот это очень большая проблема.
Speaker A
Как эту проблему мы с вами можем решить как разработчики? А проблема решается при помощи баз данных. Как базы данных решают нашу проблему? Мы заместо того, чтобы хранить какую-то важную информацию внутри переменной, мы начинаем хранить эту самую важную информацию внутри базы
Speaker A
данных. Как мы видим, теперь список дел у нас хранится не внутри переменной вот этой вот, а список дел у нас хранится внутри базы данных. Что это нам даёт?
Speaker A
Это нам даёт, во-первых, то, что если вдруг внезапно наше приложение по какой-то причине остановится или упадёт с ошибкой, то мы не потеряем все данные, которые у нас лежат в базе данных.
Speaker A
Почему? Да потому что эти данные теперь не охранятся внутри переменной, они хранятся внутри базы данных.
Speaker A
Соответственно, если наше приложение просто в любой момент по каким-то причинам перестаёт существовать, то данные, которые мы с вами сохраним в базу данных, они останутся. Что это для нас значит? Это значит, что, во-первых, мы теперь можем не переживать за то, что
Speaker A
наше приложение случайно завершится, потому что его данные всё равно мы сохраним. Мы сохраним наш список дел, мы сохраним каждую задачу, которую пользователь сохранил через наше приложение. Мы уже можем хоть немного ему какие-то гарантии сохранности данных давать. Это во-первых. А, во-вторых, что
Speaker A
будет происходить при выкатке новой версии приложения? Вот у нас backend Application версия 1. И, допустим, мы берём и выкатываем backend Application версия 2. Ну вот, мы взяли, поднимаем новую версию нашего приложения. Значит, старая версия приложения дорабатывает все запросы, которые в неё шли, и после
Speaker A
этого спокойно завершается. В это время новая версия приложения просто возьмёт и подключится к этой самой базе данных.
Speaker A
Таким вот образом. И все данные, которые в эту базу данных положила предыдущая версия приложения, они останутся в базе данных и будут доступны для новой версии приложения, чего мы не могли сказать, когда хранили все данные просто внутри переменной. Таким образом, базы данных,
Speaker A
они предоставляют нам какие-то гарантии надёжности сохранения наших данных. Как минимум в тех случаях, которые мы с вами сейчас рассмотрели. Существует большое множество различных баз данных. Они отличаются в основном тем, каким образом хранят собственно сами данные. Каким образом это значит, что может отличаться
Speaker A
формат хранения данных, а может отличаться место хранения данных. Давайте поговорим именно про место хранения данных. По месту сохранения данных глобально существует два вида баз данных. Первые базы данных сохраняют свои данные на жёстком диске, а вторые базы данных сохраняют свои данные внутри
Speaker A
оперативной памяти. Напомню, что внутри этой же самой оперативной памяти у нас запускается наше приложение, как и, в принципе, все программы, которые запускаются на нашем компьютере или же на удалённом сервере. В чём глобальное отличие этих двух видов баз данных? Те
Speaker A
базы данных, которые хранят свои данные внутри оперативной памяти, они обычно имеют очень высокую скорость обработки данных. Напомню, что оперативная память, она по своей сути гораздо быстрее, чем жёсткий диск. А это значит, что базы данных, которые работают именно в
Speaker A
оперативной памяти, они гораздо быстрее, чем базы данных, которые работают жёстким диском. С другой стороны, наоборот, базы данных, которые работают с жёстким диском, которые свои данные все хранят на жёстком диске, они работают медленнее, чем те базы данных, которые работают с оперативной памятью.
Speaker A
Ну и казалось бы, круто, мы тогда можем просто использовать все базы данных, которые работают внутри оперативной памяти. А что, быстро и круто. Но вот в чём проблема. Напоминаю, что если у нас перезагружается компьютер или если у нас по каким-то причинам приложение, а база
Speaker A
данных - это тоже приложение, просто написанное какими-то другими людьми. Если у нас какое-то приложение планово или же экстренно завершается, либо если у нас в принципе перезагружается компьютер или удалённый сервер, тогда все данные, хранящиеся в оперативной памяти, просто затираются. А это значит,
Speaker A
что несмотря на то, что если мы будем использовать базы данных, которые хранят свои данные внутри оперативной памяти, мы получим огромную прибавку к скорости.
Speaker A
Но с другой стороны, мы с вами получим огромный урон по надёжности сохранения данных по вышеописанным причинам. База данных - это тоже приложение, оно тоже написано живыми людьми. И никто не исключает, что база данных в какой-то момент может экстренно завершиться. К
Speaker A
чему это приведёт? к тому, что мы потеряем все данные, которые эта самая база данных хранила, и мы опять теряем в надёжности. Поэтому базы данных, которые хранят свои данные внутри оперативной памяти, они довольно широко используются, но только лишь там, где
Speaker A
они действительно для этого подходят. Скажу наперёд, это уже, конечно, такая продвинутая тема, но в основном такие типы баз данных используют для кэширования, потому что нам в кэше не нужно хранить какие-то данные очень долго, как бы мы это делали на жёстком
Speaker A
диске, но при этом у нас будет огромная скорость доступа к этому кэшу. Если вы не знаете, что такое кэш, то, в принципе, сейчас можно голову этим не забивать. Главное запомнить, что вот существует такой вид баз данных, которые хранят все свои данные в оперативной
Speaker A
памяти. Из плюсов мы получаем огромную скорость работы с этими данными, а из минусов мы получаем то, что сохранность этих данных под большим вопросом.
Speaker A
Хорошо. Ну и второй вид баз данных, которые хранят свои данные на жёстком диске. Из минусов - это то, что работа с данными будет происходить медленнее, чем с типом базданных, которые работают с оперативной памятью, да. Но тем не менее
Speaker A
обычно этой скорости достаточно. А из плюсов, что мы с вами получаем, так это надёжность и сохранность данных. До тех пор, пока у нас жёсткий диск просто не выйдет из строя, мы не потеряем данные, которые мы сохранили в эту базу данных.
Speaker A
А зачастую этот критерий гораздо более важен, чем вот этот вот минус, связанный со скоростью обработки данных. Ну, представим, мы пишем с вами социальную сеть, и у нас происходит регистрация нового пользователя. Ну, например, социальная сеть ВКонтакте там или Telegram, Одноклассники, у нас
Speaker A
регистрируется новый пользователь. Мы как хотим этого пользователя сохранить внутри переменной. Ну, извините меня, если мы будем новую версию нашего приложения выкатывать, то все зарегистрированные пользователи, они просто исчезнут из нашей социальной сети. Либо если наше приложение случайно там упадёт из-за какой-то ошибки, мы
Speaker A
опять же потеряем всех пользователей, которые были зарегистрированы, потому что данные из переменной, они просто очистятся. Хорошо. А если мы захотим хранить этих самых зарегистрированных пользователей в базе данных, которая работает с оперативной памятью, ну да, мы будем очень быстро сохранять
Speaker A
регистрацию пользователя. Пользователь только зарегистрировался, хоп, и мы сразу же это запомнили в нашей базе данных. Но опять же, в чём проблема.
Speaker A
Если будет какой-то, например, сбой электричества в дата-центре, и у нас компьютеры выключатся и перезагрузятся, компьютеры, я имею в виду сервера, на которых крутится наша социальная сеть, то все зарегистрированные пользователи вместе со всеми данными оперативной памяти просто будут потеряны. Поэтому в
Speaker A
данном случае наиболее оптимально нам всё-таки выбрать базу данных, которая хранит свои данные непосредственно на жёстком диске. Почему? Потому что пускай мы будем подольше ждать саму операцию регистрации, но зато мы сохраним этого зарегистрированного пользователя на жёстком диске, и наш компьютер сможет
Speaker A
перезагрузиться несколько раз, наше приложение может вылететь или, наоборот, мы можем выкатить новую версию нашего приложения, и всё равно все зарегистрированные пользователи у нас будут сохранены, потому что храним мы их именно на жёстком диске. Ну вот так вот мы с вами быстренько прошлись вообще по
Speaker A
тому, какие глобально два вида баз данданных бывают и почему мы должны выбирать тот или иной вид в зависимости от ситуации. Но тут кто-то особо внимательный может спросить: "Ну хорошо, мы вот можем сохранять данные свои в базу данных, которая хранит информацию
Speaker A
на жёстком диске". А если жёсткий диск выйдет из строя, получается, мы потеряли все данные социальной сети, которые мы собирали несколько лет. Аккаунты пользователей, сообщения пользователей, диалоги пользователей, фотографии, аватарки и статусы, всё потеряем, да?
Speaker A
Потому что если мы ограничиваемся одним жёстким диском, так оно может произойти, и рано или поздно жёсткий диск выйдет из строя. Поэтому, как делают обычно, для сохранения важных данных, ну, таких как регистрация пользователя или там новое сообщение пользователя, эти все данные
Speaker A
сохраняются в базу данных, которая информацию свою содержит на жёстком диске. Но на подстраховке у этого жёсткого диска обычно есть сразу несколько других жёстких дисков, которые просто берут и перекопируют на себя его информацию. Тем самым, если в один момент вот этот вот основной жёсткий
Speaker A
диск, он выйдет из строя, то наша база данных просто продолжит свою работу уже не с этим жёстким диском, а с каким-нибудь другим. Просто потому, что они содержат полную копию тех данных, которые были у нас на изначальном жёстком диске. И таким образом
Speaker A
обеспечивается надёжность сохранения данных. Мы мало того, что их пишем на жёсткий диск и не боимся, что у нас приложение упадёт или выкатится новая версия или у нас перезагрузится компьютер или сервер, так мы ещё и защищены от выхода из строя какого-то
Speaker A
конкретного жёсткого диска. Тем самым мы можем сохранять данные практически бесконечное количество времени. Но мы, конечно же, с вами в рамках нашего курса не будем себе покупать несколько жёстких дисков. Да и в принципе разработчик этим практически никогда не занимается.
Speaker A
Обычно это всё делают специальные люди из отдельной команды. Нам лишь с вами важно понимать принцип работы этих баз данных, почему хранить на жёстком диске - это гораздо надёжнее, чем хранить в оперативной памяти. Ну и при этом понимать, почему в принципе тогда
Speaker A
существуют базы данных, которые хранят что-то в оперативной памяти. Ну, повторюсь, потому что это просто многократно быстрее. Ну, таким образом мы с вами рассмотрели проблему надёжности хранения данных и то, каким образом базы данных нам помогают эту самую проблему решить. В рамках этой
Speaker A
части мы с вами познакомимся с одной из наиболее популярных и часто используемых баз данных. Это Postgress, часто называется просто Postgress. Postgress является базой данных, которая хранит свои данные именно на жёстком диске. При правильном использовании эта база данных может обеспечить корректную работу
Speaker A
вашего приложения и высокую скорость обработки данных даже при высоких нагрузках. Именно поэтому эту базу данных зачастую используют практически везде, где можно и нельзя. А за счёт того, что свои данные она сохраняет на жёсткий диск, мы ещё и можем обеспечить
Speaker A
надёжность сохранения этих данных. Первым делом давайте поговорим про то, в каком формате Посгress сохраняет данные, которые мы в неё передаём. Посгress сохраняет свои данные в табличном формате. Что значит табличный формат?
Speaker A
Ну, это можно сравнить просто с Excelтаблицами. Я думаю, многие хоть раз в своей жизни открывали Excel и видели, что из себя представляет Excelтаблица.
Speaker A
Так вот, Постгрыс сохраняет свои данные в таблицах, которые очень сильно напоминают эти самые Excel-таблицы. Вот я тут для примера нарисовал какую-то табличку. Опять же, это очень похоже на обычные Excel-таблицы. У нас есть какие-то столбцы и есть какие-то строки.
Speaker A
Сейчас мы разберём, как это работает всё вместе. Ну, представим, что мы опять же с вами продолжаем разрабатывать наше ээнд приложение список дел, и при помощи нашего приложения пользователь может сохранять какие-то свои задачи. У задачи есть несколько полей. Соответственно, мы
Speaker A
до этого сохраняли вот эти вот все задачи, которые нам передаёт пользователь, просто в переменную. Ну а мы с вами уже обсудили, какие из этого могут вытекать проблемы. Хорошо, теперь мы хотим сохранять это не в переменной.
Speaker A
Теперь мы все новые поступающие задачи хотим сохранять в базу данных. да не в какую-то базу данных, а именно в Postgress, которая обеспечивает надёжность сохранения наших данных, потому что сохраняет их на диске. Опять же, нужно понимать, что всегда бывают
Speaker A
какие-то случаи, из-за которых вы можете потерять свои данные. Это всегда может быть. Но благодаря тому, что мы сохраняем данные на диске, мы хотя бы их не будем терять из-за простого перезапуска нашего приложения или перезапуска нашего сервера. Хорошо, получается, мы хотим хранить вот такие
Speaker A
вот задачи в нашей базе данных. Хорошо. Каким образом эта задача у нас ляжет на табличное представление? Ну, давайте посмотрим на вот эту вот небольшую табличку, которую я тут привёл. Тут сопоставление типов данных из Голга к типам данных из Postgl. То есть какой мы
Speaker A
вывод можем из этого всего сделать? Что PostGRSQL тоже хранит данные в определённых каких-то форматах. То есть у нас есть строковый формат, у нас есть bull формат, у нас есть integer, у нас есть какое-то время и также у нас есть
Speaker A
NIL, ну, некоторый его аналог. И помимо этого ещё очень много всяких форматов данных, которые поддерживают Посрес.
Speaker A
Давайте тогда смотреть, как наша структура Task будет ложиться вообще на табличное представление данных в Постгрес. У нас у структуры Task какие есть поля? title, description, completed created completed каждое поле этой структуры - это какой-то определённый столбец. То есть title -
Speaker A
это какой-то определённый столбец. Description - это какой-то определённый столбец. Completed - это какой-то определённый столбец. Created это столбец. Ну и completed это тоже столбец. Хорошо, пока мы, наверное, ещё не совсем понимаем, к чему всё идёт. Но давайте сделаем так, как сказано, что
Speaker A
каждое поле нашей структуры - это будет какой-то отдельный столбец в базе данных. Ну, поехали. У нас есть tйтle.
Speaker A
Title поле называется именно внутри структуры, но внутри базы данных принято немножко другое именование использовать с маленькой буквы. И при необходимости, если несколько слов составляет название какого-то поля, то эти слова разделяются нижним подчёркиванием. Например, created. Мы бы с вами писали как
Speaker A
created. Нижнее подчёркивание AD. Вот таким вот образом. Но у нас tйтle просто с маленькой буквы. Хорошо, какого типа у нас должен быть tйтлle? Смотрим, что в Гошке - это тип строка. Ну а в постгресе у нас строка - это warрчар. Хорошо,
Speaker A
давайте я это и запишу. Что это у меня? Рchчар. О'кей, первый столбец мы с вами создали. Идём дальше. Дальше у нас desрипtion. Ну хорошо, я пишу desption.
Speaker A
Ну, это поле в базе данных будет называться точно так же, только повторюсь, с маленькой буквы description. Хорошо, у нас тоже типа строка. Строка - это у нас warchar.
Speaker A
Значит, это у нас будет тип warchar. О'кей. Дальше мы идём у нас completed. Ну, поехали. Значит, completed поле с большой буквой у нас называется в нашей структуре. С маленькой буквой оно будет называться в базе данных, потому что так
Speaker A
принято именовать столбцы в таблицах постгрес. Completed. Ну и смотрим на тип. То есть у нас в структуре на уровне приложения у поля complet тип буll. Ну а соответствие типу бул в постгресе - это булин. Ну давайте я вот просто его вот
Speaker A
так вот скопирую. Вот он наш булин. Вы могли заметить, что почему-то здесь я всё называю капслоком, да? Потому что в постгресе принято какие-то встроенные типы, встроенные названия, встроенные команды именовать капслоком.
Speaker A
Соответственно warchr капслоком. Это не критично, но просто служит таким хорошим тоном при работе с ипостгресом. Поэтому я вот буду придерживаться этого стиля и warчаr напишу с большой буквы, то есть капслоком.
Speaker A
Хорошо. Title, description, completed. Разобрались. Дальше у нас created. Ну, time - это у нас time stamp. Хорошо.
Speaker A
Значит, я здесь пишу created. В базе данных это поле будет называться created следующим образом. Ну и тип у него - это stamp. Estamp - это дата плюс время. То есть какая-то конкретная дата плюс какое-то конкретное время суток. Хорошо.
Speaker A
И последнее - это completed add. Ну, давайте его и сделаем. Completed add. Так называется поле у нас на уровне приложения, на уровне базы данных будет называться completed следующим образом.
Speaker A
Ну и тип у него, давайте вот тут обсудим поподробнее. До этого мы с вами все поля, которые сохраняли, они не имели у нас указателей на уровне приложения, а поле completed AD имеет указатель на уровне приложения. А давайте вспомним,
Speaker A
для чего мы этому полю завели указатель. А всё просто, потому что у нас все остальные поля в этой структуре Task, они по-любому должны быть заданы и у них по-любому должно быть какое-то конкретное значение. У заголовка, у описания, выполнено или не выполнено
Speaker A
время создания. Все эти поля должны были иметь какие-то конкретные значения, определённые на момент создания структуры. Но вот информация о том, когда задача была выполнена, её может ещё не существовать на момент создания задачи, потому что мы задачу только создали, мы ещё в душе не чаем, когда
Speaker A
эта задача будет выполнена. Поэтому мы здесь завели с вами указатель, чтобы изначально приравнять его. А что это значит? Это значит, что есть у нас указатель, в таком случае просто ещё нету время выполнения задачи. Хорошо, как эта вся парадигма ложится на базу
Speaker A
данных? По умолчанию в базе данных любое поле может быть нул. Ну, нул - это такой некоторый, в некотором смысле аналог. По умолчанию в базе данных любое поле может быть нул. Поэтому в базе данных сейчас, как мы её описали, у нас вообще, что
Speaker A
title, что description, что completed, что created, всё может быть у нас нул. Но мы не хотим этого. Мы хотим, чтобы у нас вообще-то title, description, completed и created at не могли быть nul. Что если мы создаём какую-то задачу, сохраняем её в базу данных, мы
Speaker A
хотим, чтобы это точно не было нул. Мы хотим, чтобы эти поля точно не были ну, потому что иначе у нас задача не имеет смысла. Какой смысл для нас имеет задача, у которой нету её заголовка или описания? Не имеет смысла никакого для
Speaker A
нас эта задача. Поэтому мы должны базе данных явно указать, что некоторые поля у нас не могут быть нал. Как это делается? Это делается просто после типа поля пишется следующим образом not. Вот так вот это делается. Что это значит?
Speaker A
Это говорит в базе данных о том, что если идёт попытка сохранения какой-то задачи, у которой тайтл будет нал, в таком случае вернуть ошибку и не сохранять эту задачу, потому что мы явно указали, что tйтle не должен быть у нас
Speaker A
нал. То же самое у нас должно быть из desption, потому что description у нас, я напомню, не может быть n. У нас всегда у задачи должно быть какое-то описание.
Speaker A
То же самое с полем completed. Мы всегда должны конкретно, чётко понимать, у нас задача либо выполнена, либо нет. А если мы с вами получим вот эту структуру task и поле completed будет null. И что это значит? Ну, типа, нету информации о том,
Speaker A
выполнена ли задача или нет. И всё. И мы тогда не будем знать, что нам вообще дальше делать с этой информацией. Такого допускать нельзя. Поэтому поле completed тоже у нас not null, то есть оно не может быть null. Created, тоже самое,
Speaker A
not null. А вот completed, давайте посмотрим, какой тип имеет completed, тоже time. Хорошо, значит, это timeст у нас. Но в отличие от всех остальных полей, поле complet у нас может быть нал. Почему? Да потому что когда мы задачу только создали, мы ещё не знаем
Speaker A
время её выполнения. А это значит, что это поле может быть нал. Ну и поэтому мы тут notл не пишем. Всё хорошо, мы с вами описали поля сущности, которые мы будем сохранять в этой таблице в рамках нашей базы данных Postg SQL. Давайте я подпишу
Speaker A
эту таблицу. Это у меня будет таблица задач. Это наша таблица задач. Следующим образом я вот её подписываю. О'кей, мы с вами описали поля, которые будут у каждой сущности в этой таблице. Ну, то есть мы описали по сути поля задачи,
Speaker A
которая будет сохраняться в эту таблицу. каким образом происходит вставка новых значений в эту самую таблицу. А всё просто. Если столбцы у нас обозначают какие-то конкретные поля, то вот строки - это уже будут конкретные сущности, которые мы будем в этой таблице
Speaker A
создавать. Давайте посмотрим на примере. Допустим, я хочу вставить какую-то задачу. Хорошо. У этой задачи какой будет тайтл? Ну, пускай будет выгул собаки. Выл собаки. Хорошо. Описание какое? Ну, описание какое-то будет длинное, там, условно говоря, проснуться с утра и
Speaker A
что-то там дальше начать делать, короче. То есть вот какое-то длинная описание. Потом completed, ну, она пускай у нас будет false, то есть ещё не выполнена у нас эта самая задача, мы её только создали. Created. Ну, давайте какое-то время укажем, например, пускай это будет
Speaker A
вот 14 ноября двадцать пятого года, там 10 утра 0 секунд. Вот это время создания задачи. Ну, а время выполнения у нас будет нал, просто потому что мы ещё не выполнили задачу. Хорошо. Вот мы с вами взяли и вставили в таблицу одну какую-то
Speaker A
конкретную задачу. Мы с вами взяли и сохранили в рамках нашей таблицы задач в базе данных Пос информацию о какой-то задаче. Вот пользователь добавил задачу, мы её сохранили. Если пользователь будет дальше добавлять новые задачи, они просто будут добавляться в следующие
Speaker A
строки этой самой таблицы. Ну давайте посмотрим на ещё каком-то примере. Например, у нас будет следующая задача - это домашка. Домашка.
Speaker A
Описание у этой задачи пускай будет прийти со школы, ну и там какое-то длинное описание, там какую конкретно домажку нужно сделать, какому дню и так далее. Значит, completed у нас тоже будет false, потому что мы только создали её. Created at у нас будет, ну,
Speaker A
пускай это вот будет у нас там 10 ноября. И тут время у нас будет там 16:35 мину 0,5 секунд. Вот так вот. И completed тоже у нас будет нал. Вот мы с вами взяли и сохранили в таблице задач,
Speaker A
в базе данных погрс вторую задачу. Вот она, домажка, описание её и дальнейшая информация об этой задаче. Таким образом, у нас сохраняются данные в базе данных Посгрыз. Повторюсь, имеют они табличный формат. Ну, очень похожи на Excelтаблицы. В принципе, в
Speaker A
Excelтаблицах то же самое. У нас есть какие-то столбцы с названиями и есть строки, где каждая строка - это какая-то отдельная сущность, добавляемая в эту самую таблицу. База данных у нас поддерживает не только сохранение каких-то строк, но также изменение
Speaker A
отдельных столбцов этих строк. То есть, допустим, мы с вами можем выполнить задачу домашка. Как это будет выглядеть?
Speaker A
Ну, во-первых, нам нужно completed поменять на true. Ну, а complet выставить какое-то конкретное время.
Speaker A
Пускай вот completed у нас будет вот двадцатое число. Всё. То есть мы можем в рамках уже созданных строк брать и обновлять их значения. Также база данных. Постгрес у нас поддерживает различную выборку. То есть мы можем попробовать оттуда достать абсолютно все
Speaker A
задачи, которые у нас сохранены в рамках какой-то таблицы. Также мы можем достать оттуда задачи только те, которые у нас выполнены. А все задачи, которые у нас не будут выполнены, они просто не попадут в эту выборку. И мы с вами
Speaker A
получим лишь то, что попало в выборку. При этом то, что в выборку не попало, оно не удаляется. Оно просто нам не будет возвращено в ответе. Ну, это я чуть-чуть наперёд забегаю. Мы, конечно же, это всё руками пощупаем на настоящий
Speaker A
момент. Главное понять, что база данных Пос, она хранит свои данные в табличном формате. Табличный формат - это, собственно, сами таблицы. То есть все данные в позгрес, они хранятся в табличном виде. Ну и мы с вами рассмотрели конкретный примерчик, как бы
Speaker A
мы сохраняли структуру Task в таблице в базе данных Постгрес. Далее, что нам необходимо обсудить про постгress - это то, что постгressс - это реляционная база данных. Что значит реляционная?
Speaker A
Реляционная - это от английского слова relationship, то есть отношения. Но это отношения не между двумя людьми, а немножко другого рода отношения. Сейчас, конечно же, рассмотрим, что я имею в виду. То есть реляционная база данных - это база данных, где возможны отношения
Speaker A
между сущностями. И PostGSQL как раз-таки относится именно к этому типу баз данных. То есть это реляционная база данных. Ну вот я возьму табличку из нашего предыдущего примера. Это наша таблица задач уже нам знакомая. И сейчас мы с вами рассмотрим, что же за
Speaker A
отношения у нас между сущностями возможны в PostGSQL. Представим, что у нас помимо таблицы задач есть ещё таблица пользователей. Таблица пользователей. Как она бы у нас могла выглядеть? Ну, в принципе, то же самое табличное представление данных, только я бы хранил для пользователя, ну, к
Speaker A
примеру, его, то есть фамилию, имя, отчество. Ну, и - это что такое? По сути, ФИ - это какая-то строка.
Speaker A
Получается, мы могли бы это хранить в типе warchr. Я хочу, чтобы у меня в любом случае, если я какого-то пользователя сохраняю, было задано, потому что пользователь без FO, тогда непонятно, кто это вообще такой. Поэтому FIO я сделаю not вот таким вот образом.
Speaker A
Ну и как бы это поле у меня называлось в рамках базы данных, ну, я бы мог это назвать что-то типа full name. Всё, это наш первый столбец. Что бы я ещё хотел сохранить для пользователя? Ну, допустим, я бы хотел сохранить номер
Speaker A
телефона. Номер телефона в рамках базы данных это бы у нас называлось Phone Number. Ну и тип для номера телефона тоже можно было бы взять, на самом деле, просто Warch. Почему? Потому что номер телефона - это всё-таки не число, потому
Speaker A
что у нас может он начинаться с +7, к примеру, а знак плюс ты не сохранишь в числовом типе данных. Поэтому обычно всё-таки для номеров телефона берут строки. Ну а в случае с постг строка - это у нас warchachr. Not n сюда ставить
Speaker A
не буду, потому что пускай это будет опциональное поле. Человек может как оставить свой номер телефона для нас, так и не делать этого, если он не хочет.
Speaker A
Ну, поэтому я не буду тут ставить Note nal никакого. В принципе, у меня phone number, пускай может быть N, если пользователь не захотел делиться номером телефона. Хорошо, значит, вот мы с вами создали вроде как таблицу пользователей.
Speaker A
А теперь представим, что к нам пришло наше начальство и начало требовать новую фичу, что теперь у каждой задачи должен быть автор этой задачи, чтобы потом мы могли отслеживать, какую конкретно задачу какой человек создал. Потому что, если у нас все эти задачи будут
Speaker A
сохраняться в одну таблицу задач множеством пользователей, то в таком случае все пользователи будут видеть все задачи, и не будет понятно, какой пользователь конкретно какую из задач создал. Ну, повторюсь, потому что вот у нас есть наше приложение, и в него
Speaker A
разные пользователи шлют свои запросы на создание задач. Это приложение принимает задачу и потом сохраняет просто это всё в базу данных. К чему это приведёт? К тому, что мы наплодим множество задач. А чьи это задачи? Да. Какого конкретного пользователя, там, первого или второго,
Speaker A
мы уже не узнаем. Поэтому нам приходят с требованием создать новую фичу, что мы должны помимо всей информации о задаче сохранять ещё информацию о том, кто был автором этой задачи. Ну давайте мы это с вами, собственно, и сделаем. То есть
Speaker A
поле у нас сразу я назову его, как бы оно называлось, просто в рамках базы данных. автор. И автор у нас какой тип должен иметь? Ну, пока непонятно.
Speaker A
Давайте посмотрим, чтобы мы с вами сохраняли в таблице пользователей. Поехали. Первого. Значит, Fullame. Ну, Иванов Иван Иванович. Хорошо. Номер телефона он, давайте оставил. +7 999 88 77 66. Хорошо, это наш первый пользователь. Дальше у нас ещё будет второй пользователь. Пускай это уже
Speaker A
будет Петров. Пётр Петрович. Ну и номер телефона он нам не оставил. То есть номер телефона у нас будет, получается, нал. Вот так вот.
Speaker A
Пока у нас будет всего лишь два пользователя в нашей таблице пользователей. Хорошо. И теперь, когда мы захотим к каждой конкретной задаче привязать какого-то конкретного пользователя, что мы можем сделать? То есть, что мы можем взять, какую информацию о пользователе для того,
Speaker A
чтобы уникально, ключевое слово уникально, этого пользователя идентифицировать. Что бы было, если бы мы с вами взяли номер телефона? Ну, вообще, вроде как, у одного человека должен быть уникальный номер телефона. В таком случае мы по номеру телефона смогли бы уникально идентифицировать,
Speaker A
какой конкретно пользователь создал текущую задачу. Но вот в чём проблема. У нас номер телефона - это опциональное поле. Пускай у нас будет третий пользователь Васильев Василий Васильевич.
Speaker A
И он тоже взял и не указал номер телефона. И что получается? Допустим, задачу "Домашка" у нас создал Петров Пётр Петрович. Номер телефона у него нал. Хорошо. А потом Васильев Василий Васильевич попробует взять все задачи, где автор нал и что и он получит задачу.
Speaker A
Петрова Петра Петровича, потому что тут номер телефона нал. А нал у нас не может уникально идентифицировать конкретного пользователя. Что я имею в виду под словом уникально? А имею в виду то, что если мы здесь вот под полем автор видим
Speaker A
нал, то мы не можем конкретно понять, кто это: Петров Пётр Петрович или Васильев Василий Васильевич. Кто из них?
Speaker A
Мы не можем понять однозначно. Это значит, что мы не можем уникально идентифицировать пользователя, если мы в поле автор возьмём номер телефона.
Speaker A
Хорошо, номер телефона нам не подходит. Ну вот, не подходит нам по вышеописанной причине. Хорошо, мы можем взять. Ну казалось бы, всё круто. Берём автора и ставим сюда фио человека, кто создал эту задачу, да? То есть мы сразу можем
Speaker A
видеть, что выгул собаки у нас создал Иванов Иван Иванович. Но в чём, ребятки, может быть проблема? Я думаю, многие из вас уже могли догадаться, проблема в том, что у нас в этом мире не исключено такое явление, как полная тёска. Что это
Speaker A
значит? Это значит, что рано или поздно к нам в систему может прийти и зарегистрироваться человек, у которого ФИО тоже Иванов Иван Иванович. Ну и, допустим, он тоже номер телефона себе не установил. И что и получается? Вот этот вот Иванов Иван Иванович и вот этот вот
Speaker A
Иванов Иван Иванович - это два разных человека. Но в таблице задач у нас указано автор Иванов Иван Иванович. И как я из этой информации должен понять, какой Иванов Иван Иванович в итоге является автором этой задачи? Вот этот вот или же вот этот вот? Опять
Speaker A
непонятно. Никак мы не можем по фамилии, имени, отчеству однозначно уникально идентифицировать пользователя, который создал эту задачу. На самом деле, мы так можем очень долго гадать, что же нам выбрать. Мы можем пытаться заставлять пользователей указывать, например, свой адрес, чтобы однозначно
Speaker A
идентифицировать, кто какую задачу создал, то есть по адресу проживания. Но пользователи могут не захотеть оставлять свой адрес проживания для обычного приложения списка дел. То есть, ну, странно, я хочу просто список дел себе сохранять. Зачем вам знать, где конкретно я живу? Это же бред. Да и даже
Speaker A
если бы мы заставили пользователей это всё сделать, то никто не исключает, что какой-нибудь пользователь просто от балды бы поставил какой-нибудь адрес, и оказалось, что у двух разных пользователей будет один и тот же адрес.
Speaker A
И мы опять по адресу не сможем однозначно идентифицировать, какой же в итоге из этих двух или более пользователей создал вот эту вот задачу в угол собаки. То есть проблема. Нам нужен какой-то уникальный идентификатор каждого пользователя, чтобы конкретно понимать, какой именно пользователь, с
Speaker A
каким именно аккаунтом создал вот эту вот задачу или вот эту задачу или любую другую. Как эта проблема решается средствами Postgl. А в Postgl-очень давно, и выход из этой проблемы сделали следующий. просто берётся и таблице добавляется одно поле, один столбец ещё.
Speaker A
Это у нас столбец, который так и называется: идентификатор. Вот он. И обычно, ну, по-английски идентификатор - это просто ID. То есть мы создаём для каждого отдельного нашего пользователя идентификатор какой-то.
Speaker A
Этот идентификатор может быть различных типов, но наиболее такой простой и распространённый - это обычный integer.
Speaker A
Вот у нас тип integer. И так как этот идентификатор у нас создан для того, чтобы однозначно и уникально идентифицировать каждого конкретного пользователя в нашей таблице пользователей, то ещё делается приставка Primary Kay. Что значит primary K? Вот так вот это выглядит. Primary K. как
Speaker A
раз-таки вот эта вот приставка Primary K и означает, что данный столбец создан именно для однозначной и уникальной идентификации каждой конкретной сущности в рамках нашей таблицы. Ну и создание этого самого Primary K мы можем просто делегировать базе данных, то есть она
Speaker A
сама будет для каждого пользователя создавать его уникальный вот этот вот primaryкей, уникальный идентификатор. Ну обычно просто по порядку создаётся. То есть для этого пользователя, который был самый первый создан, уникальный идентификатор будет один для последующего, два для последующего -
Speaker A
три, потом четыре, ну и так далее. Что это нам дало? А это нам дало то, что теперь в таблице задач для того, чтобы уникально идентифицировать автора этой задачи, мы можем не гадать с ФИ, не гадать с адресами, не гадать с номерами
Speaker A
телефона и так далее, и так далее. Мы можем просто взять уникальный идентификатор пользователя. и сохранить его себе. И что это значит?
Speaker A
Это значит, что задачу выгул собаки у нас конкретно создал пользователь с идентификатором один. А с идентификатором один у нас во всей системе существует лишь один пользователь. Вот он. Например, задачу Домашка у нас мог создать пользователь с идентификатором три. Что это значит? Это
Speaker A
значит, что задачу домашка однозначно и бесповоротно создал пользователь с идентификатором 3. И никак иначе по-другому. Вот что нам дают уникальные идентификаторы. Даже из названия следует, что они нам помогают уникально идентифицировать каждую отдельную сущность в рамках целой таблицы. А вот эти вот стрелочки - это и
Speaker A
есть те самые отношения между сущностями, про которые мы с вами говорили в самом начале, когда мы разбирали, что вообще значит реляционная база данных. То есть вот эти вот стрелочки, когда мы в рамках какой-то одной таблицы создаём какое-то поле,
Speaker A
создаём столбец, который служит для того, чтобы сохранять уникальные идентификаторы других сущностей из других таблиц. Вот такой вот вид связей и называется в постгресе отношениям. А благодаря тому, что постгресс в принципе поддерживает такой механизм отношения между двумя разными сущностями, а задача
Speaker A
и пользователь - это две разные сущности, именно благодаря этому постгрез и называется реляционной базой данных. Повторюсь, реляционная от слова relationship, то есть отношения. У нас между двумя разными таблицами создались отношения, благодаря чему мы можем понять, что вот эта задача выгул собак,
Speaker A
она непосредственно относится к пользователю Иванов Иван Иванович с таким-то номером телефона с уникальным идентификатором один. Ну и опять же благодаря этим же самым отношениям мы можем понять, что задача Домажка у нас создана никем иным, кроме как пользователем с уникальным
Speaker A
идентификатором 3. Васильев Василий Васильевич. Ну вот просто возможность создавать такие вот отношения говорит нам о том, что мы с вами пользуемся реляционной базой данных, к коим постгрес и относится. И тут может появиться вопрос: нужно ли нам создавать уникальный идентификатор для таблицы
Speaker A
задач? То есть, нужно ли нам ещё тут делать вот этот вот столбец ID, чтобы сохранять уникальные идентификаторы для каждой отдельной задачи конкретно в рамках рассматриваемой фичи, где нам для каждой задачи нужно сохранить её автора.
Speaker A
Нет, не надо. Но зачастую, даже если мы сейчас не знаем, для чего нам уникально идентифицировать каждую отдельную запись в нашей таблице задач, даже если мы сейчас этого не знаем зачем, то в будущем нам это может понадобиться. и скорее всего понадобится. Поэтому, как
Speaker A
принято делать? Обычно, когда мы с вами создаём какую-то новую таблицу, то по умолчанию всегда вместе с ней будет идти столбец уникальных идентификаторов.
Speaker A
Просто на всякий случай, если нам вдруг внезапно в будущем понадобится каким-то образом для чего-то уникально идентифицировать, ну, в нашем случае, например, каждую конкретную задачу. А для чего нам это может понадобиться? А вот, к примеру, мы показываем пользователю его задачи. И потом
Speaker A
пользователь хочет выполнить эту самую задачу, отметить её как выполненную. Хорошо, мы тогда должны будем эту задачу найти в таблице задач и поменять у неё там false на true. Хорошо. Как мы найдём, какую конкретно задачу хочет выполнить пользователь? Ну, вообще, мы
Speaker A
можем найти её по заголовку, но а что, если два разных пользователя создали задачу с одним и тем же заголовком? То есть один пользователь создал задачу с заголовком "Выгул собаки". Да, вот первый, например, и, к примеру, четвёртый пользователь создал тоже
Speaker A
задачу с заголовком "Выгул собаки". Вот он, четвёртый наш пользователь. Вот я изобразил, как будто бы четвёртый пользователь тоже создал задачу про выгул собаки. Ну, просто у него как бы своя эта задача, но он создал задачу с ровно таким же заголовком. И получается,
Speaker A
что если мы захотим в таблице задач отметить задачу с заголовком "Выгул собаки как выполненную", то мы можем случайно отметить лишние задачи. Ну вот, к примеру, у меня пользователь один, то есть Иванов Иван Иванович, захотел отметить задачу как выполненную. Хорошо,
Speaker A
мы идём в базу данных, ищем задачу по заголовку и отмечаем её как выполненную. Но проблема в том, что у нас тут помимо этой задачи выгул собаки есть ещё другая задача выгул собаки. И мы можем случайно её тоже отметить как выполненную.
Speaker A
Почему? Потому что у нас нету уникального идентификатора этой самой задачи. Вот мы его не создали. И всё. И мы опять вынуждены придумывать, за что бы нам зацепиться, чтобы как-то уникально идентифицировать конкретную задачу в таблице задач. Но мы можем не
Speaker A
изобретать велосипед, мы можем просто по умолчанию создать вот этот вот столбец ID уникальных идентификаторов. И у нас база данных позаботится о том, чтобы для каждой отдельной записи создать её уникальный идентификатор. И потом, если, например, пользователь один захочет выполнить свою задачу, мы именно задачу
Speaker A
с уникальным идентификатором один отметим как выполненную. А задачу с уникальным идентификатором 3 мы трогать не будем. Почему? Потому что мы будем ориентироваться по этим самым уникальным идентификаторам. Вот именно поэтому уникальные идентификаторы обычно просто в самом начале создания таблицы сразу же
Speaker A
описываются, чтобы потом, если что, в будущем, опираться на эти самые уникальные идентификаторы, чтобы быть уверенным, что мы в данный момент работаем с конкретной записью в таблице, а не с какой-то другой записью, которая просто на неё похожа. Как мы видим в
Speaker A
нашем примере, это относится и к пользователям, у которых может быть одинаковое ФИО или номер телефона, так и к задачам, у которых может быть одинаковый заголовок. То есть даже если мы поставим ограничение, что один пользователь не может создать две задачи
Speaker A
с одинаковым заголовком, то всё равно может быть такое, что два разных пользователя создадут задачу с одинаковым заголовком. То есть мы же не можем так сказать пользователю, что типа ты не можешь создать задачу с таким заголовком, потому что с таким
Speaker A
заголовком задачу создал другой пользователь. Но это же смешно. Поэтому у нас вполне вероятно два одинаковых заголовка в таблице. Поэтому используем уникальные идентификаторы. Ну, таким образом мы с вами разобрались, почему вообще постгрес является реляционной базой данных, какую роль в этих самых
Speaker A
отношениях у нас играют уникальные идентификаторы и почему, в принципе, важно при создании таблицы не забывать помимо остальных столбцов создавать ещё и столбец уникальных идентификаторов.
Speaker A
Также стоит добавить капельку терминологии. Обычно принято вот этот вот уникальный идентификатор ещё называть первичный ключ. А primary K как раз-таки переводится как первичный ключ.
Speaker A
То есть это первичный ключ иногда называется. Но опять же можно назвать primary K, можно назвать уникальный идентификатор, можно назвать первичный ключ, но просто иногда называют это первичным ключом. А ключ, при помощи которого мы будем ссылаться на этот первичный ключ, называется внешним
Speaker A
ключом. То есть вот это вот у нас это внешний ключ. Это первичный ключ, а это внешний ключ. Почему внешний? Да потому что мы при помощи этого ключа ссылаемся на какую-то сущность во внешней таблице.
Speaker A
Но это у нас опять же первичный ключ. Это внешний ключ. Это первичный ключ. Просто чтобы у вас было понимание. И нам с вами кое-что ещё необходимо узнать про Postgl. Так как PostGRQL - это реляционная база данных, то для
Speaker A
взаимодействия с ней используется специальный язык составления запросов, который называется SQL. То есть всегда, когда я говорил, что мы в базу данных вставляем какую-то новую запись, либо делаем какую-то выборку по каким-то записям, либо когда мы изменяем с вами какую-то запись в таблице, либо, может
Speaker A
быть, когда мы, в принципе, удаляем какую-то запись из таблицы. Для всего этого используется специальный язык запросов SQL. Сейчас мы с вами разберём самые-самые такие простенькие примерчики с этим языком запросов, но полностью все тонкости SQL мы с вами в этом уроке
Speaker A
разбирать не будем. Почему? Потому что тема эта достаточно глубокая, и только лишь на одну эту тему мы можем потратить несколько десятков часов изучения.
Speaker A
Поэтому, чтобы нам не растягивать наш курс погошки на несколько частей вперёд, где мы изучали бы только один лишь SQL, в этом курсе мы рассмотрим лишь базовые примеры и основу языка SQL.
Speaker A
Дополнительные же материалы, которые необходимы для того, чтобы дотянуть свой уровень знания СQэля, до такого уровня, чтобы можно было писать настоящие коммерческие проекты и проходить собеседование. Все эти дополнительные материалы я вынес в наше приватное сообщество, потому что иначе наш курс
Speaker A
погошки превратится в курс по базам данных, а это можно ещё смело частей 5 по 12 часов наклепать. С одной стороны, хорошо, но с другой стороны, к сожалению, мы тогда закончим наш курс по Гошке, доведём его до конца. Лет так
Speaker A
через пять. В интернете огромное количество отличных материалов именно по SQL, огромное количество тренажёров по SQэлю, много хорошей теории, много хорошей практики. И в нашем приватном сообществе я дам все необходимые ссылки, ведущие на материалы, которые лично я считаю полезными и годными. Ну давайте
Speaker A
рассмотрим простейшие примеры SQL-запросов, которые мы могли бы с вами писать для того, чтобы как-то работать с этими нашими таблицами в рамках нашей базы данных. Ну хорошо, давайте представим, что мы хотим в таблицу пользователей добавить какого-то нового пользователя. Как бы это выглядело? Ну,
Speaker A
во-первых, у нас таблица пользователей должна как-то называться именно на уровне базы данных. На уровне базы данных эта таблица, скорее всего, бы называлась что-то типа users.
Speaker A
Хорошо, у нас есть название этой таблицы, у нас есть название её полей, а значит, мы теперь можем начать с ней работать. Давайте посмотрим, как бы мог выглядеть SQLзапрос для создания нового пользователя в таблице пользователей.
Speaker A
Для того, чтобы создать новую запись в какой-то таблице, в реляционной базе данных при помощи SQL, нам необходимо начать писать следующие слова: insert into, что значит вставить в. После этого мы пишем название таблицы. У нас это users, insert into users. После этого мы
Speaker A
должны в скобочках через запятую перечислить поля, ну или же название столбцов, которые мы с вами будем вставлять. В нашем случае это full name и phone number. Почему я в этом перечислении не указал столбец ID? Да потому что это у нас primary ke, а это
Speaker A
значит, что мы можем настроить базу данных так, чтобы она эти уникальные идентификаторы генерировала сама. И нам не нужно заботиться о том, какой конкретно уникальный идентификатор будет сгенерирован, потому что база данных это за нас сделает. Хорошо, мы должны вставить только лишь full name и phone
Speaker A
number. Вот об этих двух столбцах база данных уже сама не может позаботиться, поэтому мы уже своими руками должны конкретные значения этим полям задать.
Speaker A
После этого я могу либо на той же строке продолжить, либо на новой строке начать писать values. И тут в скобочках я должен также через запятую передать значения, которые будут вставлены в соответствующие столбцы. Значит, full name пускай у меня будет Иванов.
Speaker A
Игорь Владимирович. Ну а phone number пускай у него будет там +7 88 999 1122. Ну и в конце можно ещё точку запятой поставить, чтобы явно указать, что мы закончили этот SQL-запрос. Хорошо, давайте посмотрим внимательней, что этот SQL-запрос вообще
Speaker A
будет делать. Во-первых, мы говорим при помощи insert in, что мы хотим создать новую запись в какой-то таблице, пока непонятно в какой. Потом мы указываем конкретную таблицу, в которой мы будем создавать новую запись. Ну, то есть у нас есть сейчас две таблицы: таблица
Speaker A
задач и таблица пользователей. Мы явно указываем, что будем создавать новую запись в таблице пользователей. Вот она, users. Вот наша таблица. Хорошо. Дальше в скобочках мы перечисляем, какие именно столбцы мы будем вставлять. Ну, в нашем случае нам только full name и fone
Speaker A
number нужно вставить. Почему нам не нужно вставлять идентификатор, мы уже обсудили, потому что база данных при правильной её настройке сделает это за нас. Хорошо. После этого мы пишем ключевое слово values и через запятую в порядке соответствующем том, что мы
Speaker A
писали в изначальных скобочках, мы передаём значение полей, которые мы будем вставлять. Соответственно, вот этот вот весь SQL запрос, он приведёт к тому, что у нас создастся новая запись в таблице users следующего вида.
Speaker A
Уникальный идентификатор у нас генерируется автоматически. После этого FIO возьмётся из запроса, то есть именно Иванов Игорь Владимирович у нас тут будет вставлено. И номер телефона тоже возьмётся из запроса. Соответственно, этот номер телефона будет вставлен сюда в таблицу. Таким образом, мы конкретно
Speaker A
при помощи SQL-запроса создали новую запись в таблице Users. Ну и точно таким же образом мы бы могли создавать новые записи в таблице задач. То есть таблица задач, опять же, у неё должно быть какое-то название на уровне базы данных.
Speaker A
Ну пускай это будет tasks. Вот так вот и всё. И мы бы писали insert into tasks. В скобочках бы передавали те поля, которые мы хотим вставить в нашу базу данных. И после этого писали бы ключевое слово values и через запятую перечисляли
Speaker A
значение этих полей. Пронумерую этот SQL запрос циферкой один. Ну и давайте посмотрим, как бы мы с вами вообще вставляли новую запись нового пользователя, если он нам не указал номер телефона. Ну давайте, это вот будет под циферкой два у нас запрос.
Speaker A
Начало запроса точно такое же. Insert, int, users, то есть, да, в какую таблицу мы вставляем. После этого в скобочках мы должны перечислить поля, которые мы будем вставлять. Phont number мы будем вставлять? Нет. Поэтому я его убираю. И после этого Фио пускай будет
Speaker A
какой-нибудь Максимов Пётр Леонидович. Ну и номер телефона мы не указываем, потому что до этого мы указали, что номер телефона мы не будем передавать.
Speaker A
Значит, давайте следовать своим же правилам и не будем передавать номер телефона. При помощи такого SQL-запроса у нас создастся следующая запись. Опять же Primary K при правильной настройке у нас генерируется автоматически, поэтому он тут будет сам сгенерирован. После этого мы вставим фамилию, имя, отчество.
Speaker A
Максимов Пётр Леонидович. Вот он. А номер телефона мы не передали, но так как у нас в описании поля, в описании этого столбца не указано нол, значит, он может быть нал, и он будет нал, если мы его не передадим. Соответственно, здесь
Speaker A
будет нал. Вот таким вот образом. Ну вот мы разобрали самые такие минимальные примерчики, как вообще можно вставлять новые записи в какую-то таблицу при помощи языка составления запросов SQL.
Speaker A
Давайте я тут подпишу, что это у нас добавление данных в таблицу. Хорошо. Дальше давайте прикинем, что если я хочу, к примеру, опять же из этой таблицы users получить всех пользователей, которые у меня есть, у которых задан номер телефона. Вот мне
Speaker A
интересно получить всех пользователей, у которых есть номер телефона заданный. У которых номера телефона заданного нету, я не хочу их получать в выборке. Хорошо.
Speaker A
Как я должен написать тогда SQL запрос? Я должен написать select, то есть получить, и потом через запятую я должен передать название полей, название столбцов, которые я хочу получить в этой выборке. Если я хочу получить все три столбца, тогда я должен их перечислить.
Speaker A
То есть select ID запятая full name запятая phone number. Таким образом, я получу в выборке таблицу со всеми тремя этими столбцами. Если меня не интересует в данном запросе уникальный идентификатор пользователя и я хочу получить лишь только полное имя и номер
Speaker A
телефона, тогда я могу не писать вот это вот ID сюда. Хорошо. Написал сект. Потом через запятую перечислил название столбцов, которые я хочу получить. Потом я должен опять же на той же строчке или на новой строчке написать ключевое слово
Speaker A
from. После него идёт название таблицы, из которой я получаю, собственно, данные. В данном случае я уже сказал, что я хочу получить из таблицы users поля full name и phone number. Если я оставлю вот этот вот запрос, как сейчас,
Speaker A
то мне в ответе вернётся вот эта вся таблица, потому что тут никаких фильтрационных условий не было задано, но только лишь её столбцы, full name и phone number. То есть мне вернётся таблица вот в таком вот виде. Вот конкретно вот эти данные я получу.
Speaker A
благодаря такому SQL-запросу. То есть я тут сказал, я хочу получить столбцы full name и phone number из таблицы users.
Speaker A
Ну, собственно, этот запрос побежал по изначальной таблице users и выбрал из неё два столбца: full name и phone number следующим образом. И, собственно, вот он наш результат. Ну хорошо, давайте я пока этот запрос помечу циферкой один.
Speaker A
Что будет, если я чуть-чуть поменяю этот запрос? Если я в этом запросе напишу вроде как всё то же самое, то есть select full name phone number, то есть full name phone number from users, то есть вот из этой вот таблицы users. Но
Speaker A
после этого добавлю ещё фильтрационное условия. Я напишу where phone number is not null. Что это значит? Это значит, что я хочу из таблицы users получить поля full name и phone number. Вот эти вот самые поля. То есть вот эти вот
Speaker A
столбцы. Я делаю выборку по этим столбцам. И из этой таблицы, которая у меня получается, я хочу взять только строки, у которых fone number is not null. То есть где fone number не null. А это у меня вот эта строка
Speaker A
и вот эта строка. Вот эти строки мне не подходят, потому что тут phone number у меня nл, а значит они мне не подходят. Таким образом, благодаря вот такому вот запросу в ответе я получу вот такую вот табличку.
Speaker A
Ещё раз, что тут произошло? Я сделал выборку столбцов full name и phone number и с таблиц users. Вот таблица users, вот столбцы full name и phone number. По ним я делаю выборку. Вот у меня full name, ну и вот phone number.
Speaker A
Потом я говорю, что дай мне только те строки, где phone number не нал, а не нал он у меня только раз и два. И всё. В итоге я получил в ответе таблицу, у которой есть два столбца full name,
Speaker A
phone number и две строки, подходящие под фильтрационный параметр. То есть только те пользователи, у которых Phone number у меня не нал. Соответственно, таким образом мы при помощи селекта можем делать различную выборку данных из наших таблиц внутри базы данных. Ну,
Speaker A
давайте я тут подпишу, что это у меня выборка данных. Ну, давайте ещё рассмотрим случаи, когда пользователь с уникальным идентификатором 2 решил удалить свой аккаунт. Что это значит? Это значит, что не всегда, конечно, зависит от бизнес-требований, но в некоторых
Speaker A
случаях мы должны взять и удалить этого пользователя из нашей таблицы пользователей. Как это будет выглядеть?
Speaker A
Мы опять начинаем писать SQL запрос. Начинается он со слова delete, то есть удалить. Потом мы пишем, из какой таблицы удаляем. From users, delete from users. И после этого я должен поставить условия, при каком условии я буду удалять записи в этой таблице. Where ID
Speaker A
= 2. Что тут произошло? Я сказал, что я хочу из таблицы users удалить записи, где ID, то есть где уникальный идентификатор равен двум. Что произойдёт? Изначально таблица users возьмёт и удалит из себя вот эту вот строчку с уникальным идентификатором 2.
Speaker A
Оп, всё, этого пользователя больше нету в нашей таблице пользователей. Всё. Ну и давайте я сюда подпишу, что это у нас удаление данных.
Speaker A
Стоит понимать, что SQL не заканчивается на том, что мы с вами обсудили. Мы рассмотрели лишь малую часть от всех его просто безграничных возможностей по работе с данными, по созданию, по выборкам, по удалению, по обновлению данных. То есть мы с вами даже ещё не
Speaker A
обсудили, как можно в рамках уже существующей какой-то строки обновить какой-то определённый столбец. Например, пользователь с уникальным идентификатором 3 у нас обновляет себе номер телефона, добавляет его. Мы можем это спокойно взять и обновить ему какое-то поле. В этом нет никакой
Speaker A
проблемы. И что, кстати, важно, мы, когда с вами удалили запись, которая была у нас на второй строке, то уникальные идентификаторы всех пользователей оставшихся никуда не сдвинулись. Они остались на месте именно потому, что это уникальные идентификаторы. Если у нас удаляется
Speaker A
пользователь с уникальным идентификатором два, это значит, что удалённый уникальный идентификатор перестаёт существовать в таблице. Это не значит, что мы должны вдруг переназначить зачем-то все другие уникальные идентификаторы. Почему? Да потому что у нас тогда окажется, что задачу Домашка создал Иванов Игорь
Speaker A
Владимирович внезапно, хотя он, вообще-то, не создавал эту задачу. А всё потому, что у нас съехали уникальные идентификаторы. Нет, такого быть не должно. Уникальные идентификаторы у нас никуда не съезжают. У нас они остаются так, как были. даже несмотря на то, что
Speaker A
мы там удалили запись с уникальным идентификатором 2. Ну и что значит е просто не будет? Все остальные уникальные идентификаторы должны остаться на месте. Ну и если мы руками туда не будем специально как-то лезть, то база данных обеспечит нам это
Speaker A
правило, и у нас останется корректное состояние наших данных. Таким образом, мы с вами обсудили три особенности базы данных PostgressQL. Первое - это то, что в базе данных постгress у нас данные имеют табличный формат. Второе - это то, что постгress у нас - это реляционная
Speaker A
база данных, а значит, что мы между разными сущностями можем создавать отношения. И третье - это следствие второго. Из-за того, что Постгрес у нас это реляционная база данных, мы для взаимодействия с этой базой должны использовать язык составления запросов SQL. Для того, чтобы потренироваться в
Speaker A
написании SQL-запросов и узнать кое-что новое про базы данных, я вам крайне настоятельно рекомендую перейти в наше приватное сообщество и ознакомиться с дополнительными материалами. Ну а теперь я считаю, что мы достаточно с вами начальной теории изучили про базы данных
Speaker A
и, в частности, про Postgl. И теперь уже можно переходить к практике. Ну а практика начинается, естественно, с того, что мы должны установить себе на наши локальные машины эту самую базу данных Постгress. Для того, чтобы скачать и установить Postgress на
Speaker A
Window, переходим в браузер и в поисковой строке пишем Postgress Windows install. Ну, собственно, переходим по первой же ссылке. Это официальный сайт Postgress. Вот www.postgressql.org.
Speaker A
Ну, здесь вот нажимаем на вот эту вот кнопку Download the Installer. Выбираем версию постгреса, которую мы хотим скачать. Ну, можно просто выбирать самую последнюю. Я вот выберу конкретно 18.1. Вот у меня [откашливается] Windows, поэтому я выбираю установщик под Windows.
Speaker A
Автоматически начинается скачивание этого установщика, поэтому просто ждём, пока он докачается. Наш установщик скачался. Теперь мы его запускаем. Идёт установка всяких сторонних пакетов, необходимых для запуска самой Postgress.
Speaker A
Вот мы видим окно установки этого самого постгреса. Нажимаем далее. Выбираем путь, куда постгрес будет установлен.
Speaker A
Ну, я оставлю стандартный путь. Далее мы с вами можем выбрать, что будет установлено вместе с постгресом на наш компьютер. Сам Postgrгress сервер, PG admin, Stack Builder и Commandline Tools. Что есть, что Postg SQL сервер - это, по сути, и есть та самая база
Speaker A
данных, про которую мы с вами говорили. Это и есть та сущность, в которую мы с вами сможем отправлять SQL-запросы. Она их будет как-то обрабатывать и сохранять данные на диске. Хорошо, с этим разобрались. Что такое PGДМИ? Ну, тут вот справа написано только по-английски,
Speaker A
что PG adдмин - это графический интерфейс для того, чтобы управлять и работать с Pastgress QL. Что это значит?
Speaker A
Это значит, что PG adдмин - это просто какой-то интерфейсик удобный для работы непосредственно с самой базой данных. То есть мы можем этот интерфейс и не устанавливать, но я вам крайне советую всё-таки его установить, потому что иначе вам придётся искать какие-то
Speaker A
другие пути взаимодействия с базой данных. Далее Stackбиilder. Ну, как мы видим, что это просто какая-то утилита, которая в теории может нам помочь скачать и установить дополнительные утилиты. Пускай пока она у нас будет. И Commandline Tools - это всякие утилиты,
Speaker A
которые доступны из терминала, которые могут нам помочь взаимодействовать с базой данных. тоже лишними не будут.
Speaker A
Поэтому я вам просто советую все четыре галочки оставить на месте, чтобы мы установили всё, что предлагает нам установщик. Нажимаем далее. Теперь нам предлагается выбрать директорию, в которой будут храниться данные, которые мы будем класть в базу данных. То есть
Speaker A
это как раз-таки то самое место на жёстком диске, куда база данных будет сохранять информацию, которую мы в неё будем записывать. Ну, можете выбрать какую-то свою директорию. Я оставлю всё как есть в том виде, в котором мне это предлагает установщик. Нажимаю далее.
Speaker A
Теперь нам нужно придумать пароль для самого главного пользователя в Posg. То есть Погз, так же как и, например, Windows, позволяет нам создавать различных пользователей, у которых есть различные права. То есть какие-то пользователи могут полностью управлять базой данных, а какие-то пользователи
Speaker A
могут лишь читать из неё, но не могут удалять или изменять уже существующую информацию. Но нам сейчас нужно придумать пароль для самого главного пользователя. Ну, я особо придумывать ничего не буду, чтобы не забыть. Всё равно эта база данных будет у меня
Speaker A
развёрнута только на моей локальной машине. Это не база данных, которая разворачивается у нас в продакш-сервере, поэтому тут не обязательно придумывать какой-то сложный пароль. Но я выберу простенький пароль из четырёх символов и нажму далее. Теперь нам нужно выбрать порт, на котором будет работать база
Speaker A
данных. Вот тут я крайне советую оставить стандартный порт 5432 и ничего не менять, потому что этот порт, он практически всегда подразумевается, что занят по сгрес, если она в принципе есть на машине. Нажимаем далее. Тут можно выбрать регион, в рамках которого ваша
Speaker A
база данных будет думать, что она находится. Но я ничего пока этого делать не буду. Это уже тема для отдельного разговора. Оставлю дефолт. Ну и всё.
Speaker A
Нажимаю далее и устанавливаю базу данных. Ну, а вместе с ней ещё и несколько полезных утилит. Установка завершена, но нам предлагается ещё запустить стакбидер для того, чтобы посмотреть, что ещё он нам может предложить дополнительного к установке.
Speaker A
Ну, давайте это и сделаем. То есть я тут оставляю галочку и нажимаю финиш. Мы попадаем в стакбиer, и он нам предлагает подключиться к нашей базе данных. Здесь вот мы можем в контекстном меню найти нашу установленную базу данных, на
Speaker A
указанном порту нажать и нажать дальше. И здесь вот целый список того, что нам может предложить дополнительно установить этот самый стакбилдер. На самом деле сейчас нам здесь ничего не надо, но просто на будущее, если у вас появится какая-то необходимость в
Speaker A
дополнительных пакетах, то вы можете знать, что в том числе некоторые из них можно будет найти в стакбилдере. Ну а так сейчас, повторюсь, нам ничего дополнительно тут устанавливать не нужно, поэтому я просто его закрою. Да, я уверен, что я хочу выйти отсюда.
Speaker A
Теперь мы с вами установили базу данных и необходимые для неё дополнительные пакеты. Теперь давайте откроем графический интерфейс нашей базы данных.
Speaker A
Открываю пуск и пишу PG admin 4. Ну или же, может быть, какая-то другая версия.
Speaker A
Самое главное, что это именно PG adдмин. Запускаем. Ждём, пока он запустится. И наш PGМИН открылся. Теперь давайте попробуем из этого графического приложения подключиться непосредственно к базе данных. Слева вверху мы видим вкладочку сеvers. И если я на неё нажму, тут как
Speaker A
раз-таки раскрываются все доступные базы данных PassG SQL, которые есть у меня на системе. Ну, в нашем случае это одна-единственная база данных, которую мы только что скачали. Вот она, Passgre SQL1. Теперь нажимаю правой кнопкой мыши на неё и connect сервер. Тут я должен
Speaker A
ввести пароль, который я задал при установке базы данных. Ввожу этот самый пароль, нажимаю окей. И если мы видим, что у нас тут раскрылись дополнительные вкладочки, появились какие-то таблицы, это значит, мы с вами успешно подключились к базе данных из
Speaker A
графической админки PG adдмин. Для того, чтобы нам скачать и установить постгрес на Linux, в частности на Ubunту, открываем веб-браузер и пишем Ubunту install.
Speaker A
После этого пролистываем и находим официальный сайт Postgress. Нажимаю. И здесь у меня буквально есть подсказки, какие команды мне нужно использовать, чтобы я мог установить Постгрес. Первым делом мне нужно добавить репозиторий с этим самым Постгресом. Я просто скопирую приложенные команды и вставлю их в
Speaker A
терминал. Вставляю в терминал и нажимаю Enter. Ввожу пароль. Нажимаю ещё раз Enter. Всё, я добавил удалённый репозиторий с постгресом. Теперь я могу установить непосредственно сам Постгрес. Для этого я скопирую вот эту вот команду и обращу внимание, что тут именно восемнадцатая
Speaker A
версия. Конкретно на этой версии мы будем рассматривать дальнейшее взаимодействие с Постгресом в рамках текущего урока. Копирую и вставляю опять в терминал. Нажимаю Enter. Нажимаю Да, всё. Теперь я скачал и установил постгсно сноубунту и могу посмотреть, что она у меня действительно скачалась и
Speaker A
работает. Для этого я пишу system статус и тут Postgress QL. Enter. И как я вижу, что у меня Postgressвис теперь активен, всё с ним хорошо. И он у меня как раз-таки был запущен вот только-только что, потому что я его,
Speaker A
собственно, только и скачал. Всё, зелёненькая enйabled, значит, я скачал постгрес, он у меня запущен и работает.
Speaker A
После того, как мы установили постгрес на Убунту, нам необходимо с вами сейчас создать пароль для подключения к этой самой базе данных. На самом деле, в базе данных Postgress есть возможность создавать различных пользователей. В рамках этой базы данных у каждого
Speaker A
пользователя будет свой логин, будет свой пароль, и есть возможность каждому пользователю назначать свои какие-то права. То есть буквально как в операционной системе, только в рамках базы данных. Для чего нам в рамках базы данных может понадобиться создавать несколько пользователей? Ну, например,
Speaker A
мы можем создать одну какую-то группу пользователей, у которой есть доступ ко всем возможным операциям над данными.
Speaker A
добавление данных, изменения данных, удаление данных. То есть это какие-то админские права. И также мы можем отдельно создать группу пользователей, у которых есть права только лишь на чтение данных, и всё. То есть удалить что-то или поменять они уже не могут. Ну вот
Speaker A
такое своего рода разграничение доступа. Так вот, в базе данных Постгрес всегда по умолчанию создаётся пользователь, который называется, как ни странно, постгрес. Но вот именно на Убунту, когда мы с вами скачиваем Посгресql базу данных, то пароля для этого пользователя
Speaker A
Постгрес автоматически не задаётся, и мы должны сами придумать этот пароль. И благодаря этому паролю через пользователя постгрес мы будем подключаться к базе данных. Что нужно для того, чтобы создать пароль? Мы пишем в терминале судоo уu postgess psql poostgess. Нажимаем Enter. Мы с вами
Speaker A
попали в терминальную утилиту для работы с базой данных постгрес. Зашли мы от имени пользователя Постгры. И теперь давайте создадим для него пароль. Тут пишем обратный сш password пробел поgress. То есть мы сейчас будем для пользователя Postgress задавать пароль.
Speaker A
Нажимаем Enter. Ну и, собственно, мы должны этот самый пароль ввести. Я ввожу свой пароль. Ещё раз ввожу. Всё, я создал пароль для пользователя Погрес.
Speaker A
Теперь мы с вами сможем спокойно подключиться к базе данных Погрес, к нашему пользователю Postgress по установленному паролю. Пишу exit. вышел из этой консольной утилиты для взаимодействия с базой данных. И теперь нам осталось установить графическое приложение для работы с базой данных. То
Speaker A
есть мы, в принципе, могли бы работать с базой данных и через эту консольную утилиту PSQL. Но это не всегда удобно.
Speaker A
Гораздо удобнее и нагляднее пользоваться графическим приложением в некоторых случаях, особенно пока мы с вами на начальном этапе и только учимся.
Speaker A
Соответственно, нам нужно скачать эту графическую утилиту для работы с постгресом. Графическая утилита называется PG adдмин. Скачать её можно большим количеством разных способов, но в рамках операционной системы Уунту это делается довольно просто через пакетный менеджер Snap. Возможно, на вашей
Speaker A
системе унту не будет пакетного менеджера Snap, но в таком случае необходимо его просто руками взять и установить. Для этого необходимо прописать обычную команду суда UPT install snapd. Нажимаем Enter, вводим пароль для нашего пользователя. И вот пакетный менеджер Snap теперь установлен
Speaker A
на ваш дистрибутив Уунту, и мы можем им пользоваться. Для этого нам нужно написать в терминале следующее: су, Snap, install, PG, admin 4. Нажимаем Enter, и идёт установка этого самого графического приложения для работы с базой данных. Нам нужно просто
Speaker A
подождать. Всё, поздравляю, мы с вами скачали PGДми. Давайте теперь его запустим и через этот самый PGмин подключимся к нашей установленной базе данных Postgress. Пишу в поиске PG adдмин. И вот он у меня находится.
Speaker A
Нажимаю, жду, пока он запустится. Вот открылся наш PGМИ. И тут нас сразу просят задать пароль для самого ПГадмина. Ну, потому что в самом ПГ-админе мы с вами можем сохранять некоторое множество логинов и паролей. И поэтому на сам PGми нам тоже
Speaker A
предлагают установить пароль. Ну, давайте это и сделаем. Я тут введу такой же пароль, который я установил на саму базу данных.
Speaker A
Окей. Теперь слева вверху есть у нас вкладочка сеvers. Нам необходимо на этой вкладочке нажать правой кнопкой мыши регистр сервер. Имя, в принципе, можем задать любое. Я напишу studgress ser сервер. После нажимаю conneнеction.
Speaker A
И здесь нужно ввести хост для подключения к базе данных. У нас база данных располагается на том же самом компьютере, на той же самой системе, что и графический интерфейс PGMIN.
Speaker A
Получается, подключаться мы будем на local host. Порт 5432 - это стандартный порт для работы с базой данных. То есть на этом порту постгрес поднимается, и на этом порту мы можем к этой базе данных установить подключение. Username poгress. И пароль вводим тот, который мы
Speaker A
установили. Ввожу пароль, нажимаю save. Всё. Если у вас здесь вот появились всякие вкладочки, здесь появились всякие графики, значит, вы успешно установили базу данных Постгрес на вашу систему. Вы успешно установили графический интерфейс для работы с этой базой данных и успешно
Speaker A
подключились к базе данных из этого графического интерфейса. А значит, мы можем идти дальше. Для того, чтобы скачать и установить постгress на MacOS, мы можем перейти в браузер и написать в поисковой строке MacOS Postgress install. Переходим на официальный сайт
Speaker A
Постгреса. Здесь выбираем Download the installer. И здесь выбираем установщик, который нам необходим. Нам в данном случае необходим установщик на MacOS, и я выберу Postgrгess версии 18.1. Нажимаю установить.
Speaker A
Ну и сейчас у меня пойдёт скачивание этого самого установщика. Мне нужно дождаться, пока он до конца у меня скачается. Всё, установщик скачался.
Speaker A
Запускаем его и запускаем установку. Да. Нажимаю открыть. Ввожу пароль, чтобы подтвердить открытие установленного файла. И у меня запустился сам установщик. Нажимаю далее. Тут он мне предлагает место, куда я смогу установить сам постгрес и всякие сторонние пакеты. Я оставлю стандартное
Speaker A
месторасположение. Далее. Теперь нам предлагается выбрать, что конкретно мы хотим установить в рамках текущей установки. Давайте посмотрим, что у нас есть. Postg SQL сервер. То есть это и есть наша база данных. Это сервер нашей базы данных. к которому мы сможем
Speaker A
подключаться, в который мы сможем отправлять свои запросы и сохранять данные. Это нам по-любому нужно. PGDIN.
Speaker A
PGDIN - это графический интерфейс для взаимодействия с базой данных Postgess. Очень удобная вещь, поэтому тоже необходимо оставить. Stackбиilder - это уже утилита, которая помогает устанавливать при необходимости всякие сторонние пакеты. Нам она сейчас, конечно, не сильно нужна, но я бы просто
Speaker A
оставил на будущее, если вдруг понадобится. Ну и Command line Tools - это набор утилит, которые доступны из терминала и при помощи которых я могу взаимодействовать с базой данных, тоже не будет лишним. Я советую оставить.
Speaker A
Нажимаю далее. Теперь мне необходимо выбрать место расположения на жёстком диске, куда Постгрес будет сохранять все данные, которые я в него пишу. То есть это то самое место на диске, куда Постгрес сохраняет переданную в него информацию. Ну, я оставлю стандартное
Speaker A
месторасположение, которое мне предлагает установщик. Нажимаю далее. Теперь мне нужно придумать пароль для самого главного пользователя в базе данных Постгрес. То есть в базе данных Постгрес, так же как и в операционных системах, можно создавать разных пользователей, у которых будут различные
Speaker A
права. Например, может быть какая-то группа пользователей, у которой есть полное право на управление базой данных.
Speaker A
То есть они могут и создавать новые записи, и менять существующие, и удалять уже существующие. А можно создать отдельную группу пользователей базы данных, которые могут, например, только лишь читать из неё, но не могут её менять, для того чтобы предоставить
Speaker A
кому-то доступ только на чтение. Ну, по умолчанию у нас в Постгресе создаётся один-единственный пользователь. Он называется, как ни странно, Постгрес. И у него будут полные права на работу с базой данных. Теперь нам нужно придумать для этого пользователя пароль. Какой-то
Speaker A
сложный пароль. Тут нет смысла подбирать, потому что эта база данных у нас будет запущена лишь на нашем компьютере в учебных целях. Поэтому я тут довольно простенький пароль введу.
Speaker A
Подтверждаю пароль и нажимаю далее. Теперь нужно выбрать порт, на котором будет развёрнута база данных. Крайне советую оставить стандартный порт 5432.
Speaker A
Нажимаю далее. Тут мы можем выбрать кодировки, которые будут использоваться нашей базой данных. Но я тут советую оставить всё стандартно, как есть, ничего не трогать, потому что это уже тема для более глубокого изучения.
Speaker A
Нажимаю далее, далее, далее, и всё, пошла установка базы данных на компьютер. Всё, у меня всё скачалось, установилось. Тут я могу убрать галочку, чтобы у меня этот стакбилдер не запускался в самом начале, и нажимаю финиш. Всё, я скачал и установил базу
Speaker A
данных Постгрыс. Теперь нам нужно из графического интерфейса PG Adдми подключиться к базе данных Postgress, чтобы удостовериться, что мы установили всё корректно. Для этого открываем PGмин. Я просто в поиске пишу PG adдмин и открываю его. Всё, у нас запустилось
Speaker A
графическое приложение для работы с базой данных. для того, чтобы подключиться к базе данных. Здесь слева вверху мы можем раскрыть вкладочку сеvers, и у нас сразу же будет предложено ввести пароль для подключения к какой-то базе данных. Ну, мы можем это
Speaker A
сейчас закрыть и посмотреть, что у нас тут сейчас доступна. Одна единственная база данных для подключения. Это та база данных, которую мы только что с вами скачали и установили. Pass 18. Нажимаем правой кнопкой мыши connect сервер. Тут всё, что нам нужно сделать - это ввести
Speaker A
пароль, который мы задали при установке базы данных. Я ввожу этот самый пароль, нажимаю окей. И всё. Если у вас слева появились вот эти вот вкладочки, если справа у вас появились эти графики, значит, вы успешно скачали и установили базу данных и успешно скачали и
Speaker A
установили графический интерфейс для работы с этой самой базой данных. Плюс из ПГадмина подключились к постгресу.
Speaker A
Значит, всё сделано корректно, и мы можем идти дальше. Теперь нам необходимо удостовериться в том, что мы из нашего гошного приложения можем подключиться к базе данных. Для чего это нужно? Почему это важно? Да потому что вся работа с базой данных в backндend приложениях
Speaker A
происходит непосредственно изнутри эээнд приложения в базу данных. То есть руками мы в базу данных практически никогда с вами не будем залезать. Зачастую, в принципе, у человека нету доступа к базе данных, которая находится на настоящем продовом сервере для того, чтобы
Speaker A
случайный сотрудник компании не мог получить доступ к конфиденциальным данным пользователей. Ну и, в принципе, чтобы нам с вами на каждый пользовательский сетевой запрос руками не бежать и не добавлять в базу данных какие-то записи, всё это за нас будет
Speaker A
делать наше гошное приложение. И именно для этого мы должны уметь изнутри гошного приложения подключаться к базе данных. Ну давайте это сейчас попробуем сделать. Для этого я создам себе новую ветку. То есть пишу gitстаatus.
Speaker A
Вижу, что я сейчас нахожусь на main ветке, но у меня изменённый main. Файл. Ну давайте посмотрим, что за изменения.
Speaker A
А изменения у меня тут только те, что я добавил какой-то комментарий. Ну давайте я сейчас создам себе новую ветку Git Brench. Назову её Feature Postgress.
Speaker A
Enter. После этого пишу GitBch. Вижу, вот она у меня создалась эта ветка. Копирую её название и на неё я переключаюсь. Git checkout feature pogress. Всё, теперь я на веткер Posgress. Пишу gitстаatus. Ну вот вижу, что у меня тот незакомеченный див, те
Speaker A
незакомеченные изменения на мастер ветки, они у меня автоматически перенеслись на feчер/погress ветку. Хорошо, давайте смотреть, что я тут вообще добавил за комментарий какой-то непонятный. На самом-то деле, этот комментарий - это так называемая строка подключения к базе данных Постгрыз. Что
Speaker A
это такое, мы сейчас с вами увидим на конкретном примере. Я её скопирую. И теперь я создам себе дополнительную директорию, назову её Feature Postgress.
Speaker A
И в ней я создам ещё одну поддиректорию, назову её Simple Connection. Всё, в этой директории Simple Connection я создам файл simple connection.go.
Speaker A
И в нём я попробую создать подключение к базе данных и проверить, что это подключение у меня валидное и рабочее.
Speaker A
Ну, собственно, пакет Simple Connection. Тут можно ставить нижнее подчёркивание, можно не ставить. В принципе, зависит от того, какой стиль именования пакетов принят в вашей команде, в вашей компании. Напишу тут функцию, пускай она у меня называется check connection.
Speaker A
Всё, она у меня не будет ничего принимать, не будет ничего возвращать. И всё, что внутри этой функции будет происходить - это непосредственно само подключение к базе данных и проверка этого подключения. Как мы будем подключаться с вами к базе данных? На
Speaker A
самом-то деле есть большое множество вариантов, как мы можем это сделать. Но я советую и буду пользоваться дальше в этом курсе библиотекой PGX для языка программирования GO. Что это за библиотека? Это очень популярна библиотека, которая много где используется для работы с базой данных
Speaker A
Postg SQL изнутри языка программирования GO. Эта библиотека используется и в целях тестирования, и в целях написания настоящих бэкэнд приложений. В больших компаниях, маленьких компаниях очень много где используется. Это не значит, что это единственная, возможная библиотека для работы с базы данных
Speaker A
Постгрес из Гошки, но тем не менее довольно популярна библиотека. И если вы научитесь пользоваться этой библиотекой, то перейти на какую-то другую для вас уже будет несложно. Ну давайте теперь эту библиотеку мы с вами установим. Я вот перейду сюда на сайт с
Speaker A
документацией, и тут у меня будет в левом верхнем углу ссылочка для скачивания этой самой библиотеки. Я копирую эту ссылочку, перехожу обратно в наше приложение, открываю терминал. Ну и так как я вижу, что мы уже являемся модулем, то мне не составляет никакой
Speaker A
проблемы взять себе в зависимость какую-то библиотеку. То есть стать зависимым от какой-то библиотеки, ну или же говоря другим языком, подключить себе в мою программу эту самую библиотеку.
Speaker A
Делается это, напоминаю, через команду, а потом вставляем ту скопированную ссылку. Нажимаю Enter. Всё, у меня добавилась библиотека PGX.
Speaker A
Теперь я могу начать ей пользоваться. Давайте перейдём обратно в наш файл Simple Connection. И здесь я пишу следующую конструкцию. PGX, то есть, обращаясь к этой самой библиотеке, как вы видите, у нас теперь доступно просто PGX и PGX V5. Конечно же, советую
Speaker A
пользоваться именно вот этой вот V5, потому что это на данный момент, на момент записи этого курса, наиболее актуальная версия библиотеки, стабильная, проверенная и свежая. Как узнать, какая версия самая стабильная, свежая на момент просмотра вами этого видео? Да просто переходите на страницу
Speaker A
Гитхаба с библиотекой PGX и здесь вот переходите по ссылочке с документацией. Видите, здесь версия и написано latest.
Speaker A
- это значит самая последняя версия. То есть сейчас самая последняя версия - это 5.7.6.
Speaker A
Ну и, соответственно, здесь вот будет ссылочка на скачивание самой актуальной стабильной версии. Просто копируйте эту ссылочку, будь то V5 или V6, или V7 или что-нибудь там ещё, и у вас будет самая последняя стабильная версия этой библиотеки. Хорошо. Возвращаемся в наш
Speaker A
редактор кода. Ещё раз давайте напишем pgx. Вот она. PGX V5. Нажимаю, она импортируется. Теперь пишу точка, то есть обращаюсь к какой-то функции в рамках этой библиотеки обращаюсь я к функции connect. Вот она.
Speaker A
То есть это публичная функция, которая, если почитать описание, как раз-таки создаёт нам подключение к базе данных Постгрос. Ну давайте посмотрим, что у нас эта функция принимает. Первым аргументом она принимает какой-то контекст. Сейчас неважно, что это за контекст. Нам сейчас главное лишь
Speaker A
подключиться просто к базе данных. Ну и так как у нас просто тестовая функция, которая лишь создаёт и проверяет подключение, больше ничего она не делает, давайте я прямо в этой функции и создам контекст backкграунд. Всё. То есть положу контекст бэкграунд в
Speaker A
переменную CT X просто для того, чтобы создать хоть какое-то подключение. То есть мне сейчас в целях написания вот этой вот небольшой тестовой функции нет смысла сильно запариваться над разбиением контекстов на несколько уровней приложения. Ну и так далее.
Speaker A
Передаю этот контекст первым аргументом. И вторым аргументом мы смотрим, у нас требуется какая-то строка подключения.
Speaker A
Что это за строка подключения? Это как раз-таки та строка, которую я дал в комментариях в мейнфайле. Давайте я её скопирую отсюда и перенесу в файл simple connection вот сюда вот этот комментарий. Давайте в соответствии с этим комментарием будем заполнять нашу
Speaker A
строку подключения. Значит, у нас первым делом в этой строке подключения должно идти слово постреress. Postgress двоето toча/с/ Что это такое? Это указание, что подключение у нас идёт по сети. Чуть дальше мы с вами рассмотрим, почему именно по сети мы с вами подключаемся к
Speaker A
базе данных Постгрыс. И вот это вот постгс двоеточие/L - это как раз-таки указание протокола подключения. То есть к базе данных Пос мы с вами подключаемся по протоколу Посгрыз, что логично. После этого мы с вами должны написать имя пользователя, к которому мы будем
Speaker A
подключаться в базе данных. По умолчанию мы с вами какого-то особого пользователя с вами не создавали. По умолчанию в PGress всегда создаётся юзер, который называется Postgress. Ну, к нему мы и будем подключаться. Дальше двоеточие. И через двоеточие нам нужно указать
Speaker A
пароль, который мы с вами задали для нашей базы данных. Ну, я задал пароль простенький пас, вот такой вот. После этого ставим собаку и нужно указать хост, на котором расположена база данных, к которой мы хотим подключиться.
Speaker A
В данном случае, в нашем случае у нас база данных расположена на нашем же компьютере локально. И на этом же компьютере мы будем с вами пытаться создать подключение. Что это значит? Это значит, что хост подключения к базе данных у нас local host аналогично HTTP
Speaker A
запросом. То есть мы подключаемся к базе данных, которая находится на нашем же компьютере. Это значит, что запрос на подключение и, собственно, само подключение нужно поддерживать на локал хосте. После этого через двоеточие указываем порт, на котором расположена наша база данных. Ну, в нашем случае это
Speaker A
5432, стандартный порт. И через слш нужно указать название конкретного логического подраздела, к которому мы с вами будем подключаться в рамках всей нашей большой базы данных. Ну, мы с вами никакой логический подраздел специально не создавали, опять же, поэтому будем
Speaker A
пользоваться логическим подразделом, который был создан по умолчанию. А он называется Постгрыс. Вот видите, у нас в строке подключения целых три раза встретилось слово постгрыз. Первый раз - это название протокола подключения, второй раз - это имя пользователя и третий раз - это логический подраздел.
Speaker A
Имя пользователя и логический подразделы мы с вами как-то специально отдельно не создавали, поэтому мы пользуемся теми, что у нас созданы по умолчанию. А по умолчанию они вот называются так незамысловато постгressс, постгрес.
Speaker A
Хорошо, мы с вами заполнили эту строку подключения. Давайте посмотрим, что эта функция конем вообще будет возвращать. А возвращает она нам два значения. Первое - это указатель на подключение к базе данных. Помните, да, в первой части мы с вами разбирали, почему в случае с
Speaker A
подключением к базе данных у нас используется именно указатель. Да, потому что под капотом это самое подключение может регулировать очень сложные процессы конкурентного доступа к базе данных, если весь этот доступ происходит именно через это вот одно подключение. Поэтому, если бы мы
Speaker A
передавали копию этого подключения, то никак это самое подключение не могло бы регулировать конкурентный доступ к базе данных, потому что у всех пользователей этого подключения, у всех функций, которые используют это подключение, была бы именно копия на это подключение. А
Speaker A
нам же нужно, чтобы все функции, все места программы, которые с этим подключением работают, они работали именно с одним единственным экземпляром этого подключения, чтобы, повторюсь, само подключение могло корректно регулировать конкурентный доступ к базе данных. Поэтому возвращается именно по указателю. И второе возвращаемое
Speaker A
значение - это ошибка, которая могла у нас возникнуть при подключении к базе данных. Ну что ж, давайте будем это всё принимать. Кон запятая ер, то есть принимаю и conneнеction, то есть подключение, и ошибочку. Давайте сразу же буду проверять ошибку. Если не равно,
Speaker A
так как у нас тестовый пример, тестовая функция, я не буду ничего придумывать. Я сразу просто панику кину. Вот так вот.
Speaker A
Паник. Всё. То есть, если какая-то у нас ошибка при подключении, сразу панику кину, чтобы знать об этом, что у меня что-то не получилось. Хорошо. После этого мы делаем ещё дополнительно com.ping.
Speaker A
Что такое pпи? Pпи - это тестовый запрос в базу данных, который просто проверяет, что установленное подключение у нас действительно валидно и действительно у нас есть доступ. У нас наши запросы доходят в базу данных. Поэтому я делаю вот этот вот тестовый запрос, чтобы,
Speaker A
собственно, проверить, что подключение не только создано, но ещё, что у меня фактически мои запросы доходят до базы данных. Тоже принимает эта функция какой-то контекст. В нашем случае передадим уже наш существующий контекст бэкграунд и возвращается на ошибочку. То есть просто если у нас запрос дошёл, всё
Speaker A
корректно, тогда у нас нет ошибки. Если запрос не дошёл, значит ошибка. Ну давайте я буду это и проверять. Если у меня не равно, в таком случае тоже просто кину панику. Хорошо. И если я смог создать подключение и я смог
Speaker A
проверить его пинг запросом, в таком случае я выведу подключение к базе данных. Произошло успешно. Восклицательный знак. Всё, сохраняю файл. И теперь я вижу, что я написал тестовую функцию checknection.
Speaker A
Она у меня создаёт подключение к базе данных и проверяет его. Если всё хорошо, то выводит на экран об этом сообщение.
Speaker A
Если что-то плохо, то мы кинем просто панику, и наше приложение упадёт, и мы увидим, в чём же была ошибка. Теперь в мейнейне я хочу вызвать эту самую функцию. Она у меня находится в пакете Simple connection и называется checknection. Всё. То есть мне ничего
Speaker A
больше делать не надо. У меня эта функция ничего не принимает, ничего не возвращает. Она просто создаёт подключение и проверяет. Ну хорошо, давайте запустим нашу программу. ПишуUN.
Speaker A
Подключение к базе данных произошло успешно. Всё. Поздравляю. Мы с вами не только установили базу данных, но ещё и смогли к ней подключиться изнутри гошного приложения. Ну давайте ещё для точности я тут укажу неверный порт, например 5431 да? То есть на порту 5431 у нас никакой
Speaker A
базы данных нету. Запускаю и вижу, что я упал с ошибкой, что fail it to connect ла-ла-ла. Вот на порту 5431 connection refused. То есть вообще не получилось подключиться к порту 5431. Как только ставлю порт 5432, всё подключается. Отлично. Ну а это ещё
Speaker A
не всё. Напоминаю, что мы с вами работали в рамках ветки feature/postgress. И нам, по-хорошему, вот эти вот изменения залить в мастер ветку, ну или в main ветку, для того чтобы просто их запомнить и потом уже отталкиваться от этих самых изменений,
Speaker A
когда мы будем с вами создавать какие-то новые ветки. Ну, можем посмотреть на наш gitdiv. Значит, у нас файл добавился он всякими зависимостями, да, потому что мы скачали библиотеку PGX и вместе с ней автоматически ещё много библиотек у нас
Speaker A
с вами установилось. Мы видим, что у нас появился какой-то go файл. Также можем посмотреть, что в мейне у нас произошло подключение нашего Simple Connection пакета и вызов функции check Connection.
Speaker A
Ну и, собственно, добавлен файл simpleconnection.go. Вот он. Хорошо, давайте я напишу git status. Вижу мои изменения. Напишу git ad точка. Всё сразу добавляю. Пишу git commit m и тут напишу add simple connection. Всё, описание комита у меня. Add simple
Speaker A
connection. Хорошо. Нажимаю enter. Пишу gitp. Мне, конечно же, ошибку показывает, потому что у нас на Гитхабе в рамках нашего репозитория нет никакой ветки featчер Poggress, и нам нужно добавить setupstam origin, чтобы эта ветка создалась. Копирую, вставляю. Всё, я запушил ветку Feature Postgrress с
Speaker A
последним комитом, в котором мы с вами написали функцию Check Connection и вызвали её из мейна. Теперь давайте зайдём на GitHub и посмотрим вообще на наши изменения и как они выглядят.
Speaker A
Создадим пулрек requкст и зальём его в мастерку. Вот я перехожу в наш Study репозиторий. И здесь я вижу, мне сразу же GitHub подсказывает, что я вот 48 секунд назад запушил ветку Future PoGress и предлагает мне сразу, чтобы никуда далеко не лезть, создать
Speaker A
полуреквест. Я воспользуюсь этой кнопкой. Довольно удобно. Смотрю, что у меня ветка, куда я буду заливаться - это main. И ветка, откуда я буду заливаться - это Feature Postgress. Всё правильно.
Speaker A
Значит, никакого описания дополнительного я не буду сейчас писать. Нажимаю Create PL request. Нажимаю file changes. Смотрю, в принципе, на изменения, которые мне предлагает этот пореквест. Изменение, которое мне этот лquest предлагает - это создание файла simpleconnection.go.
Speaker A
Функция в нём checknection. Вот эта вот наша с вами функция. Далее у нас файл изменён следующим образом. Goam у нас тоже добавился автоматически. На всякий случай напоминаю, что файл goam - это файл, в котором содержатся криптографические ключи для наших с вами
Speaker A
зависимостей. Это нужно для того, чтобы при сборке нашего проекта какой-то злоумышленник не смог подсунуть свою библиотеку под видом какой-то нашей валидной зависимости, чтобы мы, например, ожидали, что мы будем зависеть от библиотеки PGX, а по факту мы будем зависеть от очень похожей библиотеки, но
Speaker A
с вредоносным кодом. Вот как минимум, чтобы такого не было, у нас есть файл go сам и дополнительные криптографические ключи. Как конкретно это работает, вы уже можете изучить сами. Это не тема сейчас нашего разговора, но просто для того, чтобы никто не пугался этого
Speaker A
файла, go сам. Вот так вот быстренько говорю о том, что это такое. Ну и мы видим, что main. Пакет Simple Connection и вызывает из него функцию Check Connection. Хорошо, в принципе, я сам себя сейчас проревьюил. Это, повторюсь, полезная практика. Я увидел, что, в
Speaker A
принципе, всё, что я сейчас добавляю в свой проект, это валидно. Может быть только за исключением файла go. Давайте сейчас я покажу, о чём я говорю. Сейчас я в корне проекта напишу mode tid. И как вы видите, у меня мой go файл изменился.
Speaker A
Ну и, соответственно, вместе с ним изменился файл go сам. Что изменилось в год файле? У меня раньше моя зависимость от библиотеки PGXV5 была индирект.
Speaker A
Почему индирект? Потому что мы её скачали и установили в наш проект, но мы её не использовали. После этого мы импортировали с вами файл PGXV5, и наша зависимость PGXV5 уже из индирект стала вполне себе директ зависимостью, то есть из непрямой зависимости стала прямой
Speaker A
зависимостью. И после того, как я написал гомо Тайде, то Гошка, она пересмотрела ещё раз весь проект, и она увидела, что эта зависимость PGXV5, она вполне себе директ, поэтому перенесла её сюда. Так ли это важно, что у нас прямо
Speaker A
проект аж не соберётся из-за того, что у нас вдруг какая-то зависимость, которая на самом деле директ, помечена как индирект. Нет, то есть проект у нас всё равно соберётся. Но почему есть такое разделение на индирект и директ зависимости, то есть на непрямые
Speaker A
зависимости и на прямые зависимости? Да, просто для того, чтобы какой-то случайный разработчик, который зашёл в наш проект, мог вот так вот открыть год файл и сразу увидеть, от чего напрямую зависит этот проект и какие библиотеки он импортирует, чтобы при желании он мог
Speaker A
себе создать такое же окружение или он мог просто сделать какие-то краткие выводы по проекту, а все индирект зависимости его уже не так сильно интересуют. Почему его индирект зависимости не так сильно интересуют?
Speaker A
Да, потому что просто установив все прямые зависимости, автоматически доустановятся все необходимые косвенные вот эти вот индирект зависимости. Если чучуть так разобраться, что вообще такое индирект зависимость - это зависимость, которая требуется для устанавливаемых нами напрямую библиотек, потому что устанавливаемые нами напрямую библиотеки
Speaker A
тоже от чего-то, в свою очередь, зависят. Ну и мы должны установить тоже вот эти косвенные зависимости. При этом у нас есть желание различать то, что мы сами установили напрямую своими руками и то, что было установлено автоматически в качестве непрямых зависимостей. Просто
Speaker A
это иногда полезно видеть, знать и делать на основе этого какие-то выводы. То есть я открываю GOMТФай по прямым зависимостям я сразу могу понять, из чего вообще состоит проект, с чем он работает, и у меня нет необходимости смотреть во всю эту кашу непрямых
Speaker A
косвенных зависимостей. Ну, надеюсь, с этим разобрались. Соответственно, Гос сам у нас чуть-чуть обновился вместе с этим. В целом могу вам посоветовать в Госам, в принципе не смотреть. Тут ничего полезного вы не поймёте, да, как бы что-то там в голове пытаться
Speaker A
скомпилировать этот хэш-код, но это просто смешно. Вот самое, что нас интересует - это go. Ну, соответственно, мы увидели всё, что у нас в нём поменялось. Сохраняю файл, пишу gitстатус, вижу опять изменения. Пишу git add то git commit - m. Ну и тут так
Speaker A
и напишу в описании комита. Всё, git pш. Запушил изменения, запушил новый комит в нашу ветку. вижу в наших комитах два комита. Хочу посмотреть File changes по последнему комиту нажимаю File changes.
Speaker A
Нажимаю здесь вот последний комит ad. Ну и могу видеть, что здесь у нас просто обновлён гомофайл, go самфайл. Ну, в принципе, всё хорошо. О'кей, меня полностью устраивает теперь нашquest. И я нажимаю merch request. Confirm merch.
Speaker A
Всё, я залил наш merch request в мастер ветку. Теперь мы можем у себя локально переключиться на main ветку и написать gitpol. и произойдёт магия. У нас в наш mainфайл сейчас добавится автоматически весь код из фича постгресвет ветки и,
Speaker A
соответственно, simple connection функция тоже. Мы с вами находимся в main ветке и получили весь код из фича постгресветки. Ну всё, теперь мы с вами проверили, что у нас действительно удаётся установить подключение к базе данных. Мы проверили это подключение, и
Speaker A
мы разработку всю ввели в рамках отдельной ветки, после чего по готовности залились в мастер ветку. Мы с вами мало того, что проверили подключение к постгрес, так ещё и с точки зрения гита мы с вами сделали всё правильно. Не сразу пушили весь наш код
Speaker A
в мастер ветку, а провели всю работу через отдельную гиitку. Потом мы с вами корректно провели полреквест, сами себя отревьюили, сами посмотрели свой же код, и только после этого мы залились в мастер ветку. Нам сразу же GitHub предлагает удалить постгресветку, если
Speaker A
она нам больше не нужна. И зачастую так оно и бывает, что если вы залили какую-то ветку в мастерку, то больше вам ваша фичаетка не нужна, её можно удалить. Но надо сказать, что если я нажму здесь удалить, то удалится эта
Speaker A
ветка только лишь на удалённом сервере. Да, давайте это проверю. Что действительно вот у меня тут больше нету моей фича постгроссвет ветки, но локально эта ветка у меня всё ещё будет Git Branch. Вот она есть. Давайте её тоже удалим. Копирую её название и пишу
Speaker A
Git Branch тире D и название ветки. То есть тире D - это значит delete. Нажимаю Enter. Пишу GitBch и вижу, что действительно у меня эта ветка удалилась. Фича/2. Ветку тоже можно удалить. Она нам, в принципе, не нужна.
Speaker A
GitBch D fit 2. Всё, теперь у меня есть локальная только мастер ветка. Хорошо. Но тут уже обратная логика. Когда я локально у себя написал удаление ветки Feater 2, она у меня удалилась только локально. На удалённом сервере у меня
Speaker A
всё ещё есть эта фир 2 ветка. Ну, в принципе, я могу её отсюда удалить, потому что мне она тут тоже не нужна.
Speaker A
Нажимаю здесь view all branches. Вот вид ветка. И давайте я её просто удалю отсюда. Всё. Active branches - это типа ветка, на которой я в данный момент нахожусь. Но я сейчас просто буду на main ветке. И как мы видим, я успешно
Speaker A
удалил Фир 2 ветку и локально, и с удалённого репозитория, потому что ни там, ни там мне эта ветка больше не нужна. Всё, теперь мы с вами, наконец-таки, готовы идти дальше и уже работать полноценно с нашей базой данных. Теперь я бы хотел с вами
Speaker A
разобрать некоторую путаницу в терминологии, которая у нас могла возникнуть, пока мы с вами устанавливали базу данных и пока мы с вами к ней подключались. Что же мы с вами на самом деле установили на наши компьютеры, когда я говорил, что мы с вами скачиваем
Speaker A
базу данных. На самом деле мы установили не просто базу данных, мы с вами установили целую систему управления базой данных Постгрес. То есть система управления базой данных - это целый комплекс всяких утилит, которые нам позволяют с вами создавать базы данных,
Speaker A
хранить в них информацию и так далее. Этот комплекс утилиit в себя включает, помимо прочего, Pastgre SQL сервер. Что такое Pastgre SQL сервер? Это приложение сетевое в первую очередь, которое работает на определённом порту. Чаще всего это именно 5432, и может принимать
Speaker A
сетевые запросы для работы с базой данных. То есть работа с базой данных, она практически всегда происходит именно по сети, именно при помощи PGR SQL-сервера. В том числе когда мы с вами из PGмина подключались к базе данных, мы с вами это делали тоже по сети, тоже при
Speaker A
помощи PSGSQL-сервера. Мы с вами задавали хост, на котором у нас работает наша СУBD, то есть система управления базами данных. Мы с вами задавали порт, на котором работает наша СУБД, ну и логин пароль. То есть мы работали с базой данных именно при помощи PGS
Speaker A
SQL-сервера. Повторюсь, это сетевое приложение, которое работает на определённом порту и к которому мы с вами можем подключаться, чтобы в дальнейшем как-то работать с базой данных. Далее, если более конкретно рассматривать эту систему управления базами данных Постгрес, то в ней имеется
Speaker A
возможность создавать логические подразделы, которые иногда в некотором контексте тоже можно назвать базами данных. Это довольно странно, но вот такая вот терминология нам дана.
Speaker A
Собственно, разработчиками Погресс. У нас есть SubD Posgress - это большой программный комплекс, который, в том числе, в себя включает PostgressQL-сервер. И в рамках этого SUBD Posgress у нас есть возможность создавать логические подразделы, которые тоже иногда могут называться базами
Speaker A
данных, как ни странно. Но для уточнения я буду всё-таки это называть логическим подразделом, потому что это таковым и является. И как раз-таки эти логические подразделы и хранятся на жёстком диске.
Speaker A
Passql - это просто какая-то программка, которая у нас запускается и работает в оперативной памяти и обслуживает входящие сетевые запросы, ну, и даёт какие-то сетевые ответы. А вот логические подразделы, в которых хранится информация, находятся уже на жёстком диске. Получается, вот этот вот
Speaker A
Pг SQL-сервер, взаимодействуя с логическими подразделами, на самом-то деле взаимодействует с данными, которые лежат на жёстком диске. Ну, давайте более конкретно рассмотрим, что же за логический подраздел. Вот, например, постгресс - это некоторый логический подраздел, в рамках которого мы можем
Speaker A
создавать какие-то свои таблицы, в рамках которого мы можем создавать какие-то другие сущности, и они будут существовать только в рамках этого логического подраздела. В рамках другого логического подраздела этих самых таблиц существовать уже не будет. Для чего эти логические подразделы нужны? Да
Speaker A
просто-напросто потому, что одной и той же СУBD, одним и тем же экземпляром СУБД могут пользоваться различные приложения, в том числе различные приложения. И каждое ээ кэнд-приложение хочет работать с данными в рамках своего логического подраздела, просто чтобы случайно без
Speaker A
нужной на то причины не попасть в другой логический подраздел и не поменять его данные в тот момент, когда это не требовалось. Хотя всё ещё у нас будет доступ к другим логическим подразделам.
Speaker A
Нам как бы никто это не запрещает. Но точно так же, как мы с вами код в приложении выносим в различные функции, также и всякие сущности на уровне базы данных принято выносить в различные логические подразделы. чаще всего, когда этими сущностями пользуются разные
Speaker A
приложения, но мы первое время будем пользоваться одним единственным логическим подразделом, который у нас есть в нашем СУ, который у нас есть в нашей СУБД- это логический подраздел Постгрыс. Почему? Да потому что он создаётся автоматически и нам ничего не
Speaker A
нужно предпринимать, чтобы им пользоваться. Других логических подразделов у нас на самом деле нету. Это я просто для примера привёл, что они тоже могут быть. Хорошо. То есть ещё раз, когда мы с вами из ПГадмина подключались к нашей базе данных, на
Speaker A
самом-то деле мы с вами по сети устанавливали подключение к Subd Posgess, а конкретно к PostgressQL-серверу. Вот мы с вами из PGдмина на local host. А почему на local host? А потому что вот эта вот subDGress, она у нас развёрнута на нашем
Speaker A
же локальном компьютере. Именно поэтому мы подключение к этой СУBD устанавливали не на какой-то там удалённый сервер, а именно на local host. На local host мы с вами устанавливаем соединение на порт 5432. Вводим логин-пароль, и у нас из ПГ-админа появляется возможность
Speaker A
управлять вот этими логическими подразделами, да и в целом всей SubD Postgс. Точно также мы с вами устанавливали подключение к SBD Postgress, конкретно к PSGRSQL-серверу через гошное приложение. Всё абсолютно то же самое на local host, на порт 5432.
Speaker A
И наши подключения, наши запросы какие-то дальнейшие. Всё это обслуживает по сгресql-сервер. Исходя из того, что мы в этом самом подключении передаём, он уже будет как-то менять данные в логических подразделах. Ну вот тут такой небольшой момент, что несмотря на то,
Speaker A
что это сетевые запросы, они происходят не по протоколу HTTP, они происходят по протоколу Постгрыс. Чем протоколы вообще между собой отличаются? Тем, как они работают под капотом. Но нам, как программистам прикладного уровня, пока мы с вами учимся, пока мы с вами не
Speaker A
лезем в подкапотную реализацию сетевых протоколов, нам просто достаточно понимать, что HTTP запрос и запрос на Postgre SQL сервер - это несколько разные запросы, и это не одно и то же.
Speaker A
То есть у нас в запросе по протоколу Postgess нет такого понятия, как методы запроса, да, если кто помнит методы в HTTP запросах, это get, у нас post, delete, ну и так далее.
Speaker A
постгрессзапросах сетевых у нас нет такого понятия, как методы, у нас нет такого понятия, как хедеры, у нас нет такого понятия, как Gсон тело, запросы ответа, просто потому что это другой протокол. Но тем не менее это всё ещё запросы по сети. Тем не менее мы, когда
Speaker A
подключаемся к базе данных, даже если она у нас развёрнута на нашем локальном компьютере, мы всё равно это делаем по сети. И так как у нас есть целое сетевое приложение, которое обслуживает эти наши запросы, мы спокойно на одном порту
Speaker A
можем подключаться к этой самой СУBD Postgress из разных приложений одновременно. И когда мы с вами работаем через PGM, и когда мы с вами работаем через гошное приложение, мы с вами всегда работаем с одной и той же системой управления базами данных
Speaker A
Постгрес с одним и тем же её экземпляром. Ну и потом мы уже можем выбирать, какому логическому подразделу мы будем обращаться, и создавать сущности и работать с ними в рамках этого логического подраздела. Ну и после этих уточнений мы уже до конца
Speaker A
разобрались, что же мы с вами скачали, установили, к чему же мы в итоге подключились, и можем продолжать свою работу. Ну теперь, наконец-таки, переходим к практике, и я быстренько расскажу, что же мы сейчас будем делать.
Speaker A
Мы сейчас подключимся к Subd Posgress из pgдмина. Мы подключимся к Subbd Posgress из гошного приложения. Мы с вами будем использовать именно логический подраздел, который создан по умолчанию и называется он постгрыз. Вот этих вот логических подразделов у нас, в
Speaker A
принципе, не существует. И в рамках логического подраздела Постг мы с вами создадим таблицу для хранения данных с несколькими столбцами. И в эту таблицу при помощи гошного приложения мы с вами будем добавлять какие-то строки данных.
Speaker A
И потом мы из ПГадмина сможем смотреть на эти самые данные, которые туда положило гошное приложение, чтобы вообще себе отдавать отчёт в том, что за таблицу мы создали, что за данные мы туда кладём, удаляем ли мы какие-то данные оттуда, меняем ли мы какие-то
Speaker A
данные. Ну и чтобы перед нами просто состояние СУBD Погрс, а конкретно нашего логического подраздела Постгрес было как на ладони, чтобы мы всё видели, видели, все изменения, которые мы с вами производим. Ну переходим в V вкод. И тут давайте опять создадим новую ветку,
Speaker A
чтобы работать в рамках новой ветки. И все наши изменения были в рамках этой же самой ветки. То есть глобально большого смысла создавать новые ветки на каждую новую фичу, когда вы работаете один над своим проектом, нету, потому что нет
Speaker A
никого другого, кто зависит от вашей мастерки и кому вы можете что-то испортить, если зальёте новые комиты, не готовые в эту мастерку. Но для того, чтобы сразу готовиться к тому, как это происходит на работе, к тому, чтобы сразу набивать вот эту мышечную память,
Speaker A
мы будем с вами создавать новые ветки, комитить в эти ветки и потом делать пулреквесты в мастер ветку. Поэтому давайте я пишу GitBCH и назову эту ветку feature/SQL. То есть тут мы наконец-таки будем писать SQL запросы, которые будут из нашего гошного приложения
Speaker A
отправляться сразу в СУBD Погрес и менять как-то данные и логические подразделы, которые есть в этой самой базе данных. Ну, нажимаю Enter и переключаюсь на эту самую ветку. Get checkout. И вот название ветки я вставляю, пишу gitстаatus. Вот, собственно, я на ветке Feature SQL. Всё,
Speaker A
теперь я могу начать писать свой код. Предварительно я открою PG adдмин и здесь слева вверху во вкладочке север я подключусь к погсерверу, который мы с вами недавно установили. У меня тут ещё куча других PSGSQL-серверов. Это не имеет отношения к курсу. Это уже я для
Speaker A
себя и для сообщества нашего приватного разрабатывал различные программные продукты. Поэтому меня интересует только лишь вот этот вот первый под название PGR SQL18 PGR SQL сервер. Нажимаю на него правой кнопкой мыши и connect сервер. Всё, я подключился. Возможно, вас попросят ввести дополнительно пароль
Speaker A
какой-то, но я нажимал галочку Save Passwords, поэтому меня не просят вводить пароль. Ещё раз, давайте посмотрим, что у нас тут есть. А у нас тут есть наш вот этот PG SQL сервер и в нём databases. Да. Что такое databas?
Speaker A
Это как раз-таки те самые логические подразделы, которые у нас есть в рамках PastGSQL-сервера. Вот этот вот PASGRSQL 18. Можно его переименовать, если вы хотите. Вот property тут name. Ну давайте я его назову стадии Pastgr SQL.
Speaker A
Ну просто PSQL сервер. Нажимаю save. Всё. Вот он наш Study PassG SQL сервер. И в нём логический подраздел Postgress.
Speaker A
Вот это вот наш Stady PG SQL сервер и в нём логический подраздел погрыз. Вот к нему мы и подключились. Вот этих вот подразделов у нас просто не существует.
Speaker A
Они существуют только лишь на схеме для примера. По факту, как мы видим, у нас один этот самый логический подраздел.
Speaker A
Что есть в этом логическом подразделе? А в нём у нас куча различных сущностей, которые поддерживаются базой данных. На самом деле СУD Pastg SQL - это далеко не просто таблицы для хранения данных. Это огромная функциональность, которая нам помогает очень сложные и масштабные
Speaker A
данные обрабатывать так, чтобы это было быстро, чтобы это было надёжно, чтобы это было безопасно. В рамках третьей части курса Погошки мы с вами просто физически не сможем рассмотреть всё, что умеет SBD Постгрыс. Даже я сам спустя несколько лет разработки, даже
Speaker A
коммерческой, не знаю все тонкости SubD PoгС. Я вам честно скажу, мало кто вообще на нашей планете знает абсолютно все тонкости работы СУBD PGREQL. Как я советую, как я вижу правильным вообще впитывать вот эту вот скилуху работы с постгресом? Во-первых, это, конечно же,
Speaker A
практика. Практикой мы с вами будем заниматься в рамках этого курса. Во-вторых, это теория. Теории довольно много. Её можно найти на официальном сайте Постгрес, но там её прямо очень много и непонятно, что конкретно учить.
Speaker A
Чтобы понять, что конкретно учить, я вам советую смотреть задачи с собеседований, с реальных, которые дают, смотреть запись реальных собеседований, и там уже становится понятно, какую конкретно теорию, какой конкретно пласт теории по постгрессу. чаще всего спрашивают и чаще всего хотят, чтобы вы, как разработчик,
Speaker A
понимали, исходя из всего вышесказанного, я хочу, чтобы вы не пугались, если мы вдруг не рассмотрим какую-то вкладочку. Все знания, которые вам необходимы для того, чтобы писать программы и для того, чтобы успешно проходить собеседование, вы получите, во-первых, при выполнении практики,
Speaker A
которую предлагает этот курс, а, во-вторых, при подготовке к собеседованиям. О'кей, идём дальше. Значит, смотрим, какие вкладки есть. Тут у нас много вкладок. Нас сейчас интересует вкладка Схемас. Ну, то есть это схемы какие-то в рамках нашего логического подраздела. Что такое схема?
Speaker A
Сейчас будет смешно, но схема - это логический подраздел логического подраздела. Чем отличается логический подраздел, который называется databas, от логического подраздела, который называется схема? Отличаются они следующим образом. Вот я нарисовал схему. Тут наш логический подраздел погрес, который у нас во вкладке
Speaker A
Databas. А тут у нас два логических подраздела, которые во вкладке схемы. Ну, то есть в нашем случае у нас тут один лишь подраздел в вкладке Схемас. Он называется паб. Значит, в чём отличие?
Speaker A
Отличие в том, что если мы с вами устанавливаем какое-то подключение к погресерверу и указываем конкретный логический подраздел уровня датабейса, с которому мы подключаемся, у нас это Postgress, вот он, то мы получим в ответ от PostgressQL-сервера такое подключение, с помощью которого мы
Speaker A
сможем работать только лишь в рамках вот этого вот логического подраздела. Если мы вдруг захотим получить доступ к логическому подразделу вот этому вот, нам придётся с вами изнутри нашего гошного приложения создавать новое подключение, которое уже нам позволит работать вот с этим логическим
Speaker A
подразделом. Поэтому, если мы захотим работать с двумя логическими подразделами одновременно, нам придётся одновременно держать два PSGR SQL подключения. С этим разобрались. Но если мы с вами создали подключение к погressсерверу конкретно к логическому подразделу вот этому вот Postgress, и в
Speaker A
рамках этого логического подраздела на уровне databases, мы захотим работать с разными логическими подразделами в рамках вкладки схемы, то есть в рамках вот этой вот вкладки. Нам в этом случае уже не придётся создавать различные подключения. Мы благодаря одному единственному подключению
Speaker A
кгресql-серверу сможем работать сразу с несколькими логическими подразделами на уровне схем. Вот в этом и отличие. То есть, по сути, у нас что логический подраздел на уровне databas, что логический подраздел на уровне схем, это всё всего лишьнавсего разбиение данных,
Speaker A
которые мы храним в базе данных, на какие-то зоны. Для чего это нужно? для того, чтобы, как бы это не звучало, логически разделять хранимые данные.
Speaker A
Потому что если бы мы хранили с вами данные просто в одной куче, это было бы неудобно. И с точки зрения производительности это было бы плохо, потому что уdgessl пришлось бы в рамках однойдинственной кучи с данными, в рамках одного единственного ведра
Speaker A
мусорного со всеми этими данными искать нужные вам. А это неудобно ни вам, ни самому Посгреql-серверу. А когда у нас идёт такое логическое разделение хранимых данных на множестве уровней, это сильно упрощает жизнь и вам как разработчику, и СУD PGRSQL как
Speaker A
программному комплексу, который призван предоставлять функциональность по работе с данными. Именно поэтому существует всё это разделение на базы данных в рамках одной СУД и на схемы в рамках одной базы данных. Повторюсь, терминология может вас запутать, но главное, чтобы вы
Speaker A
поняли, для чего это всё нужно. Нужно это для того, чтобы не получить одну огромную свалку хранимой информации.
Speaker A
Соответственно, у нас есть одна большая СУБД. В этой СУБД могут быть разные так называемые базы данных, но это лишь некоторая терминология, которая может запутать. Поэтому я это называю логическими подразделами. И в этих логических подразделах могут быть ещё одни дополнительные логические
Speaker A
подразделы. Это на самом деле похоже на то, что у нас есть одно большое гошное приложение. В этом гошном приложении у нас есть деление на пакеты. И в этих пакетах у нас ещё есть деление на функции. Ну и, в принципе, очень похожая
Speaker A
вещь у нас в СУBD Постгрес. Есть одна большая постгрес. В ней есть деление на базы данных логические. И в этих логических базах данных есть ещё деление на логические подразделы схемы.
Speaker A
Собственно, это просто данность, которой не надо пугаться и всего лишь-навсего нужно понимать, для чего это сделано.
Speaker A
Повторюсь, для того, чтобы, во-первых, по полочкам организовывать хранение наших данных. Ну и, во-вторых, для удобства и разработчиков, и самой СУБД, потому что СУБД гораздо проще найти данные, когда вы ей скажете, в какой логической базе данных они лежат и в
Speaker A
какой логической схеме они лежат, чем просто, если бы была одна огромная свалка данных и вы говорите СУБД, найди вот в этой вот огромной свалке данных нужные мне данные. Ну, вы понимаете, это было бы просто неудобно и очень долго.
Speaker A
Ну, собственно, предлагаю идти дальше. Вот у нас наш погресql-сервер. В нём логический подраздел логическая база данных постгрес. И в нём ещё один логический подраздел схема, а именно паблик схема. Паблик схема - это логический подраздел на уровне схем, который создаётся автоматически при
Speaker A
создании логического подраздела на уровне баз данных. Вот мы создали с вами логическую базу данных Пос, и в ней есть паблик схема. Это, в принципе, стандартное поведение. В рамках этой паблик схемы, наконец-таки, уже отдалённо знакомые нам таблицы. И как мы
Speaker A
видим, у нас сейчас нету никаких таблиц в рамках нашей базы данных, вообще ничего. Но это и логично, потому что мы с вами ещё ничего не создавали. Ну а сущности, которые мы с вами сохраняем в базу данных, они у нас хранятся именно в
Speaker A
таблицах. А это значит, что нам для того, чтобы сохранить какую-то сущность, нам нужно сначала для неё создать таблицу. Какие сущности мы будем с вами сохранять? Ну давайте, чтобы далеко не ходить, мы создадим таблицу для сохранения задач нашего ту-ду листа и
Speaker A
будем туда сохранять эти самые задачи. Хорошо, переходим в наш гошный код. Мы уже создали себе ветку Feater SQL, переключились на неё и можем спокойно работать в рамках этой ветки. Хорошо, в рамках директории Feature Posgress я создам новый пакет, назову это Simple
Speaker A
SQL следующим образом. Тут у меня будет файл simpleql. И пакет SimplesQL. Поставлю здесь нижнее подчёркивание. Мне просто так проще читать название пакетов, которые состоят из нескольких слов. Значит, дальше давайте думать, что нам нужно сделать.
Speaker A
Мы с вами в конечном итоге хотим уметь сохранять наши задачи в базе данных. Вот в этой базе данных мы хотим уметь с вами сохранять наши задачи. Задачи мы должны сохранять в таблицу. У нас нет таблицы, значит, нам нужно создать таблицу.
Speaker A
Давайте я здесь создам функцию, которая у нас будет отправлять SQL запрос в базу данных для создания новой таблицы. То есть я сейчас в рамках нашего гошного приложения опишу функцию, при вызове которой будет отправляться SQL запрос в PGR SQL-сервер по сети. Так как у нас
Speaker A
соединение изначально установлено именно к логической базе данных PostgreС, то всё взаимодействие будет происходить в рамках этой логической базы данных. Наш SQL запрос в рамках этой логической базы данных будет требовать в рамках паб схемы создать таблицу, назвать её tasks,
Speaker A
например, или как мы захотим, создать определённые столбцы в этой таблице, и каждый столбец будет определённого типа.
Speaker A
Вот именно это от нас в первую очередь требуется. Создать таблицу, в рамках которой мы будем в дальнейшем сохранять наши данные, потому что если нету таблицы, то нам некуда сохранять наши данные. Ну хорошо. Давайте эту самую таблицу с вами и создадим. Для этого я
Speaker A
перехожу обратно в гошный код и создаю функцию. Пускай она называется create table. В принципе, название функции в рамках кошки никак не влияет на то, какая таблица будет создана в постгресе.
Speaker A
Значит, помним, что нам для работы с базой данных нужно установить подключение. Подключение у нас, в принципе, уже устанавливается в функции checknection. Правда, это самое подключение из функции не возвращается.
Speaker A
Давайте сделаем, чтобы возвращалось. Тип подключения - это у нас указательно pgx. Ну давайте мы будем его возвращать. у указательp pgx. И здесь после вывода подключения к базе данных произошло успешно, я буду писать return con. Таким вот образом. Вообще, по-хорошему, мне
Speaker A
ошибку обрабатывать не внутри этой функции, а где-то за её пределами. Именно поэтому я буду возвращать не только лишь подключение, но ещё и возможную ошибку. И сразу буду писать вот здесь вот return pgx connect. Таким образом я изнутри этой функции Check
Speaker A
Connection верну результат выполнения PGX Connect. Ну и что у меня вернёт PGX Connect, то у меня и вернёт моя функция Check Connection. Также я бы хотел создавать контекст не внутри этой функции, а принимать его уже откуда-то извне, потому что вообще есть такое
Speaker A
правило, что контекст бэкграунд должен создаваться в приложении один раз где-то в самом начале этого приложения. Если вы создаёте контекст бэкграунд внутри какой-то функции, это либо какой-то ваш тестовый пример, либо это уже некорректное написание кода, потому что такой контекст, он не несёт в себе
Speaker A
никакого смысла, и мы при помощи него никак не сможем контролировать выполнение функции Check Connection.
Speaker A
Поэтому я буду принимать контекст откуда-то извне и передавать этот самый контекст в функцию PGX Connect. Хорошо.
Speaker A
И я буду вот это вот подключение, которое я создаю функции checknection, получать в мейне, да? Вот в мейне я буду получать con запята y потом вот это вот самое подключение, которое я получил, я буду передавать в аргументах функции
Speaker A
create table, потому что напоминаю, для того, чтобы хоть что-то сделать с базой данных, нам нужно установить и получить к ней подключение. И только через это самое подключение я смогу с базой данных работать. Давайте я буду тут принимать это подключение pgx.com.
Speaker A
Вот оно. И всё. Благодаря нему я смогу работать с базой данных. Отлично. Давайте main чуть-чуть почистим. На самом-то деле мне теперь вообще не нужно вот это Hello Git Feater 1, Feature 2.
Speaker A
Я, если честно, хочу это просто удалить. Мне это в дальнейшем не нужно. Так же как и пакеты Feater 1, Feater 2 тоже мне больше не нужны. Значит, check conneнеction у меня принимает какой-то контекст. Ну давайте я создам контекст
Speaker A
бэкграунд в самом начале нашего приложения. для того, чтобы потом уже от этого самого контекста отталкиваться. Пока я буду просто передавать этот контекст в функцию checknection и не буду как-то сложно эти самые контексты нарезать.
Speaker A
Сейчас у нас не про это. Check connection принимает контекст, возвращает либо созданное подключение к базе данных, либо error. Давайте ещё переименуем checknection в create connection, потому что теперь у нас уже не проверяется этой функции подключения к базе данных, оно создаётся. Create
Speaker A
connection. Всё, создали. Теперь нужно проверить ошибку. Если не равно, тогда точно так же панику просто кину. Иначе всё, у нас есть с вами подключение. Пинговать мы его не будем, потому что если вдруг с ним что-то не так, мы всё равно об этом
Speaker A
узнаем, когда попробуем создать таблицу. О'кей. И после этого я вызываю Simple SQL пакет и в нём create table. Туда я передаю это самое созданное подключение.
Speaker A
Ещё раз, мы с вами в функции теперь уже create connection создаём подключение к базе данных и возвращаем его вместе с возможной ошибкой. В мейне мы перехватываем это подключение с возможной ошибкой, проверяем на ошибку.
Speaker A
Если ошибки нету, то созданное подключение к базе данных мы дальше передаём в функцию для создания таблицы.
Speaker A
И эта функция уже будет использовать данное подключение для того, чтобы таблицу, собственно, создать. Давайте посмотрим, какие у нас есть методы у структуры КОН. То есть кон - это у нас структура, вот самая обычная структура, в которой много всяких полей. Нас на
Speaker A
самом деле сейчас не интересует, что в ней за поля. Пишуко точка. И тут мы видим огромное количество различных функций, которые нужны для различных сценариев использования этого самого подключения. Нас сейчас интересует сценарий, когда мы просто хотим описать какой-то SQL-запрос, вот я его синеньким
Speaker A
обозначу, и по сети прокинуть его в PGR SQL-сервер, чтобы потом создать таблицу в рамках паблик схемы. То есть нас интересует просто проброс SQL запроса.
Speaker A
Нас не интересует ответ, нас не интересует ничего. Нам просто хочется закинуть SQL-запрос в PG SQL-сервер. Для того, чтобы вот так вот взять и закинуть SQL-запрос в базу данных, у нас есть в рамках структуры подключения метод Ex.
Speaker A
По описанию мы можем почитать, что Excе-то SQL, то есть какой-то SQL-запрос будет передан. Exeg у нас первым аргументом принимает контекст, и вторым аргументом он принимает SQL string. То есть мы должны передать ему какой-то контекст и сам SQL запрос, который мы хотим, чтобы
Speaker A
был выполнен. Далее у нас идут возможные аргументы для этого запроса. Но что за аргументы, мы с вами рассмотрим далее.
Speaker A
Значит, exeg принимает контекст, поэтому давайте мы тоже в create table будем принимать контекст, чтобы его пробросить в функцию exeg context тонтекст.
Speaker A
Вот так вот. Значит, exeg принимает контекст и какую-то строку. Давайте эту строку, то есть этот SQL запрос, я вынесу в отдельную переменную. Ну, назову эту переменную SQL Query инициализирую эту строку не через обычные двойные кавычки, а через вот
Speaker A
такие одинарные косы. Для чего? Потому что двойные кавычки я не могу разнести на несколько строчек, а вот косые кавычки я могу разнести. Обычный SQL запрос всё-таки большой, и он разносится на несколько строк, поэтому я буду использовать вот эти вот одинарные
Speaker A
кавычки. Хорошо, мы с вами должны создать таблицу. Как создаётся таблица при помощи SQL-запроса? пишется ключевое слово create. После этого мы указываем, что именно мы хотим создать. table. Я хочу создать таблицу. После этого нужно передать название таблицы, которую мы с
Speaker A
вами хотим создать. Tasks. Кто-то может спросить: "Где же ты в этом запросе указываешь логическую базу данных и логическую схему?" То есть я сразу же в этом запросе указал название таблицы, которую хочу создать. При этом я не указывал название логической базы данных
Speaker A
и логической схемы, в рамках которых я хочу создать таблицу. Почему так? Ну, давайте разберёмся с каждым по отдельности. Во-первых, логическая база данных у меня была указана во время создания подключения. Вот здесь вот я, когда указывал подключение, вот видите,
Speaker A
последним аргументом через слш в строке подключения я указывал название логической базы данных. Я указал постгрес. Соответственно, вся работа дальнейшая с этим подключением будет у меня в рамках вот этой вот логической базы данных. И в дальнейшем при использовании этого самого подключения
Speaker A
мне больше не нужно указывать в рамках какой логической базы данных я работаю. Хорошо, почему я не указал схему, в рамках которой я буду создавать таблицу?
Speaker A
Потому что я работаю в рамках стандартно созданной по умолчанию паблик схемы. А если я не указываю никаких схем в своих запросах, то используется именно эта паблик схема, просто по умолчанию.
Speaker A
Поэтому я могу в своём SQL-запросе просто написать название таблицы, и сразу СУD Posgess поймёт, в рамках какой логической базы данных и в рамках какой схемы нужно создавать эту самую таблицу.
Speaker A
Хорошо. Create table tasks. После этого открываю круглые скобочки. И внутри этих круглых скобочек я должен описать, из чего эта самая таблица у меня состоит.
Speaker A
Какие поля у сущностей, которые сохраняются в рамках таблицы Tasks, должны быть. В конце SQL-запроса обычно принято ставить точку запятой. Иногда эта точка запятой может автоматически добавляться некоторыми библиотеками, например, нашей PGX - это самая точка с запятой в некоторых случаях будет
Speaker A
автоматически добавляться в конце. Но чтобы не гадать, чтобы не зависеть как-то от подкапотной реализации какой-то конкретной библиотеки, я всё-таки в конце SQL запроса вручную поставлю точку запятой, чтобы точно знать, что она там есть. Хорошо, давайте начнём описывать наши столбцы или же
Speaker A
поля сущностей, которые будут сохранены в рамках таблицы tasks. Давайте вспомним, как выглядела наша таблица, когда мы с вами только её предполагали.
Speaker A
Вот следующим образом я предлагаю сейчас убрать столбик с авторами, потому что это уже требует создания ещё одной, второй таблицы. Давайте пока начнём с создание одной таблицы, поэтому столбец с авторами я вот таким вот образом уберу. И у нас первый столбец - это
Speaker A
идентификатор. Почему мы с вами уже успели обсудить. Значит, первый столбец должен называться у нас ID. Далее мы должны указать тип столбца. Я напишу serial primary ke primary K? Primary K, потому что это у нас первичный ключ. Что такое первичный
Speaker A
ключ? Мы опять же с вами уже успели обсудить. Это у меня именно первичный ключ. То есть ключ, который уникально идентифицирует каждую отдельную запись в рамках таблицы Tasks. Что такое сериал? Seral - это специальный тип первичных ключей, который представляет из себя обычные
Speaker A
целые числа, но которые создаются автоматически для каждой отдельной записи. Это как раз-таки та самая настройка для базы данных, о которой я вам говорил, что нужна специальная настройка для того, чтобы первичный ключ создавался автоматически был уникальным и чтобы за этим следила именно база
Speaker A
данных. Один из способов это сделать - это указать тип столбца сериал. Тогда при каждой новой вставке в нашу таблицу у нас вот эта вот ID будет принимать автоматически какое-то значение. В случае с сериал это каждый раз на один
Speaker A
больше. То есть первая вставка у нас будет иметь айдишник один, вторая вставка будет иметь айдишник два, третья вставка айдишник три, ну и так далее.
Speaker A
Вот именно для того, чтобы поле ID у нас автоматически генерировалось, было целым числом, которое каждый раз увеличивается на один, мы пишем тип serial. Хорошо.
Speaker A
Смотрим дальше, какие столбцы у нас требуются в таблице tasks. Дальше у нас title, то есть какой-то заголовок задачи. Ну хорошо. Ставлю запятую, пишу tille, то есть название столбца. После этого тип столбцар char. Но не просто варчар, а в скобочках я должен передать
Speaker A
максимальное количество символов, которые этот столбец может сохранить. Для чего это нужно? Для того, чтобы Постгре смогла заранее просчитывать размер тех или иных полей для внутренних всяких оптимизаций. Давайте я вот с запасом возьму 200. То есть 200 символов максимум у нас может быть для нашего
Speaker A
заголовка. Ну также это полезно, чтобы у нас не было каких-то слишком огромных вставок в наши заголовки. Например, какой-нибудь злоумышленник захочет создать нам задачу, у которой в заголовке 3 млрд символов. К чему это приведёт? К тому, что мы с вами
Speaker A
попытаемся на нашем хосте сохранить 3 млрд символов. К чему это может привести? К тому, что у нас просто забьётся место на жёстком диске.
Speaker A
Напоминаю, Постгрес хранит данные на жёстком диске. И у нас просто не останется места для новых вставок. У нас не останется места для новых задач, которые захотят создать другие пользователи. тем самым злоумышленник сможет сломать нашу систему. Чтобы этого не было, мы пишем ограничение на размер
Speaker A
строки, которая может быть вставлена в поле title. После этого я ещё напишу not, потому что я хочу, чтобы всегда при вставке у меня пользователь передавал заголовок задачи. Хорошо. Смотрим дальше. Дальше у нас описание.
Speaker A
Description. Окей. Description тоже это у нас warchar. Ну и давайте для описания я побольше символов сделаю. Например, максимум 1.000 символов может быть у нас в описании задачи. Мне кажется, вполне должно хватить. Точно также not, потому что я хочу, чтобы у каждой записи в
Speaker A
рамках таблицы tasks всегда было что title, что desриption, потому что без них задача не имеет смысла. Возможно, если как-то глубже проанализировать бизнес-требования к нашему приложению, то можно прийти к выводу, что desрипtion всё-таки иногда может быть нал, потому что пользователь может захотеть создать
Speaker A
задачу, у которой есть только заголовок, например, уборка, но нету описания. То есть пользователь не хочет конкретно продумывать какое-то глубокое описание для своей задачи, поэтому он просто не хочет его указывать. Такое может быть. И в таком случае у нас поле description не
Speaker A
должно быть not. То есть оно должно уметь при необходимости принимать значение на просто если пользователь его не задал. Но это уже начинаются тонкости бизнес-требований, которые мы должны либо сами для себя решить, если мы какой-то свой подпроект разрабатываем, либо обсудить со своим тимледом или со
Speaker A
своими коллегами, со своим продуктовым аналитиком, если мы работаем в рамках какой-то компании. В нашем случае я сам принимаю решение, что я хочу видеть в поле description, а я хочу, чтобы description всегда было задано. Поэтому я пишу not. Значит, дальше у нас поле
Speaker A
completed типа bulean. Completed bullan. Ну и completed я тоже хочу, чтобы было not now. Но тут уже вопрос не бизнес-требований, тут вопрос здравого смысла. Если у нас у задачи может не быть описания, и это в целом нормально, то не быть информации о том, выполнена
Speaker A
ли задача или нет, у нас уже не может, потому что мы тогда вообще не поймём, в каком состоянии задача находится. Типа, в смысле, она и не выполнена, и не выполнена, ну, типа, в какой-то суперпозиции находится. Ну, это бред. Я
Speaker A
всегда хочу, чтобы у меня конкретно было задано булевское значение для каждой задачи, для поля completed. Поэтому пишу now, чтобы я всегда знал конкретно про каждую задачу, выполнена она или нет.
Speaker A
Повторюсь, если у нас про столбец description ещё спорно not now, то про столбец completed по-любому должно быть not now. Конечно, кто-то дотошный может сказать, что вообще-то у нас у задачи может быть больше, чем два состояния.
Speaker A
Она может быть не только выполнена или не выполнена, но ещё у нас могут прилететь такие бизнес-требования, что задача у нас может быть, например, created, то есть только создана, после этого completed, то есть выполнено, после этого uncomed - это если задача
Speaker A
была отмечена как не выполнена после того, как она была выполнена. Но в таком случае нам, в принципе, поле були не подойдёт. Нам уже придётся писать какие-то другие свои поля. То есть я вам сейчас это всё говорю просто, чтобы вы
Speaker A
понимали, что мы уже дошли до такого уровня, где нету какой-то чёткой детерминированности, нету какой-то чёткой определённости того, как вы должны писать свои программы. Написать одно и то же есть миллиард способов.
Speaker A
Поэтому, если вы захотите сделать как-то по-своему, это не значит, что это неправильно. Это просто значит, что это по-другому. Но в нашем случае у нас у задачи может быть только два состояния.
Speaker A
Это либо выполнено, либо не выполнено. Поэтому я хочу иметь для этой информации тип були, то есть либо true, либо fф, либо выполнено, либо нет. И я хочу, чтобы эта информация всегда абсолютно была задана для каждой отдельной задачи, хранимой в базе данных. Поэтому пишу not
Speaker A
now. О'кей. Какие у нас там дальше поля? Давайте смотреть. Дальше у нас время создания. Created. Ну, хорошо. Значит, created. Created add - это у меня stamp тоже not, потому что я хочу всегда знать, когда у меня задача создана. Ну и
Speaker A
дальше completed тоже stamp. Но он уже может быть нал, потому что у нас задача может ещё не быть выполнена, соответственно, у неё отсутствует время выполнения. Всё, мы с вами описали все поля в нашей таблице для задач, которые мы хотели. В конце запятой не должно
Speaker A
быть, то есть после самой последней записи запятая не нужна, иначе наш запрос SQL будет считаться невалидным.
Speaker A
Теперь мы этот самый SQL-запрос должны отправить через наше подключение в базу данных. Хорошо, я его и передам. таким вот образом. То есть у нас exeg принимает ещё третьим аргументом какие-то там аргументс, но мы пока это не используем. Это мы разберём чуть
Speaker A
дальше. Пока что мы можем просто передать контекст и SQLзапрос, который будет послан в базу данных. Excе-то comm. Что такое comm, а я, если честно, не знаю. Давайте посмотрим. Нажимаю на функцию Exec. Вот здесь эта структура Command Tag. И тут разработчики
Speaker A
библиотеки PGX заботливо оставили комментарий, чтобы я знал, что такое Command Tag. Command - это статус текст, возвращаемый погре SQL для какого-то SQL-запроса. Ну, соответственно, тут будет просто какой-то текст, который будет отображать статус запроса. На самом деле, мне это не сильно интересно,
Speaker A
потому что для меня будет достаточно лишь того, что функция Exк не вернула никаких ошибок. То есть, если ошибки не было, значит, у меня была создана таблица. И я не хочу смотреть какой-то там дополнительный текст, возвращаемый базой данных. Мне это сейчас, в данный
Speaker A
момент, не интересно. Поэтому я первый аргумент, возвращаемый функции exк, буду игнорировать и буду получать лишь ошибку вот эту вот. И после этого проверять, что если у меня ошибка неравна Нил, а что в этом случае сделать? Можно кинуть панику, но тогда, если кто-то захочет
Speaker A
использовать нашу функцию create table и вдруг случится ошибка, то мы просто паникой положим всё приложение. Я этого не хочу, поэтому я ошибку буду просто возможную ошибку я буду просто возвращать из функции. И пускай уже тот, кто эту функцию вызывает, сам думает над
Speaker A
тем, что же ему с этой самой ошибкой делать. Я лишь её буду возвращать, если она у меня есть. Ну, если ошибки нет, то я буду возвращать л. Хорошо. Вот наша функция create table, которая описывает SQL запрос для создания таблицы и
Speaker A
отправляет этот самый SQL запрос через подключение к базе данных непосредственно саму базу данных. И я смотрю, была ли какая-то ошибка во время выполнения этого запроса или нет. О'кей, давайте вызовем эту функцию и запустим наше приложение. Вот. Create table.
Speaker A
Контекст она требует первым аргументом, вторым аргументом подключение. Хорошо, возвращает ошибку. Ну и давайте я буду смотреть, что если у меня не равно, в таком случае тоже просто панику кину, иначе выведу на экран. Таблица в базе данных была создана успешно. Для
Speaker A
наглядности я справа открою PG adдмин, чтобы смотреть за состоянием нашей базы данных конкретно логического подраздела погрес, конкретно логического подраздела схемы паб. Значит, в схеме пабли у нас вот здесь таблиц никаких нету, как вы видите. Пока мы ещё не запускали наше
Speaker A
приложение. Давайте я запущу наше приложение, напишу go run.go. Что должно произойти? Во-первых, должно создаться подключение к нашей базе данных, к логическому подразделу погрыз. После этого подключение передаётся в функцию Create table, в которой используется для создания таблицы tasks с определёнными
Speaker A
столбцами. Ну и возможные ошибки мы возвращаем изнутри этой функции. После этого проверяем на ошибку и если всё хорошо, то выводим информацию о том, что всё хорошо. Давайте запустим наше приложение и посмотрим, создастся ли какая-то таблица. Значит, нажимаю Enter.
Speaker A
Таблица в базе данных была создана успешно. И тут я могу посмотреть, что у меня ничего ещё не создалось. Почему?
Speaker A
Потому что нужно нажать правой кнопкой мыши рефреш, то есть обновить полученную информацию по списку таблиц. Рефреш.
Speaker A
Раскрываю. И тут я вижу, у меня появилась наша таблица tasks, только что созданная. Могу правой кнопкой мыши по ней нажать. И тут протис. Это свойство созданной таблицы. Мы видим название, мы видим, кому эта таблица принадлежит, а именно пользователю Постгрес
Speaker A
стандартному. Мы видим, в рамках какой схемы лежит эта таблица. Также можем нажать colмs или же столбцы. И здесь мы увидим все столбцы, ну или же все поля.
Speaker A
Но всё-таки поля - это чаще термин, который применяется к гошному приложению, к структуре. То есть у структуры у нас какие-то поля, а у таблицы в базе данных у нас всё-таки столбцы. Значит, столбцы, которые мы создали. ID тип integer. Но на самом
Speaker A
деле я вам рассказывал, что сериал - это такой тип, который в себе хранит целые числа. Но у него есть вот дефолт, то есть значение по умолчанию. Значение по умолчанию у нас просто каждый раз будет увеличиваться на единичку. Столбец
Speaker A
title, description, completed, ну и так далее. В общем, все столбцы, которые мы создали. И у них у всех тип тот, который мы передали, ограничения, которые мы с вами передавали и not. То есть, как мы видим, у нас всё note, кроме completed,
Speaker A
completed может быть null. Ну и primary ke у нас только лишь вот один самый первый. Больше его нам и не нужно.
Speaker A
Поздравляю, мы с вами создали нашу таблицу. Также мы можем правой кнопкой мыши по ней нажать здесь view edit data, то есть посмотреть или же редактировать какую-то информацию. И здесь нажать first 100 store, то есть первые 100 записей, которые у нас есть в этой
Speaker A
таблице. Нажимаем и ожидаемо у нас здесь, вот видите пустой вывод. То есть нету ничего в нашей таблице в данный момент. А здесь сверху нам показывает SQL запрос, который был отправлен в нашу базу данных. Select звёздочка. Что значит звёздочка? Звёздочка после
Speaker A
селекта означает отобразить все столбцы, которые есть в таблице tasks. Значит, тут обращение к таблице tas Tasks идёт через паблик схему, но повторюсь, это совершенно необязательно. Я могу это всё стереть. То есть он у меня тут просит подтверждения, что я хочу действительно
Speaker A
изменить этот SQL-запрос. Я нажимаю да и я могу ещё раз выполнить этот SQL-запрос. Выделяю его и здесь execute scriptриt. Как мы видим, всё произошло точно так же, потому что указание паблик схемы - это не обязательно. Хотя иногда для дополнительной точности это не
Speaker A
мешает, но тут уже всё зависит от того, как принято в вашей компании, в вашей команде, как на это смотрят ваши коллеги. Обычно всё-таки для паблик схемы не пишется вот эта вот пабли точка. Обычно это не делается. В конце
Speaker A
этого SQL-запроса нету точки запятой, потому что её автоматически добавляет PG adдмин, но в целом можно и самому добавить, если очень хочется. Как мы видим, всё то же самое, всё работает.
Speaker A
О'кей, мы с вами создали таблицу Tasks, но у нас нету для неё никаких данных. А значит, мы захотим эти данные туда положить. Казалось бы, можно идти дальше, но давайте я вам покажу один интересный момент. Если мы с вами
Speaker A
запустим наше приложение ещё раз, давайте его запустим, мы получим ошибку. Что нам пишется в рамках ошибки? Ошибка.
Speaker A
Сущность Tasks already exist. То есть таблица Tasks уже существует в рамках нашей базы данных, но мы попытались её, запустив наше приложение ещё раз, создать второй раз. Что делать, чтобы такой ошибки не было? Можно как-то пытаться руками проверить, есть ли в
Speaker A
нашей СУБД, в нашей вот этой вот постгб схеме таблица tasks и если её нету, тогда создавать. Но это всё очень сложно. Syntaxis SQL нам даёт замечательный инструмент для того, чтобы создавать таблицу только в том случае, если она не была создана. Для этого
Speaker A
просто после create table нужно написать if not exists, что переводится с английского, если не существует.
Speaker A
Соответственно, create table if not exist, значит создать таблицу только если она ещё не была создана. Ну и дальше описание таблицы. Давайте ещё раз запустим нашу программу. Ну и как мы видим, что у нас никаких ошибок нету. И стандартный вывод: таблица в базе данных
Speaker A
была создана успешно. Хотя, конечно, по факту мы не создали никакой новой таблицы. Мы просто отправили следующий SQL запрос в базу данных. База данных посмотрела, что таблица TASKS уже есть.
Speaker A
Увидела условие, что создавать таблицу нужно только, если её нету. Ну а если она есть, то ничего делать не надо.
Speaker A
Соответственно, у нас никаких ошибок не было возвращено. Create table выполнилось без ошибок, паники не было, и мы вывели наш успешный вывод. Хорошо, теперь давайте начнём заполнять эту таблицу какими-то данными. Я в пакете Simple SQL создам файл simple.go. то это
Speaker A
всё ещё у меня пакет Simple Scale. И тут у меня будет функция insert r. Что такое r? R - это строка. То есть я сейчас буду добавлять в нашу базу данных какие-то строки. Ну давайте приступим. Напомню, чтобы нам работать с базой данных, нам
Speaker A
нужно иметь подключение к этой самой базе данных. Поэтому мы будем принимать подключение к базе данных в аргументах функции pgx.conc.
Speaker A
Вот наше самое подключение. Дальше мы должны обратиться к этому подключению и выбрать какой-то метод, который нам наиболее подходит. Сейчас мы хотим просто отправлять SQL запросы в PGRE SQL сервер на создание новых записей. То есть мы не хотим никакие данные из
Speaker A
постгреса получать. Мы хотим лишь отправить SQL запрос и удостовериться в том, что не было никаких ошибок. Поэтому я буду использовать уже известный нам метод Exc. Exeg принимает контекст.
Speaker A
Давайте я буду принимать контекст в функции insert r context то contex передавать этот контекст первым аргументом в метод ex и вторым аргументом от нас требуется SQLst то есть SQL запрос по стариночке буду класть его в переменную SQL query и в
Speaker A
одинарных косых кавычках опишу значит для того чтобы нам создать новую запись в таблице нужно написать insert into intгозвание таблицы можно через схему можно без схемы я буду без указания схемы использовать, потому что у меня таблица tasks находится в паб схеме, значит, я
Speaker A
могу не указывать конкретно, что это паблик схема, просто схема по умолчанию. Иt, после этого название таблицы tasks.
Speaker A
После этого в скобочках я должен передать название столбцов, которые я хочу заполнить. Для того, чтобы конкретно вспомнить название столбцов, я могу либо рядом держать описание SQL-запроса для создания таблицы, либо могу в PG-админе правой кнопкой мышки нажать на нашу таблицу и здесь проties.
Speaker A
Ну и тут у меня будут мои столбцы, название столбцов. Значит, первый столбец, который я буду передавать в своём SQL-запросе - это не ID, это столбец title. Почему? Потому что столбец ID у нас serial, а значит, он будет заполняться автоматически базой
Speaker A
данных. От меня требуется лишь передавать все остальные столбцы, потому что для них не задано никакого автоматического заполнения. Значит, столбец title у меня будет. Вот я его пишу title. Потом столбец description.
Speaker A
Ну или если я здесь смотрю в описании создания таблицы, то я могу отсюда брать эти названия. Description.
Speaker A
Дальше у меня completed. Дальше у меня created. И дальше у меня completed, но так как я в базе данных буду создавать новую задачу, то у меня информации о времени выполнения задачи нету. Значит, я не буду это передавать. У меня тут not
Speaker A
не стоит, как мы видим, да? Вот note у меня нету. Это значит, что поле complet вполне может быть n. Но сейчас мне это как раз-таки и нужно. Хорошо. Insert into. После этого пишем название таблицы, куда мы хотим вставить новую
Speaker A
запись. После этого мы в скобочках перечисляем название столбцов, которые будут задействованы в нашем SQL-запросе на вставку. После этого либо на той же строке, либо на новой мы продолжаем писать ключевое слово values.
Speaker A
И тут мы уже в скобочках дальше должны передать значение для каждого столбца в соответствующем порядке. Давайте я вот вскрою пока проводник, чтобы у меня полностью код помещался на всей моей половинке страницы. Ну и здесь я буду передавать значения для столбцов.
Speaker A
Значит, тайтл. Какой тайтл я хочу вставить для моей задачи? Пускай будет домажка всем нам знакомая. Значит, строки в SQL-запросах выделяются одинарными кавычками, прямыми. Далее описание. Описание у меня пускай будет сделать домашку по мате Ш до двадцато десятого, там двадцать шестого,
Speaker A
например. После этого информация о том, выполнена ли задача или нет. Ну, я задачу только создаю, поэтому она у меня, конечно же, ещё не выполнена.
Speaker A
false. Так как у меня строка, лежащая в переменной SQLY - это SQL запрос, то нужно понимать, что этот SQL запрос будет выполняться внутри базы данных. А внутри базы данных false выглядит следующим образом. Мы должны капsлоком написать ключевое слово false. Ну и
Speaker A
created, мы должны передать время создания нашей задачи в формате, который подходит для Pastgre SQL базы данных.
Speaker A
Как выглядит этот формат? Он передаётся в виде строки, которая выглядит следующим образом. Сперва мы передаём год, то есть год создания нашей задачи.
Speaker A
Пускай это будет 2025 тире после тире мы передаём месяц создания, пускай будет 11, то есть ноябрь. Дальше мы передаём день создания, ну, пускай вот сегодняшняя дата - это 26 ноября. После этого мы ставим пробел и в привычном формате пишем время создания. Пускай
Speaker A
будет 18 часов, двоеточие 00, двоеточие 0. Всё, значит, 18: 0 минут 5 секунд. Вот это мы передаём здесь время создания нашей задачи. Хорошо. По большей части нам никаких столбцов ещё дополнительно не нужно передавать. Мы уже передали title, description. Вот наш title, вот
Speaker A
наш description, вот completed и вот created. По сути всё. В конце можем поставить точку запятой. Повторюсь, это неважно, когда мы используем именно pgx библиотеку, но на всякий случай можно поставить. Опять же, если вашей команде принято этого не делать, то можно этого
Speaker A
не делать. Можете просто посмотреть, как соседние SQL-запросы описаны, в каком формате и в таком же формате делать. Я буду ставить тут на всякий случай в конце точку запятой. Значит, что наш запрос сейчас делает? Он делает insert into, то есть вставляет в таблицу tasks
Speaker A
следующие столбцы. Title, description, completed, created, то есть делает одну новую запись, которая затрагивает вот эти вот столбцы. И дальше я передаю значение для тех столбцов, которые я заранее обозначил. Значит, для столбца tйтle значение домашка, для desрипtion сделать домажку по матише до 2010
Speaker A
десятова двадца шестого. Completed false и created. Время создания. Всё, completed at, то есть время выполнения задачи я не передаю, потому что считаю, что у меня задача только создаётся, поэтому мне не нужно передавать вот это поле. О'кей, я описал SQLзапрос. После
Speaker A
этого я должен его выполнить. У нашего коннекшена метод exec для того, чтобы отправить SQL запрос в базу данных.
Speaker A
Первым аргументом принимает контекст, вторым аргументом SQL-строку. Ну, я эту SQL-строку, этот SQL запрос и передаю.
Speaker A
Хорошо. Метод Exact, напомню, нам возвращает какие-то common tagг. Нам это сейчас не интересно, и ошибку. Поэтому comm tag мы опускаем и принимаем ошибку.
Speaker A
Соответственно, если у меня ошибка не равна, тогда я возвращаю эту самую ошибку, а иначе я могу вернуть. Ну тут, конечно же, в возвращаемых значениях я должен указать, что insert r у меня возвращает ошибку. Иначе было бы странно, что я
Speaker A
здесь указываю, что у меня инсерв ничего не возвращает, а здесь я пытаюсь какие-то значения вернуть. Ну и мне ошибку об этом подсвечивает, поэтому я указываю, что я буду возвращать именно ошибку. Внимательные люди могли заметить, что вот эта запись довольно
Speaker A
странная, что если у меня ошибка не Нил, тогда вернуть ошибку, а если ошибка Нил, тогда вернуть Нил. Это странно, и на самом-то деле можно обойтись простой вот такой вот записью Return ye. Что в этом случае будет, если у меня внутри
Speaker A
интерфейса Eror действительно внутри него будет какая-то сформированная ошибка, я верну эту самую сформированную ошибку. Если у меня внутри интерфейса будет л, тогда я вернул. Всё, мне тут дополнительно как-то проверять это не нужно, что если ошибка, вернуть ошибку, а если вернуть л, нет, я могу одной
Speaker A
строчкой вот так написать, и у меня та же самая логика будет по сути. Давайте это поправим и в другом нашем SQL-запросе на создание таблицы. Точно так же просто return и всё. Если в в этом интерфейсе будет лежать ошибка, она
Speaker A
вернётся. Если там будет л, вернётся л. Всё просто. О'кей. Нам осталось вызвать эту самую функцию на уровне приложения, чтобы она по сети по уже установленному подключению к базе данных отправила следующий SQL-запрос. И после этого мы посмотрим на результат выполнения этого
Speaker A
SQL-запроса в нашем PG-админе и увидим, создались ли у нас какие-то новые записи в нашей таблице tasks или же нет. Ну, теперь давайте в мейне вызовем нашу функцию insert R. Эта функция у нас лежит в пакете Simple SQL insert R.
Speaker A
Принимает контекст и подключение к базе данных. и точно также возвращает какую-то ошибку. Смотрим, что если у нас ошибка не Нил, тогда просто кину панику.
Speaker A
Ну и в конце я сделаю более общее сообщение об успехе. Просто саксит. Всё. Потому что мы сейчас с вами будем делать разные вещи. И я хочу в конце лишь знать, что вся моя программа выполнилась успешно. Поэтому я буду писать Саксид
Speaker A
без конкретного описания того, что же в итоге было успешно выполнено. Хорошо, давайте ещё раз я получу первые 100 строк из нашей таблицы Tasks. Увижу, что тут вообще ничего нету. И теперь я запущу нашу программу, которая создаст таблицу Tasks, если этой таблицы ещё
Speaker A
нет, и вставит в таблицу Tasks следующую строку со следующими значениями. Ну давайте я запущу нашу программу. Goun main.go. Sued. Ага. Теперь давайте посмотрим на нашу таблицу Tasks. Я могу, в принципе, выделить вот этот вот сформированный SQL запрос и просто его
Speaker A
выполнить. Оп. И что я вижу? Я вижу, что у меня в таблице Tasks появилась запись.
Speaker A
Вот она запись, которую мы с вами только что создали. Эта запись отражает одну задачу, которую мы сохранили в базу данных. Как мы видим, ID автоматически создалось равно единице. Title, то есть заголовок задачи, вставился именно тот, что мы с вами указали. Description,
Speaker A
именно то, что мы с вами указали. Completed- именно то, что мы с вами указали. Created ad, - именно то, что мы с вами указали. И completed add null, потому что мы его не указали, но он у нас вполне может быть nл. Что будет,
Speaker A
если мы с вами ещё раз запустим эту программу? Давайте посмотрим. Я ещё раз запускаю нашу программу. Соксид, то есть успешно. И в нашей таблице появилась ещё одна новая запись о новой какой-то задаче. Как мы видим, опять айдишник автоматически у нас сделался два. Ну и,
Speaker A
в принципе, все остальные поля те же самые, потому что мы выполнили тот же самый insert SQL запрос. Вот он, вот этот вот наш запросик. Кто-то может задасться вопросом, почему в нашей таблице есть две задачи с одним и тем же
Speaker A
заголовком? Да потому что мы не задали никаких ограничений на то, что заголовок у нас должен быть уникальный. Мы этих ограничений не задали, значит, этих ограничений у нас нету. У нас может быть сколько угодно задач с одним и тем же
Speaker A
заголовком. Если мы с вами вдруг захотим, чтобы какой-нибудь столбец в рамках целой таблицы у нас был уникальный, то есть имеется в виду, что значение этого столбца у каждой строки в заданной таблице должно быть уникальное.
Speaker A
Более конкретно говоря, если мы возьмём за столбец title, то чтобы у всех задач в нашей таблице tйтle, то есть заголовок, был уникальный. Если мы захотим так сделать, то мы должны это указать при создании таблицы. Как это выглядит? При создании таблицы вот наша
Speaker A
функция create table. Я в конце ставлю тут запятую и дополнительно пишу уnikю. И в скобочках я передаю название столбца, который я хочу, чтобы был уникален. Я хочу, чтобы у меня был уникален столбец title. Всё. Столбец ID у меня уже и так уникальный, потому что
Speaker A
это primary ke, то есть primary K автоматически означает, что столбец должен быть уникальный, и база данных сама поддержит это требование. А вот столбец tйтle не будет уникальный до тех пор, пока мы с вами об этом не скажем.
Speaker A
Но если мы просто сейчас перезапустим нашу программу, будет suксит, а в базе данных мы с вами увидим третью запись тем же самым заголовком. Почему? Потому что SQL запрос функции Create Table говорит о том, что попытка создать эту таблицу будет предпринята только если её
Speaker A
нет. А так как она уже есть, то всё, что мы дальше опишем, не имеет никакого значения. База данных просто не посмотрит на эту часть SQL-запроса.
Speaker A
Поэтому, чтобы база данных пересоздала нам таблицу, нам нужно сначала эту самую таблицу из базы данных удалить, а потом создать заново. И только после этого у нас выполнится полностью этот SQL-запрос и накинется новое требование уникальности на столбе title. Конечно,
Speaker A
мы можем сделать так, но это довольно варварский способ. Почему? Представим, что наше приложение уже находится в открытом доступе для пользователей какое-то время. Эти пользователи за всё это время успели создать уже множество задач в нашу таблицу Tasks. А тут вдруг
Speaker A
мы приходим и такие как разработчики говорим: "А я вот хочу, чтобы у нас столбец title был уникальный". И поэтому я полностью сношу таблицу Tasks и создаю новую. И все ваши задачи, дорогие пользователи, просто будут удалены с моего сервера. Ну, наверное,
Speaker A
пользователи не поймут нас как разработчиков и пойдут пользоваться каким-то другим более нормальным приложением. SQL поддерживает специальный синтаксис для изменения параметров таблицы без необходимости эту самую таблицу удалять и создавать заново. Конкретно этот синтаксис мы с вами посмотрим на теме миграции. Сейчас
Speaker A
мы на этом останавливаться не будем, но просто держите в голове, что не обязательно удалять и пересоздавать заново целую таблицу, если мы просто хотим поменять какие-то её свойства или, например, добавить новых столбцов. Нет, нам не обязательно для этого удалять
Speaker A
целую таблицу и терять данные. Но пока что мы не умеем этого делать. Мы не знаем этот классный синтаксис SQLэля, который позволяет менять параметры таблицы без её пересоздания. Поэтому мы просто варварски пересоздадим таблицу. Я зайду в pgдмин, нажму правой кнопкой
Speaker A
мыши по таблице tasks и здесь нажму delete, то есть удалить таблицу. Подтверждаю, всё, таблица Tasks у меня удалена. Перехожу опять в наше приложение и просто его перезапущу. В этот раз создастся подключение, вызовется функция create table. Create table выполнит вот этот вот SQL запрос.
Speaker A
Этот SQL запрос увидит, что таблица Tasks ещё не создана, то есть её не существует, потому что мы её только что удалили, и выполнит весь оставшийся SQL-запрос полностью. А этот SQL запрос, в том числе включает требования уникальности на столбцей. Выполнится, и
Speaker A
потом у нас произойдёт первая вставка в нашу таблицу, вновь созданную. Ну, давайте запущу приложение gounmail. Suce перехожу теперь в pgдми. Нажимаю здесь refresh, появляется таблица tasks и получаю её данные. Вот я вижу нашу таблицу с одной записью, которая в этой
Speaker A
таблице находится. Также я могу дополнительно нажать properties на нашей таблице tasks. Здесь перейти в constraints, это, то есть, какие-то дополнительные параметры для нашей таблицы. И здесь есть такой constraint unq. Как раз-таки здесь мы можем увидеть, что на наш столбец tй накинут
Speaker A
constraint unю. Повторюсь, constстraint - это какое-то дополнительное правило, которое навешивается на нашу таблицу в постгрес. В данном случае это un constraint. Это значит, что ограничение на уникальность. И ограничение на уникальность у нас на столбцейтle.
Speaker A
Название констрейнта автоматически даётся базой данных. Мы можем вручную выставлять это название для констрейнта, но в данном случае я не вижу никакого смысла это делать. О'кей, поняли, что у нас столбец title должен быть уникальный. И давайте посмотрим, что произойдёт, если я ещё раз выполню нашу
Speaker A
программу. Ну, вообще, какое ожидаемое поведение. Опять установится подключение к базе данных. Мы попробуем создать таблицу. Увидим вот эту приставку if not exists. А таблица есть. Соответственно, дальнейший SQL-запрос выполняться не будет. И дальше мы попробуем вставить ещё одну строчку в нашу таблицу с тем же
Speaker A
самым tйтle, что у нас уже существует в таблице и к чему это приведёт. Ну, давайте посмотрим. Запускаю программу и вижу ошибка duplicate K value un constraint. Что мне тут сказано? Что ошибка дублирование ключа уникального констрейнта. Уникальный констraint tasks
Speaker A
title K. Как раз-таки вот этот вот наш уникальный констraйн на столбе title, вот он нарушен. Это привело к тому, что нам Постгрес через подключение вернул здесь ошибку. Мы эту ошибку вывели через панику и при этом вставки не произошло. Та строка, которую
Speaker A
мы попытались вставить, она на самом деле не вставилась, потому что вставка этой строки означала бы нарушение вот этого вот констрейнта. А если констрейнт был бы нарушен при вставке новой какой-то записи, то постгрес не вставляет эту запись, чтобы не нарушать
Speaker A
constст. Давайте теперь посмотрим, что у нас есть в таблице. И как мы видим, у нас новая запись не появилась.
Speaker A
Повторюсь, из-за того, что у нас constстraйн уникальности на столбце tiт. Давайте просто поменяем tйтle с домашки на, например, знакомый нам выгул собаки.
Speaker A
Ничего другого я менять не буду. Пускай описание completed и created это всё будет то же самое, но запущу ещё раз нашу программу. Соксид. А почему? А потому что я поменял tiт и теперь у меня при вставке новой записи не нарушался
Speaker A
констint уникальности на поле title. Давайте я ещё раз получу всё, что у меня есть в таблице. И я вижу, что у меня появилась новая запись. Но при этом прошу обратить внимание, что ID, то есть уникальный идентификатор, у нас стал не
Speaker A
два, а три. Почему? Да потому что нету строгой привязки этого самого уникального идентификатора к номеру записи в таблице. То есть это не одно и то же. Номер записи в таблице - это просто служебная информация, которую нам показывает PGDМ. А уникальный
Speaker A
идентификатор - это поле таблицы, которое хранится на жёстком диске. И они не обязательно должны совпадать. То есть всё, что мы должны знать, то, что это уникальный идентификатор, и он генерируется автоматически при помощи постгреса. А почему он генерируется автоматически? Потому что мы указали ему
Speaker A
тип serial. То есть сериал, значит, автоматически генерируй мне целые числа последовательно, 1 2 3. Но по различным причинам у нас в итоге может оказываться не 1 2 3, а, например, 1 3 или 1.10.
Speaker A
Конкретно в нашем случае у нас 1.3, потому что уникальный идентификатор 2 был сгенерирован для той неудавшейся записи, когда мы с вами нарушили constнraint уникальности на столбец title. Поэтому следующее значение у нас уже идёт тройка. Ну давайте ещё раз
Speaker A
запустим нашу программу. Ну и ожидаемо получим опять ошибку уникальности, потому что у нас уже есть запись с заголовком Выл собаки, а мы попытались ещё раз вставить такую же самую запись.
Speaker A
Ну и сколько бы я не пытался, у меня всегда будет эта самая ошибка. Если же я наконец-таки введу уникальный заголовок, например завтрак то у меня успешная будет вставка новой записи. Давайте посмотрим. Ну и, как мы видим, успешно у нас действительно
Speaker A
вставилась запись, но уникальный идентификатор опять прыгнул на несколько записей вперёд. А почему? Да потому что мы попробовали несколько раз вставить в нашу таблицу записи, которые бы нарушали уникальный констint на столбе title. Но для каждой попытки у нас Постгрес
Speaker A
отдельно генерировал уникальный идентификатор, поэтому у нас тут вдруг такой скачок появился. Ну хорошо, это всё замечательно, но предположим, что у нас данные о том, какой заголовок, какое описание, какое время создания и так далее, и так далее, все эти данные у нас
Speaker A
должны приходить в HTTP запросе. Соответственно, мы в функции insert r заранее не можем знать, какой заголовок, какое описание и так далее нам передаст пользователь. Значит, мы должны уметь как-то описать шаблон для вставки новой записи, но при этом уметь менять
Speaker A
вставляемые значения в зависимости от того, какие данные нам придут по сети. Как это делается при помощи pgx библиотеки. Делается это следующим образом. Ну, представим, что у нас там где-то сверху к нам приходит этот самый HTTP запрос. Из этого HTTP запроса мы
Speaker A
достаём requвест bodyди. Потом это requestст bodyди мы с вами преобразуем в какую-то структуру. И потом мы с вами из этой самой структуры будем доставать значения различных полей и передавать их в качестве аргументов уже в функцию insert r. Давайте я сейчас это опишу.
Speaker A
То есть у нас insert R должна принимать title строкового типа, description строкового типа, completed типа и created типа time.time.
Speaker A
И вот эти вот принятые значения мы должны будем с вами передать в SQL запрос, чтобы в нашу базу данных, в таблицу Tasks, добавилась запись с такими значениями, которые нам по HTTP прислал пользователь, а не с теми значениями, которые мы заранее просто
Speaker A
захардкодили, прибили молотком. так сказать, в наш SQL-запрос. Ну, потому что вдруг пользователь хотел создать какую-то иную задачу, отличную от этой, а он не сможет этого сделать, потому что у нас SQL-запрос прибит молотком. Нет, нас это не устраивает. Нам нужна тут
Speaker A
гибкость. Ну и давайте посмотрим, как эту гибкость тут получить. Вместо конкретных значений, прибитых молотком, я буду сюда передавать некоторые магические параметры. Доллар один на место первого параметра. На место второго параметра доллар 2. На место третьего параметра доллар 3. На место
Speaker A
четвёртого параметра доллар 4. Хорошо. Передаю этот самый SQL запрос в метод Ex структуру Connection.
Speaker A
И потом ещё в этот метод Ex через запятую я просто передам все аргументы, которые должны встать на место вот этих вот магических параметров с приставкой доллар. Хорошо. Первый параметр у меня будет вставлен в столбец tйтle. Значит, тут я буду передавать tйтle. Через
Speaker A
запятую я буду передавать переменную, которая встанет на место доллар 2. На место доллар 2 у меня должно встать описание моей задачи description. Потом я передаю completed. Вот доллар 3 соответствует у меня третьей позиции. А на третьей позиции у меня идёт вставка в
Speaker A
столбец completed. Хорошо. Completed. И через запятую created. Ну вот, created это у меня time. Под капотом этот метод Exк возьмёт данный SQL запрос, посмотрит вот на эти магические параметры и попробует подставить на место них то, что мы ему передали в качестве
Speaker A
дополнительных аргументов. Если у него получится это сделать, то всё будет хорошо. Если нет, то нам вернётся ошибка, о чём я говорю. Допустим, у нас столбец completed имеет тип були, а мы туда попробуем передать какую-нибудь строку. Hello. В таком случае PGX
Speaker A
библиотека не сможет преобразовать строку Hello в тип данных булин. А поэтому нам вернётся ошибка. Но если PGX сможет преобразовать переданный ей тип данных уровня языка программирования в тип данных уровня базы данных, тогда всё будет хорошо. Ну, значит, у нас на
Speaker A
третье место передаётся доллар 3. Это completed. Completed у нас в базе данных тип були он имеет и в языке программирования бул. тип бул в гошке спокойно преобразуется в булин на уровне базы данных, поэтому тут проблем не будет. Таким вот образом. О'кей. Ну и
Speaker A
теперь нам нужно вызов функции insert row переделать, потому что она у нас теперь принимает ещё дополнительные параметры. Теперь запишу это в такую вертикальную запись. Значит, после подключения наша функция принимает title. Ну title давайте сделаем обеed.
Speaker A
Дальше у нас идёт описание покуша надо. Дальше у нас идёт completed false и created. пускай time now. Всё. То есть именно в тот момент, когда будет вызвана вставка в базу данных, именно это время у меня попадёт в аргумент created. Потом
Speaker A
этот самый аргумент при помощи библиотеки pgx преобразуется в timeстamp на уровне базы данных и вставится в таблицу. Ну хорошо, давайте запустим нашу программу и посмотрим, что же будет. Запускаю и вижу соксит. Ну давайте посмотрим, что же нам там на
Speaker A
самом деле вставилось. Опа. И я вижу, что те значения, которые я принял в качестве аргументов функции insert row, вот они, они у меня преобразовались из данных уровня языка программирования в данные уровня базы данных.
Speaker A
Соответственно, у меня таймстampп, как мы видим, сам заполнился из того, что мы ему передали. Передали мы ему time now.
Speaker A
Сейчас у нас 26 ноября 2025 года, время 19:29. Ну и, как мы видим, 26 ноября 2025 года 192857 была вставлена задача. При помощи этой функциональности библиотеки PGX мы можем динамически принимать различные данные откуда угодно, в частности почти от
Speaker A
наших пользователей и вставлять именно то, что нам передают наши пользователи. Библиотека PGX нас также может дополнительно защитить от SQL атак различных. Что такое SQL атаки? Это когда на место какого-то аргумента, например, на место description описание нам передают не описание задачи, а
Speaker A
какой-то подлый SQL-запрос. Например, вот он может так выглядеть: Drop, databa base. К чему это может в теории привести? К тому, что вся наша дефолтная база данных, весь наш логический подраздел постгрес будет удалён. Соответственно, какой-то злоумышленник при помощи SQL-запроса может нам удалить вообще все
Speaker A
данные. Но библиотека PGX нас от этого дополнительно защищает. Именно поэтому довольно важно передавать вот эти вот параметры, которые мы получаем неизвестно откуда, именно через вот эти вот магические аргументы. Стоит уточнить, что магические аргументы - это моя личная терминология. Так-то это
Speaker A
просто параметры, которые передаются в SQL-запрос и которые потом парсит библиотека PGX. Но я для удобства называю это магическими параметрами. Ну и всё то же самое. Если мы попробуем ещё раз запустить нашу программу, то ожидаемо получим нарушение констрейнта уникальности на столбец title. Почему?
Speaker A
Да потому что у нас запись в таблице tasks с title об уже существует, а столбец tйтle у нас должен быть уникальный, поэтому ошибка. Ну нам ничто не мешает это поменять. Я напишу обед, как очень оригинальный человек, и запущу
Speaker A
ещё раз. Всё, теперь у нас всё успешно. Получаем опять данные из нашей базы. Ну и видим, что 2 у нас успешно вставилось.
Speaker A
И тут уже новое время. Дата та же самая. Ну а время 19:32 мину 16 секунд. Хорошо, мы с вами таким нехитрым образом разобрали самый-самый базовый синтаксис.
Speaker A
Во-первых, создания новых таблиц, а во-вторых, синтаксис вставки новых записей в эти наши созданные таблицы.
Speaker A
Тут крайне много мелочей, которые мы с вами сейчас разбирать не будем, просто потому что это займёт несколько десятков часов одного лишь только обсуждения, не говоря уже о практике. Поэтому крайне советую переходить по ссылке в мой Telegram-канал. Там я привожу все
Speaker A
необходимые материалы, которые помогут вам до конца доизучить SQL и базы данных, чтобы этих знаний вам хватило для настоящей разработки и для прохождения собеседований. О'кей. Теперь давайте посмотрим на то, как нам менять уже существующие записи в базе данных, а
Speaker A
конкретно в какой-то таблице. Ну, допустим, у меня есть задача: "Выгул собаки". А собаку я уже выгулил. И я хочу поменять в базе данных, в строке с уникальным идентификатором три значение completed с false на true. Ну давайте я это и сделаю. Переходим обратно в наше
Speaker A
приложение. И давайте я создам новый файл в рамках пакета Simple Scale. Назову это simpledate.goo.
Speaker A
Ну и можно догадаться, что тут я буду писать SQL запрос для обновления каких-то строк в рамках таблицы. Это у меня пакет Simple Scale. И тут функция у меня пускай будет update r. Что эта функция у меня будет делать? Она будет у
Speaker A
меня, ну, принимать контекст, конечно же, какой-то. Эта функция у меня будет принимать подключение к базе данных. Ну и пока всё. Потом, если что, если мы вдруг захотим ещё какие-то аргументы принимать, ну, мы их допишем в сигнатуру функции. Ну, хорошо, давайте сразу
Speaker A
опишем наш SQL запрос, как он будет выглядеть. Первое слово - это update. То есть я хочу обновить какую-то запись или какие-то записи. То есть я могу сразу несколько записей обновить одним запросом. Uпдейт. После этого я должен написать название таблицы, в рамках
Speaker A
которой я хочу изменить какие-то записи. Я хочу изменить записи в рамках таблицы tasks, но она у нас пока однаединственная, поэтому я здесь напишу update tasks. После этого обычно принято с новой строки писать ключевое слово set. И я должен написать название
Speaker A
столбца, которое я хочу поменять. А хочу поменять я столбец completed, поэтому completed равно и справа от знака равно новое значение для столбца completed.
Speaker A
True. Хорошо, но таким запросом я в таблице tasks абсолютно всем записям в поле complete. Поэтому я должен написать дополнительное условие, каким конкретно строкам я хочу поменять столбец completed. Для этого я пишу ключевое слово where. И после этого я должен
Speaker A
написать условие, под которое должна попасть строка, чтобы попасть под моё изменения. В нашем случае проще всего завязаться на уникальный идентификатор, потому что мы можем быть уверены, что уникальный идентификатор, а именно primary K, всегда будет уникальной для каждой отдельной записи в рамках
Speaker A
какой-то одной таблицы. То есть в двух разных таблицах могут быть одинаковые уникальные идентификаторы, но в рамках одной таблицы у нас всегда уникальный идентификатор будет собственно уникален.
Speaker A
Я хочу поменять строку в игол собаки. Уникальный идентификатор у неё три, а поле называется ID. Соответственно, where ID = 3. Всё, вот он наш SQL-запрос. Этот SQL-запрос обновит строки в таблице Tasks. Строки те, у которых ID = 3. У нас только одна такая
Speaker A
строка. И в этой строке столбец completed поменяет своё значение на true. Ну давайте выполним этот SQL запрос. Опять же, никаких новых данных я в этом SQL-запросе из базы данных не достаю, поэтому я могу спокойно воспользоваться методом Excnection.
Speaker A
Значит, передаю контекст и передаю SQL запрос. Метод Exc, напоминаю, возвращает command tagг. Нам сейчас они не нужны, и ошибку. Command TG опускаем, получаем ошибку, ну, и возвращаем её. Нужно указать, что у нас функция update r возвращает ошибку. И тогда действительно
Speaker A
мы сможем из неё вернуть ошибку. Всё. Благодаря вот этой вот функции мы отправим SQL запрос, который меняет у нас строку в таблице tasks, строку с ID равно 3 и поле completed на true. Что это значит? Это значит, что в нашей
Speaker A
таблице tasks те строки, у которых ID равно 3, а это однаединственная строка, поменяет значение столбца completed с false на true. То есть вот здесь вот у нас будет true. Ну давайте это и сделаем. Давайте вызовем нашу функцию update r simpleql.update
Speaker A
r. Вот это я закомментирую, потому что у нас при попытке вставки дополнительной строки с тайтлом, который уже есть, будет ошибка. Но я не хочу сейчас ничего вставлять. Я хочу лишь обновить уже существующую строку. Значит, контекст нужно передать и подключение. Ну и буду
Speaker A
смотреть, что если какая-то ошибка у меня есть, тогда просто упаду с паникой. Да и всё. Запускаю программу.
Speaker A
Давайте посмотрим, что у нас произошло. Получаю заново все данные из нашей таблицы. И как я вижу, у меня столбец completed для записи с айдишником 3 выставился с false на true. Таким образом, я при помощи SQL запроса Update обновил уже существующую в таблице
Speaker A
строку, поменял в ней какое-то значение. То есть мне не нужно удалять старые строки и вставлять новые для того, чтобы поменять в уже существующей строке какое-то значение. Я могу напрямую обратиться к какой-то строке или к какой-то группе строк и поменять в ней
Speaker A
произвольное значение, если это не противоречит заданным в таблице констринтам. Ну и также если это не противоречит типу столбца. То есть давайте, например, в столбец compled, который имеет тип time stamp, мы попробуем положить какую-нибудь строку, например, hello. Ну давайте. Update
Speaker A
tasks set completed at равно hello where id = ну, например, вот пускай вот 10 where ID равно 10. Давайте запустим нашу программу. Ну, и мы увидим ответ от базы данных. Ошибка, неправильный синтаксис для типа данных. Timestamp. Hello. То
Speaker A
есть мы попытались hello задать как stamp. Хотя estamp у нас должен иметь следующий формат. То есть stamp - это какая-то дата вместе с каким-то временем. Это не строка Hello, поэтому нам база данных вернула ошибку. Давайте я файл Simple SQL переименую с Simple
Speaker A
SQL на Simple Create table, чтобы было более логично, потому что у нас тут происходит только лишь создание таблицы.
Speaker A
Ну и пойдём дальше. То есть мы с вами уже разобрали, как создавать таблицы, как вставлять в эти таблицы новые записи и как обновлять уже существующие записи в рамках какой-то существующей строки в нашей таблице. Теперь давайте посмотрим, как удалить какую-то запись из таблицы.
Speaker A
Ну, создам ещё один файл. В принципе, я мог бы это всё писать в рамках одного файла, но я предпочёл разбивать каждый отдельный запрос на отдельный файл просто для наглядности. Если бы я писал какое-то настоящее приложение, то я бы,
Speaker A
конечно, не то, что в один файл это всё объединил, я бы это всё объединил в какую-то одну структуру. Но мы об этом поговорим дальше. Нам сейчас главное понять то, как вообще происходит взаимодействие с базой данных, создание таблиц и создание изменения, удаления,
Speaker A
получения данных из этой самой таблицы. Ну, смотрим, как удалять записи из таблицы. Создам для этого файл simplee.go.
Speaker A
Пакет у меня simpleql, конечно же. Ну и здесь напишу функцию delete r. Значит, принимает она контекст и подключение к базе данных. Возвращать какую-то ошибку она будет. И сразу же опишу SQL запрос. SQL Query у меня будет выглядеть следующим образом. Первое, что
Speaker A
должен написать - это Delete from. После этого название таблицы, из которой я буду удалять какие-то записи. После этого я должен написать условие, под которое должна попасть строка, которая будет удалена. То есть, если я оставлю SQL-запрос как есть, то удалятся
Speaker A
абсолютно все записи из таблицы Tasks. Мне это не подходит, поэтому я хочу лишь некоторые записи из таблицы Tasks удалить. Поэтому я ставлю ключевое слово и после него укажу условие, под которое попадает запись, которая будет удалена.
Speaker A
Ну, пускай я хочу удалить запись, у которой айдишник равен восемь. Как видите, уже наблюдается большое удобство того, что мы с вами завели айдишники.
Speaker A
Благодаря ним я могу спокойно работать с конкретными записями в нашей базе данных. При этом быть уверенным, что будет применено воздействие конкретно на эту запись. Ну вот хочу удалить запись с айдишником восемь. V ID = 8. Всё, этот SQL-запрос нам удалит запись с
Speaker A
айдишником восемь. Опять же, я никакие данные из базы данных не получаю, поэтому я вызываю метод exeg у структуры подключения. Передаю контекст и передаю нашу SQL-строку. Получаю возможную ошибку и возвращаю её. Всё. Теперь давайте в мейне. Я не буду ничего
Speaker A
апдейтить, я просто сделаю удаление строки. Simpl scale то delete of. Передаю контекст, передаю подключение.
Speaker A
Смотрю, что если у меня ошибка неравна, тогда паник. Всё, запускаю программу Соксид. Переходим в PG adдмин. Видим, у нас сейчас есть как бы, да, вот эта строка с айдишником 8, но это устаревшие данные. Ещё раз получаем. Оп, строки с
Speaker A
айдишником восемь нету. Мы её удалили. То есть мы удалили задачу из нашей базы данных. О'кей. Но SQL запрос на удаление, стоит уточнить, может за одно выполнение охватывать сразу несколько строк. Так же, как и SQL запрос на обновление. Ну, давайте, чтобы сразу
Speaker A
несколько строк не удалять, я покажу вам, как это делается на примере обновления строк. Давайте я во всех строках, где у меня completed false, а у меня сейчас три таких строки, я desриption поставлю просто смайлик улыбочка. Ну вот, чтобы вам показать,
Speaker A
что я за один SQL запрос могу сразу несколько строк захватывать. Хорошо. Update tasks set description.
Speaker A
description равно вот такой вот смайлик обычный where completed = false. Completed = false. Всё, давайте я выполню этот SQL-запрос. Значит, здесь я комментирую удаление и раскомментирую обновление строк в таблице Tasks.
Speaker A
Запускаю программу, вижу suxit. И теперь давайте посмотрим, что у нас поменялось. Ожидается, что в столбце description появится у нас смайлик в тех строках, где completed у нас false. Давайте смотрим наши данные. И правда, так и произошло. То есть я за один апдейт SQL
Speaker A
запрос смог сразу поменять три строки. При этом мне не нужно для этого делать три отдельных SQL-запроса. Такие тонкости важно учитывать, когда у нас происходит работа с большими данными, под большой нагрузкой. У нас огромное количество запросов летает по сети и
Speaker A
HTTP запросов, и запросов в базу данных, ответов из базы данных. Соответственно, время, которое данные проходят по сети, оно у нас имеет какой-то ощутимый вес.
Speaker A
Если мы слишком много будем по сети гонять данных, излишнем много, то это может привести к проблемам с производительностью. Поэтому, если нам нужно обновить сразу три строки в базе данных, мы заранее знаем, что это за строки, какое условие на их обновлении,
Speaker A
то вместо того, чтобы по одной строчке обновлять, да, например, по айдишнику, я могу сразу общее условие для них задать и за один SQL запрос обновить все эти данные. Соответственно, SQL запрос один.
Speaker A
Это значит, что по сети туда-обратно данные будут гоняться лишь один раз, но за этот один раз я сразу три строчки смогу обновить. Ну, такая вот небольшая тонкость, которую хорошо бы держать в голове, когда вы пишете SQL запрос и
Speaker A
когда вы выстраиваете архитектуру своего приложения. О'кей, мы умеем с вами вставлять данные в таблицу, обновлять их, удалять. Теперь давайте научимся получать данные из таблицы изнутри гошного приложения. Сейчас мы данные из таблицы умеем получать только лишь изнутри ПГ-админа. PGми нам сам
Speaker A
автоматически генерирует SQL-запрос, который нужен для того, чтобы получить данные из таблицы. Тут есть неизвестный нам синтаксис. Давайте вот сейчас разберём, что вообще написано в ПГ-админе, потом напишем такой запрос самостоятельно. В ПГ-админе написано сект звёздочка. Звёздочка - это значит
Speaker A
получить все столбцы, доступные в таблице. Давайте я вместо звёздочки здесь напишу title, запятая description и выполню этот запрос. Как мы видим, я теперь получил не все столбцы, а только title desрипtion. Если я хочу получать все столбцы, можно поставить звёздочку.
Speaker A
тогда я буду получать все столбцы. Но когда мы пишем с вами SQL запрос в приложении в нашем, то нельзя писать звёздочку, потому что сейчас у нас имеется вот такой набор столбцов. В будущем в эту таблицу кто-то может добавить ещё дополнительные столбцы. А
Speaker A
из-за того, что у нас звёздочка, мы будем эти все столбцы получать, но мы можем быть не готовы их обработать.
Speaker A
Именно поэтому лучше именно внутри гошного приложения всегда писать конкретный набор столбцов, которые мы хотим получить, отправляя SQL запрос. Но в PGне, когда мы просто хотим посмотреть, что лежит в базе данных, мы можем спокойно звёздочку написать. Как бы нам никто не мешает это сделать,
Speaker A
ничего не сломается от этого, да, потому что мы своими глазами смотрим, а не обрабатываем как-то получаемые данные автоматически из приложения. Поэтому тут, в принципе, ззочка и стоит. О'кей, с этим мы, в принципе, разобрались. Ну, тут ещё схема пабли. В принципе, можно
Speaker A
без неё, как я уже говорил, всё то же самое будет, потому что она и так по умолчанию подставляется в SQL-запросы, если мы её не пишем. Поэтому можно её не писать, как хотите. То же самое. Хорошо.
Speaker A
Дальше у нас идёт Order by ID Ask limit 100. Что это такое? На самом деле, это две разные подкоманды. То есть это всё один SQL запрос. В нём есть подчасти.
Speaker A
Первая подчасть - это Select звёздочка from Tasks. Но мы уже поняли, что это такое. Это значит, пожалуйста, база данных. Покажи мне данные в таблице Tasks. Ну и меня интересуют все имеющиеся столбцы в рамках этой таблицы.
Speaker A
После этого Order by idsk. Это значит, что мы при помощи скользапроса просим базу данных, перед тем, как нам данные отдать, их ещё отсортировать по значению какого-то столбца. То есть мы просим выдачу, которую мы получаем, отсортировать по столбцу ID по
Speaker A
возрастанию. То есть order by - это отсортируй по, дальше название столбца, по которому сортируем. И Ask - это значит возрастание. Desk - это значит убывание. Давайте я вот с desk сделаю запрос. Вот видите, теперь у меня по столбцу ID сортировка происходит в
Speaker A
обратном порядке. Ну а если ask, то сортировка будет производиться в прямом порядке. Что значит ask - это askendдинг. Диск - это дискединг. Это два английских слова. Первое означает восходящий, ну или же прямой порядок сортировки. Динendдинг - это нисходящий,
Speaker A
ну или же обратный порядок сортировки. Соответственно, андинг - это от меньшего к большему, дискндинг- от большего к меньшему. Ну и вот ask - это значит, что сортируй по возрастанию, ак значит сортируй по убыванию. Ну и вот эти наши
Speaker A
ask, де, когда ask у меня сортируется по взрастанию, когда desk, то по убыванию. О'кей, давайте оставлю ask. И здесь у меня есть лимит 100. Что такое лимит и для чего он нужен? Чтобы понять, что такое лимит в нашем SQL-запросе, для
Speaker A
начала необходимо ознакомиться с таким понятием, как погинация. Что такое погинация и для чего она нужна? Ну, давайте представим такую ситуацию, что у нас есть какая-то таблица пользователей.
Speaker A
Вот, как мы видим, у каждого пользователя есть ФИО и номер телефона. И в этой таблице пользователей у нас 100 млн записей. То есть, предположим, у нас какая-то мегапопулярная социальная сеть, например, тот же ВКонтакте или Telegram, и у нас зарегистрировано огромное
Speaker A
количество пользователей в нашей социальной сети. Соответственно, у нас 100 млн записей, 100 млн зарегистрированных аккаунтов. И у нас вся таблица в таблице 100 млн записей просто огроменно. Вот она большая, большая, большая, большая. Ну и вот тут вот можно представить, что это 100 млн
Speaker A
записей. Предположим, что вот эти вот 100 млн записей на жёстком диске у нас занимают 10 Гб. Ну и казалось бы, какие проблемы? Мы 100 млн записей сохранили.
Speaker A
Это занимает всего лишь 10 ГБ нашем жёстком диске. Жёсткий диск у нас там может быть на несколько терабайт. Ну и, соответственно, у нас вся информация хорошо сохраняется. Предположим, что эта таблица у нас называется users, ну, то есть пользователи. И мы из нашего
Speaker A
гошного приложения в эту таблицу отправляем следующий SQL запрос. Select звёздочка from users. То есть я хочу получить столбец Fio и номер телефона из таблицы users без каких-то дополнительных фильтрационных параметров. Что это значит? У нас нету никаких условий в нету никаких лимитов
Speaker A
или ограничений. То есть этот SQL-запрос мне вернёт абсолютно все строки, абсолютно все 100 млн записей из таблицю users. Соответственно, вот эти вот 10 ГБ, которые хранились на жёстком диске, будут взяты из базы данных, взяты из таблицы users, все эти 10 ГБ и полетят
Speaker A
ко мне в гошное приложение. Гошное приложение - это в первую очередь приложение, это в первую очередь какая-то программа, а программы у нас запускаются и работают в оперативной памяти. Обычно в продовых серверах, в продовых конфигурациях каждое гошное приложение, оно запускается в некоторой
Speaker A
такой изолированной среде. Мы с вами дальше разберём, что это за изолированная среда. И в этой изолированной среде гошному приложению выделяется обычно что-то около одного-двух, может быть четырёх ГБ оперативной памяти, но зачастую это именно 1 ГБ оперативки, выделяется на
Speaker A
всё гошное приложение. Ну, потому что обычно больше и не нужно. И мы попадаем в такую ситуацию. Наше гошное приложение, которое имеет в своём распоряжении лишь 1 ГБ оперативной памяти, делает следующий SQL запрос в таблицу, которая весит 10 Гб. К чему это
Speaker A
приводит? Это приводит к тому, что все вот эти вот 100 млн записей, все эти 10 ГБ на жёстком диске, они после выполнения вот этого SQL-запроса просто летят в оперативную память. К чему это приводит? Это приводит к тому, что 10 ГБ
Speaker A
данных, хранимых на жёстком диске, не помещаются в 1 ГБ оперативки. К чему это приводит? К тому, что у нас оперативная память заполнена до конца. Гошному приложению больше негде функционировать, и оно экстренно завершается. И как бы мы не пытались там как-то ужаться, как-то
Speaker A
что-то придумать, у нас в любом случае будет происходить вот эта вот неприятная ситуация, что мы просто не можем получить данные из таблицы users, потому что она весит 10 гигов. А у нас в нашем распоряжении нету этих 10 Гб, у нас
Speaker A
только 1 Гб. Что же в такой ситуации делать? Умные люди думали, думали и придумали такой механизм, который называется пагинация. В чём его основная идея? Основная идея механизма пагинации в том, чтобы разделить строки, хранимые в таблице, на несколько отдельных
Speaker A
частей. Вот первая часть, вот вторая часть, вот третья часть, вот четвёртая часть и так далее. И мы вместо того, чтобы разом выгружать целую таблицу, состоящую из 100 млн записей, разом выгружать целую таблицу, которая весит 10 ГБ, мы будем выгружать лишь вот эти
Speaker A
вот маленькие части одна за одной. Условно говоря, вместо того, чтобы пытаться сразу прожевать огроменный кусок хлеба или же огроменный кусок мяса, мы будем этот огроменный кусок пережёвывать небольшими частями. Таким образом, к чему это приводит? Если вся таблица у нас весит 10 гигов, то вот
Speaker A
этот вот маленький кусочек у нас может весить, ну, например, 10 Мб. Вот этот вот кусочек весит 10 Мб. Вот этот вот кусочек весит 10 Мб. Вот этот кусочек и так далее. Таким образом, мы уже можем не пытаться запихнуть все 10 гигов разом
Speaker A
в нашу маленькую оперативную память размером 1 Гб. Мы можем просто сначала достать один кусочек из таблицы, вот этот вот 10 Мб, он спокойно поместится в нашу одногиговую оперативную память. Мы переварим этот кусочек 10 Мб, потом возьмём следующий кусочек, переварим
Speaker A
этот следующий кусочек, там что-то сделаем с данными, отдадим их пользователю или же произведём какие-то там математические операции, если нам нужно, и вернём на место. После этого следующий кусочек возьмём. Он опять же спокойно поместится в нашу оперативную память. мы его обработаем и потом уже
Speaker A
возьмём опять следующий кусочек. Итак, пока не дойдём до конца таблицы. Таким образом, благодаря тому, что мы разделили огромную таблицу из 100 млн записей на несколько маленьких кусочков, мы смогли переварить эту огромную таблицу просто не разом все 100 млн
Speaker A
записей за один запрос, а по небольшим кусочкам. Так вот эти небольшие кусочки обычно принято называть страницами. То есть вот это - это страница один, вот это - это страница два, вот это страница три, это страница четыре, ну и так
Speaker A
далее. То есть дальше у нас там идёт страница пять, страница шесть, страница и так до самой последней страницы. Так вот, пагинация, слово пагинация произошло от английского слова page, то есть страница. То есть, по сути, погинация - это механизм разбиения
Speaker A
каких-то огромных больших данных на отдельные странички. В нашем случае мы огроменную таблицу на 100 млн записей разделяем на небольшие странички. Ну, на схеме у нас видно, что это по четыре записи. На самом деле мы можем побольше записей брать в одну страницу, например,
Speaker A
по 100 записей или 1.000 записей. То есть 1.000 записей за раз мы вполне сможем обработать. Хотя, конечно, зависит от того, как именно мы обрабатываем эти самые записи, что именно мы с ними делаем, какого размера эти самые записи, какие данные в них
Speaker A
хранятся, ну и так далее. И общая идея механизма погинации в том, чтобы какие-то огромные данные разделять на небольшие странички и обрабатывать эти самые данные уже маленькими кусочками по одной страничке. Хорошо, с этим разобрались. Как именно работает пагинация в базе данных Постгрес? Как
Speaker A
она реализуется при помощи языка запросов SQL? В SQL для реализации механизма погинации вводится два дополнительных параметра. Первый параметр - это лимит. Второй параметр - это офсеset. Как эти два параметра работают? Параметр лимит говорит, какое количество строк попадут в нашу
Speaker A
страницу. Если у нас лимит стоит четыре, в таком случае в очередную страницу попадут четыре строки. Если бы у нас тут лимит был задан, например, три, тогда в очередную страницу попало бы только три строки, и у нас была бы вот такая вот
Speaker A
страничка. Если бы лимит тут был задан пять, тогда у нас в очередную страницу попало бы целых пять записей. Вот таким вот образом. Но я пока оставлю лимит равночетыре. Соответственно, когда у нас лимит равночетыре, у нас в страницу попадают только четыре записи. Вот так
Speaker A
вот. Что же такое offset? Offset - это смещение относительно первой записи. Что это значит? Это значит, что если мы вот пишем SQL запрос, select звёздочка from users, после этого мы пишем limits set. В таком случае мы получим вот эту
Speaker A
вот самую первую страницу. После этого мы получили вот эту вот первую страницу благодаря вот этому SQL запросу. Как нам получить следующую страницу? Для того, чтобы получить следующую страницу к офсету, мы должны добавить лимит. Офсет у нас 0, лимит 4. 0 + 4 - это 4.
Speaker A
Соответственно, офсет у нас должен стать равен четыре. Что это значит? Это значит смещение у нас будет на четыре строчки.
Speaker A
1 2 3 4. А что значит смещение? Смещение задаёт начало страницы. То есть откуда у нас начнётся страница? Теперь у нас страница начинается со смещением четыре.
Speaker A
Вот отсюда вот сюда вот мы попадаем. Благодаря тому, что мы поменяли offset, мы получили следующую страницу. В SQL-запросе это выглядело бы следующим образом. Offset 4. Ну хорошо, допустим, мы эту вторую страницу тоже обработали, мы хотим дальше получить данные. Как нам
Speaker A
это сделать? Всё просто. Мы опять к офсету должны прибавить лимит. Офсет у нас 4тыре, лимит 4. + 4 - это 8. Значит, суммарный офсет относительно начала таблицы у нас будет восемь. 1 2 3 4 5 6 7 8. Вот напишу офсеset восемь, и мы
Speaker A
попадаем вот сюда. А это что такое? А это третья страница наша. Соответственно, SQL запрос выглядит точно так же, только осет у нас уже восемь. Таким образом, благодаря вот этим вот двум параметрам лимит и офсет, в SQL реализуется механизм погинации.
Speaker A
Параметр лимит отвечает за то, какого размера будет страница в пагинации, а параметр офсеset отвечает за то, какое смещение относительно начала таблицы будет у очередной страницы. И по сути вот этот вот параметр оset нужен именно для того, чтобы двигать вот эту вот нашу
Speaker A
страницу дальше. То есть мы обработали вот эту страницу, например, хотим идти дальше. Для этого мы к авсету прибавляем лимит, то есть к восьми прибавляем четыре, это получается 12. И мы, получается, съезжаем вот сюда. И значит, что у нас уже будет следующая страница
Speaker A
обрабатываться. Оset 12. И мы получаем вот эту следующую страницу. Таким образом, благодаря вот этим вот двум параметрам офсет и лимит у нас реализуется механизм погинации в СQэле.
Speaker A
Кому интересно, вы можете сами попрактиковаться в этом. Это достаточно просто. Добавьте в таблицу какую-нибудь несколько записей. Пускай это будет 15 или 20 записей. И попробуйте из этой таблицы пополучать данные. Сначала просто select звёздочка from, там ваша таблица, потом попробуйте накинуть
Speaker A
лимит, потом попробуйте накинуть офсеset. И таким образом вы поймёте, вы пощупаете, как работает механизм погинации. У нас, к сожалению, не так много времени сейчас это всё рассматривать, поэтому мы обсуждаем пока это только на теории. Но, повторюсь, крайне вам советую попрактиковаться в
Speaker A
этом. Как раз отличный повод создать себе ещё одну таблицу, положить в неё какое-то количество данных и поиграться с этими двумя параметрами, лимит и осет, для того, чтобы пощупать, что же такое это ваша погинация, как она выглядит вживую. Просто полезно знать, что такое
Speaker A
погинация и для чего она нужна. Так вот, возвращаемся к самому нашему изначальному вопросу. Что такое лимит 100 в SQL-запросе, который у нас генерируется, когда мы с вами нажимаем View Edit Data first store offs?
Speaker A
Обратите внимание, first 100 store значит первые 100 записей, первые 100 строк. У нас происходит выборка из таблицы Tasks. Она сортируется по ID по возрастанию. Это значит, что самые первые вставленные записи у нас будут в самом начале, а в конце самые последние.
Speaker A
Лимит 100 значит ограничить полученную выборку на 100 записей. Ну и осет ноль мы можем не писать, потому что он всегда подразумевается по умолчанию. То есть, если у нас не написан of офсет, значит, у нас всегда по умолчанию идёт offset
Speaker A
ноль. Если у нас не написан лимит, значит, у нас всегда по умолчанию бесконечный лимит. Вот. Но так как нам нужно получить первые 100 записей, потому что здесь эта кнопка first 100 store rows, то как раз-таки мы пишем лимит 100. Ну, точнее, PG adдмин за нас
Speaker A
это пишет. Если мы здесь нажмём просто all rows, тогда, как мы видим, у нас не будет никакого лимита. Но если мы нажимаем first 100 store rows, тогда, конечно же, лимит есть. Почему вообще нам ПГадмин предлагает эту кнопку? Ну,
Speaker A
повторюсь, потому что у нас в таблице может быть миллионы, миллиарды записей. Ну, я условно говорю, миллиарды, конечно, навряд ли. Скорее миллионы могут быть записей, вполне. И если бы у нас была только лишь кнопка All Rows, то мы бы в некоторых случаях обрекали себя
Speaker A
на то, что мы бы просто не смогли посмотреть имеющиеся в таблице данные, потому что наш ПГадмин не вывез бы и упал с ошибкой. То есть PG Adдми - это точно такое же приложение, и у него есть точно такие же ограничения по
Speaker A
потребляемым ресурсам. Просто не 1 ГБ оперативной памяти, а может быть, у вас будет доступно на компьютере, ну, например, 12 ГБ оперативной памяти или 30 ГБ оперативной памяти. Но ПГадмину помимо того, чтобы выгрузить всю таблицу в оперативную память, ему её нужно ещё и
Speaker A
отрисовать, а это ещё больше накладные ресурсы требует. Поэтому, как из Гошки, мы можем отправлять SQLзапросы в базу данных с лимитом и офсетом. Так и из ПГадмина у нас есть опция отправить SQL запрос в базу данных с лимитом 100.
Speaker A
Просто для того, чтобы обезопасить себя от каких-то потенциальных проблем. Просто из-за того, что мы вдруг внезапно решили 100 млн записей выгрузить в ПГадмин. Но благодаря тому, что мы можем ограничивать количество получаемых записей определённым числом, мы постранично при желании можем посмотреть
Speaker A
все имеющиеся данные. Если у нас лимит 100, то изначально офсеset ноль. Потом следующая страница, допустим, у нас миллион записей, была бы уже доступна с офсетом 100. Следующая страница с офсетом 200, следующая страница с офсетом 300. Ну и так далее. Ну вот
Speaker A
отсюда и берётся вот это вот limit 100 в нашем SQLзапросе. Ну и Order by ID Ask мы тоже разобрали. Теперь, наконец-таки, мы готовы идти дальше и рассмотреть этот же самый selectзапрос. То есть select - это запрос на выборку, на получение
Speaker A
данных из базы данных, но уже не в ПГадмине, а в нашем гошном приложении, потому что не только PGми нуждается в том, чтобы получать данные из базы данных, но ещё и гошное приложение. Ну, например, в юзскейсе, в сценарии использования, когда наш пользователь
Speaker A
хочет посмотреть все задачи, которые у него есть. То есть ему нужно сделать выборку из базы данных. Ну а выборка у нас делается при помощи select SQL запроса. Хорошо, давайте напишем SQL запрос для выборки данных из базы данных в Goeng приложение. Создам файл в пакете
Speaker A
Simplq. Назову этот файл simple.go. пакет simple scale и функция select rows. Она как обычно будет принимать у меня контекст и будет принимать у меня подключение к базе данных. Ну, точнее указательно подключения. Но обычно в разговоре говорится просто подключение
Speaker A
на базу данных. Обычно всем и так понятно, что это либо указатель, либо интерфейс, но не обычная копия. Значит, возвращать она будет ошибку и что-то ещё. Вот давайте сейчас посмотрим, что же ещё Select Rows будет возвращать. Ну, сперва мы опишем SQL Query, то есть наш
Speaker A
SQL запрос на выборку. Будет он у нас максимально простой. Select. После этого должен перечислить столбцы, которые есть в нашей таблице и которые я хочу получить. Ну, я хочу получить ID, title, description, completed, created at и completed at.
Speaker A
Все эти столбцы я хочу получить. Потом я пишу from и название таблицы, из которой я буду эти данные получать. Tasks.
Speaker A
Синтаксис SQL запросов очень многогранен. Тут есть огроменное количество деталей. Я вам крайне советую в нашем приватном сообществе ознакомиться со всеми материалами, домашними заданиями и тренажёрами, которые помогут довести ваш уровень и знания СQэля до необходимого и достаточного, чтобы писать коммерческие
Speaker A
проекты и успешно проходить собеседование. Вам, как разработчикам, это сильно поможет. Поэтому я сейчас все тонкости селект запроса описывать не буду. Ну, добавлю, что тут я могу писать ключевое слово и после него точно также дополнительные условия, которым должны удовлетворять строки, которые я хочу
Speaker A
получить в своей выборке. Например, я могу написать where completed = true. Таким образом, я получу выборку всех задач, у которых completed поле равно true. То есть вот я получу в данном случае только третью задачу. Благодаря этому я сделаю выборку из таблицы tasks
Speaker A
и получу только те строки, у которых поле completed, у которых столбец completed имеет значение true. В данном случае это только запись с айдишником три. Все остальные я не получу. Ну, а если я тут напишу where completed равно false, тогда я наоборот получу все
Speaker A
строки, у которых значение столбца completed false. Это все строки в нашем случае, кроме строки с айдишником три.
Speaker A
Ну, а если никаких условий дополнительных я сюда писать не буду, тогда я просто получу все записи, которые у меня есть в таблице Tasks. Ну, давайте я вот получу все записи. Теперь уже я не просто отправляю какой-то SQL запрос в базу данных, я хочу получить
Speaker A
ещё и ответ. То есть я хочу получить конкретно выборку данных, которая соответствует моему вот этому вот селект запросу. Поэтому мне метод exeg структуры подключения уже не подходит.
Speaker A
Мне нужно использовать другой метод. Почему? Потому что Exeg просто отправляет SQL запрос в базу данных, но в ответе никакую выборку данных он нам не пришлёт. Для этого нам нужно воспользоваться другим методом. Нам нужно воспользоваться методом Qury. Мы видим их здесь два. Query querer r. Чем
Speaker A
они отличаются? Различие следующее. QRY нам вернёт все строки из таблицы, которые подходят под наш SQL запрос, а QRY r вернёт лишь только самую первую строку, которая подойдёт под наш SQL запрос. То есть QR на самом деле под капотом просто делает лимит один. Вот
Speaker A
что делает QR. Но нас интересуют все записи, которые подойдут под наш SQL запрос, поэтому я использую не QRO, а я использую просто QRY. Сюда я передаю контекст и передаю SQL запрос, который я описал. Возвращает QRY у нас два
Speaker A
значения. PGX RS. Сейчас посмотрим, что это такое. И возвращает нам ещё возможную ошибку. Хорошо. Значит, RVS запятая равно connection. Query. Давайте посмотрим, что за RS. Rows - это какой-то интерфейс. То есть мы даже не знаем, что конкретно нам возвращается.
Speaker A
Мы лишь видим интерфейс. Конечно, при желании мы можем докопаться, что вот, например, тут можно base rows увидеть, нам возвращается, может быть, нам ещё что-то возвращается, но если нам возвращается интерфейс из какой-то библиотеки, то в большинстве случаев нам особо не нужно задумываться о том, что
Speaker A
там за подкапотная реализация. Вот нам возвращается интерфейс. Что он у него есть? Метод close. Метод делает подключение доступным снова. Что это значит? Вот это значит, что если одним и тем же подключением пользуются одновременно несколько разных сущностей в нашей программе, то, вызвав метод, мы
Speaker A
освободим подключение и передадим его следующей сущности. То есть мы скажем, что мы закончили работу с этим подключением. О'кей. Метод у нас есть другие методы всякие. Ну, тут, в принципе, их довольно много. Нас все не интересуют. Нас интересует конкретно два
Speaker A
вот этих вот метода: Next и scan. Метод Next, он переключает нечто, что находится под интерфейсом Rows, в состояние обработки следующей строки. А метод скан, он сканирует текущую строку в определённые переменные, которые мы туда передадим. Ну, звучит, на самом
Speaker A
деле сложно, выглядит попроще. Давайте перейдём к тому, как это, собственно, используется. Вот я получил вот этот вот интерфейс PGX Rows. После этого я хочу из него достать, собственно, все строки.
Speaker A
Напомню, ров по-английски - это строка по-русски, ну а ровс - это строки. То есть я хочу из этого pgx rs достать какие-то конкретные значения строк, которые у меня в базе данных лежали, которые я получил при помощи этого SQL
Speaker A
запроса. Выглядит это следующим образом. Я пишу цикл for Rows. Next. Next у меня возвращает булевское значение. Что это значит? Это значит, что как только next вернёт false, то мой цикл for закончится. А когда вернёт fse? Next вернёт фSE тогда, когда я прочитаю всю
Speaker A
выборку, полученную. Когда дальше уже нет никаких строк, тогда я получу фse, значит, что я прочитал всю выборку, которую я получил, и цикл можно завершать. Хорошо. Но до тех пор, пока next возвращает true, значит, что дальше ещё есть строки, значит, дальше нужно
Speaker A
продолжать выполнять цикл. После этого я должен объявить переменную или переменные, в которые я буду сканировать получаемые из таблицы данные. В рамках нашего вот этого репозитория у меня нет никакой структуры Task, поэтому я каждый отдельный столбец буду сканировать в
Speaker A
отдельную переменную. Для этого я создам пять переменных. Первая - это tйтle типа string. Вторая - это description, тоже типа string. Третье - это completed, типа. Четвёртое - это у меня created, типа time. И пятое - это completed, только уже типа указательно
Speaker A
time. Почему? Да потому что если у меня в таблице будет лежать nл значение, то оно автоматически преобразуется внил указатель. Ну а если там будет какое-то значение, тогда преобразуется в указатель на конкретное значение.
Speaker A
Поэтому указатели на уровне языка программирования и нал на уровне базы данных можно использовать сообща.
Speaker A
Повторюсь, если в базе данных значение nл, тогда библиотека pgx преобразует его вл указатель. Если там есть какое-то на самом деле у нас значение и у нас не нал, тогда оно преобразуется в конкретное значение. Если у нас указатель, то у нас будет указательные
Speaker A
конкретное значение. Ну и, конечно же, я забыл ещё, что я айдишник тоже получаю, поэтому war idt.
Speaker A
О'кей. Дальше я пишу RVS. И сюда я передаю указатели на все объявленные переменные, в том числе на completed. Получится указатель на указатель. Довольно забавно. Значит, первым делом я передаю указатель на ID.
Speaker A
Потом я передаю указатель на title. Почему в таком порядке? Потому что в таком порядке я объявил столбцы при SQL-запросе. Если бы я тут сначала объявил tйтle, а потом ID, тогда здесь, соответственно, они должны были быть в обратном порядке. Но я буду следовать
Speaker A
тому порядку, который у меня объявлен при создании таблицы. Это наиболее логично. И поэтому я сначала сканирую полученное выборки значения в переменную ID, потом в переменную title, потом в переменную description, потом в переменную Completed, потом в переменную Created, потом в переменную Complet. Ну и,
Speaker A
собственно, всё. У меня скан может вернуть ошибку. Давайте я буду её получать и смотреть, что если у меня ошибка неравна, тогда я буду просто-напросто эту самую ошибку возвращать. Всё. Иначе я выведу на экран полученные значения. Давайте я это и
Speaker A
сделаю. FMT, println. И тут я выведу просто ID, title, description, completed, created, completed, всё. И тут return new. Я ещё забыл кое-что сделать. Это, конечно же, обработать ошибку, которая у нас может вернуться при выполнении SQL запроса. Давайте её я
Speaker A
обработаю. Если R не равно, тогда просто её верну из этой функции. И, конечно же, вспоминаем, что мы с вами quS увидели метод close. Что это значит? Это значит, что его по-хорошему вызвать, да, чтобы освободить это самое подключение для
Speaker A
следующих сущностей, для следующих частей программы, которые будут использовать одновременно с нами то же самое подключение. Поэтому я здесь сразу же после того, как я удостоверился, что я получил корректные ROVS, то есть здесь не было никакой ошибки, я пишу de rows.
Speaker A
Что это мне даёт? Это мне даёт гарантию, что как только моя вот эта вот функция Select RS завершится, неважно здесь или здесь, я в любом случае освобожу подключение КОН для следующих потребителей. Хорошо, что же в этом коде вообще у нас произошло? Мы создали SQL
Speaker A
запрос на выборку следующих перечисленных столбцов из таблицы Tasks. Потом мы выполнили этот SQL-запрос и получили из базы данных сырые данные.
Speaker A
Вот эти вот данные сырые мы с вами получили, проверили, что не было ошибок, и задефёрили освобождение подключения.
Speaker A
После этого в цикле каждый раз вызываем rows next. Благодаря вот этому мы каждый раз на каждой новой итерации цикла будем на обработку выбирать новую и новую и новую строку, полученную из базы данных.
Speaker A
Если next возвращает true, значит, ещё есть строки, которые можно обрабатывать. Если next возвращает false, это значит, что больше нету строк на обработку. Это значит, что цикл можно завершать. Ну, потому что напомню, что если в цикле есть какое-то условие, то цикл будет
Speaker A
работать до тех пор, пока это условие равно true. Как только оно равно false, то цикл завершается. Хорошо, сделали rs next первый раз попали на первую строку.
Speaker A
После этого мы объявляем на каждой новой итерации цикла заново эти переменные. Напоминаю про области видимости, что если у нас переменная объявлена внутри цикла, то область её видимости - это одна итерация цикла. И как только мы перейдём на следующую итерацию, то эти
Speaker A
переменные будут созданы заново. Старые их значения, они останутся в старой итерации, если мы их никуда дополнительно в какое-то внешнее хранилище не положим, в какой-нибудь слайс или что-нибудь такое. попали на первую вот эту нашу строку, вызвав первый раз rows next, объявили
Speaker A
переменные, в которые мы будем получать значение вот этих вот столбцов. И при помощи rows. Мы каждый отдельный столбец в рамках первой строки просканировали в соответствующие переменные. После этого проверили, что не было никаких ошибок при этом сканировании, и после этого
Speaker A
начали выводить на экран полученные значения. Всё. Таким образом, мы с вами получили все данные из базы данных и вывели их на экран. Ну давайте посмотрим, как это работает. Вaфункции я комментирую вставку. Я не хочу ничего дополнительно вставлять. Я просто вызову
Speaker A
функцию simpleql.select rows. Передам туда контекст, передам туда подключение и проверю, что никаких ошибок у меня не было. Если ошибка была, то кину панику. Запускаю программу и я вижу, что я получил все данные, которые у нас лежали в таблице Tasks из базы
Speaker A
данных в гошное приложение. Первым делом у меня идёт айдишник записи, потом у меня идёт её заголовок, потом у меня идёт description, потом completed, потом created, потом completed, как мы видим, completed add везде nil, потому что на уровне базы данных он у нас нал. Давайте
Speaker A
я вот здесь вот, например, в домажку completed задам время. Ну вот, пускай будет у меня двадцать седьмой год. Окей.
Speaker A
И теперь я могу сохранить изменения. Вот сюда вот нажимаю Save Data changes. Всё, я при помощи интерфейса PG adдми вручную изменил значение столбца completed AD у записи с айдишником один. Ну и tйтle домажка. Давайте посмотрим теперь, что нам выведет гошное приложение.
Speaker A
Как мы видим, там, где у меня домажка с айдишником один, теперь у меня completed add - это вполне себе конкретная дата, вполне себе конкретное время. а не просто нил. Ну, таким образом у нас происходит взаимодействие между нал или
Speaker A
же конкретным значением в базе данных и между переменной на уровне гошки. Но если мы точно знаем, что у нас какое-то конкретное значение будет лежать в столбце, что он not null, тогда нам не нужны никакие указатели. Мы с вами можем
Speaker A
наблюдать, что у нас почему-то нету корректной сортировки по айдишникам, но потому что мы здесь не указали order buy. Давайте это сделаем. Order by id.
Speaker A
То есть я хочу, чтобы у меня по айдишникам была сортировка по возрастанию. Ещё раз запускаю. Ну и теперь вижу, что у меня всё отсортировано. Вывод может быть не самый идеальный. Мы можем над этим чуть-чуть поработать. Ну давайте я отдельную
Speaker A
функцию создам, приватную. Fun print task. Она у меня будет принимать ID, title description completed.
Speaker A
Не влезает у нас всё, поэтому разбиваю каждый аргумент на новую строку. После completed created at time тоt и completed at указатель на time.time.
Speaker A
Всё. И что эта функция у меня будет делать? Она будет следующим образом выводить каждую отдельную задачу.
Speaker A
Сначала будет писать вот такие вот тире, чтобы отделить каждую новую задачу. После этого будет выводиться ID двоеточие ID. Ну и так для всех остальных полей. После этого после ID у нас title, двоеточи title, потом у нас completed, completed, двоеточи completed, ну и так
Speaker A
далее. created at двоето created completed add двоето completed at ну и description забыл, конечно же, вот он description.
Speaker A
Всё. После этого я вместо вот такого сухого вывода буду вызывать функцию print task и передавать туда все эти параметры id title description completed created completed вызываю запускаю и как я вижу теперь у меня каждая отдельная задача имеет такой
Speaker A
вот красивый вывод где все её поля наглядно видны её значения ну и казалось бы всё круто мы теперь с вами умеем создавать таблицу в базе данных мы с вами умеем туда вставлять какие-то это новые записи. Мы умеем эти новые записи
Speaker A
удалять, мы умеем их обновлять, мы умеем получать эти самые данные из нашей базы. Но тут есть некоторая проблема, которая заключается преимущественно в том, что так как мы с вами пишем, может быть крайне неудобно работать с данными.
Speaker A
Например, вот я хочу сделать selectров, то есть я хочу получить вот эти вот все данные, которые лежат в нашей таблице. Я их получаю, но я их просто кладу в переменные и потом вывожу на экран. А что, если я захочу вернуть эти самые
Speaker A
данные, все наши данные из таблицы, из функции, чтобы потом, к примеру, в мейне получить эти самые rows, получить эти данные и потом что-нибудь с ними сделать, например, отдать в HTTP ответе.
Speaker A
То есть пользователь сделает какой-то HTTP запрос на получение задач, то есть get tasksса. Мы в свою очередь сходим в базу данных, в таблицу Tasks и получим все эти Tasks из нашей базы данных. Да.
Speaker A
Вот мы получили наши Rows. После этого мы хотим эти Rows отдать по HTTP обратно пользователю, чтобы он после отправки HTTP запроса получил HTTP ответ со всеми данными, которые у него хранятся в базе данных. Как мы бы это могли сделать,
Speaker A
если у нас следующий формат получения данных из базы, когда мы просто каждую отдельную строку, каждый отдельный столбец в отдельную переменную кладём?
Speaker A
Ну, мы бы могли с вами придумать какой-нибудь массив, ну, то есть слайс. И каждый элемент этого слайса - это была бы мапа, где ключом является название поля, а значением является, собственно, значение этого поля. Но это было бы неудобно, потому что у нас значения
Speaker A
могут быть разных типов. Тогда бы мы могли в значении хранить просто интерфейс пустой, а потом при необходимости приводить этот интерфейс к определённому типу. Ох, ну давайте посмотрим, как это могло бы выглядеть. У нас была бы переменная tasks, это у нас
Speaker A
был бы слайс, состоящий из мап. У каждой мапы ключ - это название поля, а значение - это пустой интерфейс. Ну и, соответственно, размер этого слайса пускай ноль будет изначально. Тогда бы нам после каждого скана нужно было бы создавать tasк. Это у нас была бы мапа,
Speaker A
ключстн, значение пустой интерфейс. И после мы бы сразу же на месте инициализировали эту мапу, писали бы ID, там title title и так дальше, и так дальше. После этого мы бы созданную таску добавляли в слайs. Tasks равно append task. И
Speaker A
возвращали бы всё это в конце из нашей функции. Возвращали бы этот самый слайс вот этих вот непонятных вообще каких-то вот мап. Значит, тут мы возвращаем уже не просто erро, а возвращаем ещё слайс map, где ключом является строка значением пустой интерфейс. Вот таким
Speaker A
вот образом, просто страшным это всё бы выглядело. Вот так вот. Ну и тут, значит, запятая erро, а здесь тоже л запятая еро, а здесь tasks запятаял.
Speaker A
Вот так вот бы это всё выглядело. Ну, тут понятное дело до конца это всё надо дописать. Страшно, очень страшно. А потом как это использовать, это вообще непонятно. Это очень сложно было бы использовать. Именно поэтому для того, чтобы работать с какими-то таблицами, с
Speaker A
какими-то данными в базе данных изнутри языка программирования, обычно создают так называемые модели. На самом деле модель - это нечто похожее на то, что мы с вами уже знаем. А мы с вами рассматривали дтшки. Но в чём отличие?
Speaker A
Отличие в том, что дшки используются, чтобы обработать входящий HTTP запрос и превратить входящие JSON байты в какую-то понятную структуру на уровне языка программирования. А вот модель уже нужна, чтобы преобразовать сырые данные из базы данных, которые мы получаем.
Speaker A
Поэтому, когда мы с вами видим дтошку, то мы можем сразу понимать, что это сущность для работы с входящими какими-то сетевыми запросами. А вот модель - это сущность для того, чтобы работать с данными, которые у нас лежат в базе данных. Более конкретно мы про
Speaker A
это поговорим на теме архитектура приложения, но пока важно лишь такое максимально общее понимание, что ДТошки используются для обработки входящих сетевых запросов. Ну, например, входящий HTTP запрос. А модель у нас нужна для того, чтобы работать с данными, которые приходят из базы данных. Получается,
Speaker A
дтшки мы используем на том уровне нашего приложения, которое обрабатывает входящие сетевые запросы и предоставляет какой-то доступ для работы с нашим приложением внешним каким-то клиентам.
Speaker A
Повторюсь, например, HTTP-клиентом. А на том уровне нашего приложения, где мы взаимодействуем с базой данных, там мы уже данные представляем в виде моделей и называем это всё моделями. Хотя, что дтшка, что модель в конечном счёте - это обычные гошные структуры, обычные
Speaker A
структуры на уровне языка программирования, но как минимум благодаря тому, что они называются по-разному, мы можем их различать, мы можем наделять их различной функциональностью, потому что бывает такое, что нам, чтобы принять HTTP запрос, нужна одна функциональность. А чтобы получить или, наоборот, положить
Speaker A
какие-то данные в базу данных, нам вот у этой вот структурки, которая представляет сами данные, нужна другая функциональность. Поэтому идёт такое разделение. Повторюсь, сейчас важно лишь общее понимание. Более конкретно мы это рассмотрим на теме архитектура приложения. Сейчас же я это всё сказал
Speaker A
для того, чтобы было общее понимание, почему не принято вот так вот голыми данными просто общаться на разных уровнях нашего приложения. Да потому что это неудобно, да? Представьте, мы вот это вот всё чудо, которое у нас лежит в базе данных, вот оно, преобразуем в
Speaker A
слайс map. И у каждой мапы ещё значением является пустой интерфейс, то есть вообще непонятно что. Вот представим, что мы в мейне не только ошибку получили, но ещё и вот tasks.
Speaker A
Вот так вот мы это всё получили, допустим, проверили, что там у нас ошибка не равна Нил, да, кинули панику, если ошибка, и вот наши tks. Что нам с этим [смех] делать вообще? Но это очень страшно. Это очень неудобно. Нам,
Speaker A
получается, нужно идти циклом по этой мапе, каждое поле преобразовывать в конкретный типы. Только тогда мы сможем с этим работать. Это прямо очень большой труд, который нам не нужен. Заместо этого мы можем данные, которые мы получаем из базы данных, класть не в
Speaker A
мапу, а создать отдельную для этого модель для того, чтобы работать с этими данными. Давайте её и создадим. Я создам новый файл в рамках пакета SimpleusQL.
Speaker A
Назову его models.go. В этом файле я опишу модель для взаимодействия с базой данных. На примере вот селект запроса. То есть слева у меня сектзапрос, справа модель, с которой этот selectзапрос будет работать. И в этом файле models. Я опишу
Speaker A
модель для работы с базой данных, конкретно с таблицей tasks, чтобы я мог вот эти вот данные получать и с ними работать. Давайте я это сделаю. Пакет Simple Scale. И здесь у меня обычная структурка Type Task model strctct. То
Speaker A
есть это модель, которая будет представлять каждую отдельную строчку из таблицы tasks. Ну, то есть, по сути, каждую отдельную задачу, хранимую в базе данных. Давайте я вот создание таблицы налево положу, чтобы видеть, какие у меня тут вообще есть поля. О'кей, у меня
Speaker A
есть поле ID типа integer, у меня есть поле title типа string. Для чего я пишу эти поля с большой буквы? Ну, я хочу, чтобы они были публичные, чтобы другие пакеты, которые вызывают мой пакет Simple SQL, смогли получать вот эти вот
Speaker A
task model и смогли получать отдельно их поля. Потому что если бы тут было с маленькой буквы, то смысл от того, что какой-то потребитель нашего пакета Simple Scale получил там, например, слай с этих Task model, нет никакого, потому что он не может получить доступ к
Speaker A
каждому отдельному полю. Поэтому делаю эти поля публичными, называю их с большой буквы. Дальше у меня идёт description, тоже строкового типа. Дальше у меня идёт completed булевского типа. Далее у меня created, типа тоt и дальше у меня completed at,
Speaker A
типа указатель на time. Поздравляю, я сделал модель для нашей задачи. Теперь как я эту модель буду использовать?
Speaker A
Начну с select запроса. И здесь функции select rows вместо того, чтобы объявлять вот эту вот кучу переменных, я объявлю одну лишь только переменную Task. И типу этой переменной Task Model. И уже в этой структуре будут содержаться все вот эти
Speaker A
вот переменные, которые я ранее объявлял отдельно, но теперь они все собраны в одной модели. Соответственно, они мне больше здесь не нужны. И в скан я буду класть отдельные поля этой самой модели.
Speaker A
То есть, к сожалению, в PGX я не могу вот так вот напрямую сразу положить целую структуру.
Speaker A
Мне нужно отдельно её поля прописывать. Ну давайте это сделаю. Task.id первоя. Дальше у меня task.title.
Speaker A
Дальше у меня task. Дальше у меня task completed. Task created и task completed. Соответственно, то есть задачи - это у меня уже не слайс map, а слайс моделей.
Speaker A
Task model. На самом деле есть дополнительные библиотеки, которые могут позволить сканировать целую модель в одну строчку. Но если кому-то интересно, можете сами поискать, погуглить, поспрашивать у неронки. Всё-таки такой ванильный подход стандартный - это просто использовать px со всеми его
Speaker A
возможностями. возможности PGX, они ограничиваются тем, что мы должны каждое поле для нашей модели прописать отдельно, чтобы своими руками контролировать, какой столбец, получаемый из базы данных, будет соответствовать какому полю в нашей модели. Хорошо, теперь нам нет смысла руками здесь собирать какую-то мапу. Мы
Speaker A
просто в наш слайс моделей кладём очередную просканированную модель. Теперь print task при необходимости может просто принимать одну модельку task model и выводить task.id, task.title, ну и так далее. Давайте напишем description, task completed, task created at и task completed.
Speaker A
Всё, теперь print может принять только лишь task и всё. Ну и возвращать мы уже будем не слайс MAP, а слайс моделей Task Model. Теперь снова у нас сигнатура функции помещается в одну строчку, что довольно комфортно. Ну, в принципе, всё.
Speaker A
Мы с вами делаем SQL-запрос, получаем сырые данные, потом преобразуем эти сырые данные не в какие-то непонятные мапы, а во вполне определённый task model, которые потом на уровне языка программирования крайне удобно использовать. Вот тут тут не дописал.
Speaker A
Хорошо, давайте посмотрим, как теперь это будет выглядеть в мейне. В мейне я буду получать слайс модели и ошибку.
Speaker A
Проверять ошибку. И после этого у меня есть преобразованный из сырых данных уровня Постгреса во вполне понятной модели уровня языка программирования слайс данных. И я могу с ним работать как захочу. Работать с моделями со слайсами мы с вами уже вполне умеем. В
Speaker A
том числе я потом смогу описать себе дополнительно, но уже, правда, в другом файле, task dto уйк dto будут jon теги, тут это у меня будет ID, тут это у меня будет tйтle, ну и так далее. То есть, повторюсь, благодаря тому, что у нас уже
Speaker A
данные из постгреса - это не какие-то сырые данные в виде непонятных мап и пустых интерфейсов, а вполне себе определённый task model моде, то есть вполне себе определённые модели, мы дальше уже можем комфортно с этими данными работать. В том числе тут я в
Speaker A
мейне могу дополнительно вывести на экран эти tasks просто благодаря встроенной функциональности принталена. Ну давайте я сохраню все файлы и запущу нашу программу снова.
Speaker A
Во-первых, мы видим тот же самый вывод, который у нас был до этого. Этот вывод у нас благодаря функции printask. Ну, давайте, чтобы он нам не мешал, я его закомментирую заново запускаю. И вот я вижу вывод всех наших структур просто в
Speaker A
одну строчку благодаря встроенной функциональности принтна. Если кто-то вдруг помнит про библиотеку ПП из самой первой части, то мы можем её дополнительно сейчас с вами скачать.
Speaker A
Давайте я её получу. Goget как Abun PPП. И при помощи этой самой библиотеки я сейчас делаю красивый вывод этого нашего слайса. PP Printl Tasks. Запускаю программу.
Speaker A
Тадам. Вот он наш вывод нашего слайса моделей. Раз модель, два модель и так далее. Ну, соответственно, повторюсь, получаем все преимущества того, что у нас данные хранятся в понятной определённой модели, а не в сырых данных, которые мы изначально получаем
Speaker A
из постгреса. Но преимущества модели, они не заканчиваются на одной лишь только выборке данных. Преимущества моделей распространяются на всю работу с репозиторием. Давайте посмотрим, как бы у нас выглядела функция insert. Мы бы теперь тут принимали не набор параметров, а мы тут теперь могли бы
Speaker A
принимать вполне себе определённую модель Task Model. И всё. И тут мы бы уже с вами в метод Exк передавали вполне себе определённые поля.
Speaker A
Давайте я для удобства сделаю передачу аргументов каждый с новой строчки. И тут уже task.title, task.
Speaker A
Task. Task. Всё. И мы в этой функции insert r конкретно понимаем, что мы будем вставлять, что мы будем создавать, и вполне понимаем, какие данные ожидаются в этой самой функции. Далее метод delete, но delete уже нужно смотреть, каким образом мы удаляем. Если у нас
Speaker A
удаление происходит конкретно по какой-то выборки айдишников, тогда тут вполне резонно принимать уже не слайс task model, потому что, ну, зачем нам вся информация о задаче, если мы удаляем всегда только по айдишнику? Тут уже лучше принимать слайс интов. И теперь мы
Speaker A
можем чуть-чуть переписать наш SQL-запрос. он уже будет выглядеть не idвно какое-то конкретное значение, а id = any. И в скобочках мы передаём магический параметр доллар. О, что значит any? Это значит, что если айдишник будет равен хоть какому-нибудь элементу из этого магического параметра,
Speaker A
тогда эта строчка попадает под удаление. Как это выглядит? Мы передаём контекст SQL query. И после этого мы передаём наш массив айдишников. Ну, давайте я это назову Tasks IDs. Передаю Tasks ids. И что тут произойдёт? Библиотека PGX примет этот слайс интов вот здесь вот и
Speaker A
на место вот этого магического параметра подставит этот самый слайснтов в виде нескольких значений. После этого SQL запрос посмотрит на строки, где ID равен какому-нибудь одному из этого слайса. И эти строки будут удалены. То есть, если я сюда передам массив айдишников 1 10
Speaker A
20, то будут удалены строки из таблицы tasks, у которых ID равно 1 10 и 20. То есть все три строки будут удалены за один SQL запрос. Конкретно в случае с удалением нам не особо помогают модели, именно в нашем конкретном примере.
Speaker A
Значит, select, insert, delete мы уже рассмотрели. Далее давайте посмотрим на то, каким образом модель нам помогает легче, проще и удобнее описывать функции по обновлению каких-то сущностей в базе данных. Я хочу моей задачи уметь менять заголовок. Я хочу моей задачи уметь
Speaker A
менять её описание. Я хочу уметь менять информацию о том, выполнена ли задача или нет. Я хочу, соответственно, уметь менять время её выполнения. Допустим, время создания. Я тоже хотел бы, может быть, уметь менять, но положим, что сейчас нам это не нужно. Вот я хочу
Speaker A
уметь менять 1 2 3четыре различных поля. Каким образом я бы написал функцию на языке программирования Goldeng, которая бы мне помогала менять вот эти вот поля, значения этих столбцов в базе данных постгрес. Ну, допустим, начнём с самого первого поля, с поля tiт. Вот я хочу
Speaker A
уметь менять полейт. Хорошо. Ну, я бы написал функцию, что-то вроде Update title. Эта функция бы принимала, конечно же, контекст, принимала бы она подключение к базе данных, после этого принимала бы айдишник той записи в базе данных, у которой я хочу поменять,
Speaker A
собственно, тайтл. То есть, если я передаю айдишник три, это значит, что я хочу поменять тайтл у записи с айдишником три. Ну, в принципе, логично.
Speaker A
Хорошо, айдишник бы принимали и что-то вроде Newle, то есть тот, на который я буду менять уже существующий. Возвращаем ошибочку. Ну и что бы мы делали? У нас было бы SQL query, что-то вроде следующего. SQL query update tasks, set title. Да, мы же меняем
Speaker A
title. Set title =1. Ну, то есть первый магический параметр. Where ID = доллар 2, то есть второй магический параметр. И мы бы с вами выполняли этот самый SQL-запрос через connection, context SQL query.
Speaker A
После этого первый параметр. Первый параметр у нас - это tйтle. Значит, передаём сначала new title, после этого передаём айдишник. Всё. Ну, казалось бы, всё хорошо. То есть мы с вами описали SQL запрос, мы с вами его выполнили, возможно, ошибку вернули. Что делает SQL
Speaker A
запрос? Он в таблице Tasks обновляет строки, у которых айдишник равен второму параметру. То есть вот второй параметр, который мы передали. Соответственно, у нас только одна такая запись будет.
Speaker A
Соответственно, этот SQL запрос только у одной записи изменит поле title. Ну и, соответственно, мы выставляем title на new title. О'кей. То есть, допустим, тут был бы у нас айдишник 3 и new title был бы что-то типа вот такой вот по кефй
Speaker A
смайлик. Но в таком случае этот SQL запрос бы нашёл запись с айдишником три и здесь вместо title поставил бы вот этот вот смайлик. Ну, понятное дело.
Speaker A
Хорошо. Теперь я хочу уметь не только title менять, а, как мы говорили, ещё и desрипtion. И что мне для этого делать?
Speaker A
Писать новую функцию, получается. Хорошо, пишу функцию update desption. В принципе, всё то же самое. Я могу даже скопировать.
Speaker A
Но что отличалось бы, это то, что мы уже передаём не new title, а new description.
Speaker A
И SQL Query у нас бы выставляла уже не title, а description. Ну и здесь, соответственно, мы тоже передаём уже new description. Хорошо. А теперь я хочу уметь менять поле completed. И мне что, третью функцию писать, а потом четвёртую, а потом
Speaker A
пятую. А теперь представим, что у нас таблица чуть посложнее нескольких столбцов. столбцов может быть на самом-то деле довольно много, и нам что, для каждого отдельного столбца писать вот эту вот отдельную функцию? Но это как минимум очень неудобно. Это опять
Speaker A
практически полное дублирование кода. Это всё сложно поддерживать, потому что представим, если у нас на каждое обновление какого-то поля нужно ещё какое-то уведомление отправлять кому-нибудь. Например, пользователь поменял вот эту вот свою задачу, и мы, допустим, должны ему на почту ещё
Speaker A
отправить письмо о том, что вы там поменяли свою задачу в ту-дулисте. Ну, к примеру. И что? И нам, получается, этот код нужно абсолютно везде дублировать.
Speaker A
Это неудобно. Как модель, которую мы с вами писали, task model помогает нам решить эту проблему. Проблему она помогает решить следующим образом. Я пишу одну функцию update task. Эта функция у меня принимает контекст. Эта функция у меня принимает подключение к
Speaker A
базе данных. И эта функция у меня вместо того, чтобы какое-то одно поле задачи принимать, принимает сразу всю Task model, возвращает ошибку. И как теперь будет выглядеть наш SQL-запрос? Теперь я, в отличие от предыдущих кейсов, когда я обновлял только лишь одно поле
Speaker A
выборочно, я буду обновлять сразу все поля переданной модели. Ну, за исключением поля ID, потому что ID первичный ключ. То есть мы не можем просто так поменять, потому что на этот первичный ключ могут быть завязаны другие таблицы, да? Вспоминаем, что у
Speaker A
нас Постгрес - это реляционная база данных, а значит, просто так уникальный идентификатор мы поменять не можем. Но это можно сравнить с тем, как если человек в реальной жизни просто так меняет ФИО в паспорте, но это приводит к большим проблемам. Ему нужно
Speaker A
перевыпускать все документы, ему нужно перерегистрироваться во многих социальных сетях и сервисах. Ну вот тут примерно такая же логика, что если просто мы поменяем вот так вот айдишник, у нас всё может сломаться. Делать этого не нужно. Первичный ключ у нас как был
Speaker A
присвоен при создании какой-то записи, так пускай он у неё и остаётся. Но все остальные столбцы, ну, опять же, зависит от бизнес-логики, но в нашем случае все остальные столбцы мы вполне хотим уметь менять. О'кей, давайте я напишу соответствующий SQL запрос. Я пишу
Speaker A
update tasks, то есть я хочу обновить какие-то записи в таблице tasks. После этого я пишу set и поехал перечислять все столбцы, которые у нас есть в этой таблице. Ну вот, давайте я сразу описание таблицы сюда выведу. Вот наши
Speaker A
столбцы. Повторюсь, за исключением первичного ключа. И поехал. title равно доллар1 запятая description равно доллар 2. Ну или же второй магический параметр.
Speaker A
Comped = 3. Created at = дол 4пятая completed at = доллар 5. То есть я сказал, что я у каких-то записей в таблице tasks обновлю title, description completed created, completed, то есть все поля, но пока непонятно у какой
Speaker A
записи. А конкретно ID = дол 6. Вот такой вот SQLзапрос. О чём он говорит? Что у какой-то конкретной записи с каким-то определённым айдишником из таблицы Tasks я обновлю все её поля. А значение для этих полей я возьму из переданной
Speaker A
модели. Выполним этот SQL запрос. Connection, execute. Ну и передаём context SQL Query. И поехали передавать все эти параметры. Значит, на первом месте у нас стоит title. Значит, task.title. Дальше у нас description.
Speaker A
Task. Дальше у нас на третьем месте completed. Task. Дальше task created. Дальше task completed .D. Ну и дальше шестым параметром у нас айдишник идёт. Значит, task ID. Всё. Возвращаем возможную ошибку. И что мы с вами получаем в итоге? Благодаря модели, вместо того,
Speaker A
чтобы для каждого отдельного столбца прописывать отдельную функцию, которая будет этот столбец менять, мы можем прописать одну лишь только функцию обновления какой-то записи в таблице. И в зависимости от того, какую модель на уровне языка программирования мы в эту функцию передадим, такие значения
Speaker A
соответствующая запись в базе данных примет. И нам теперь нет никакой необходимости описывать сразу большое множество различных функций на обновление каждого отдельного столбца, если у нас есть одна универсальная функция, которая может обновить нам сразу любой столбец в зависимости от
Speaker A
того, какую модель мы передадим. Ну давайте посмотрим, как это использовать. Переходим в main. Давайте я удалю всё, что тут было написано. Оно нам может мешать. Ну, create table пока оставлю, пока это трогать не буду. Ну и поехали.
Speaker A
Первым делом я хочу получить все строки, которые есть у нас в базе данных, то есть все записи, то есть все задачи, которые лежат у нас в таблице Tasks. Для этого я обращаюсь к Simple SQL пакету и делаю в нём select rows. Передаю
Speaker A
контекст, передаю подключение и тем самым я получу все задачи, которые у меня есть. Ну и возможную ошибку.
Speaker A
Значит, если у меня ошибка не равна нил, тогда кину панику просто. Иначе я получил список всех задач из моей базы данных. Теперь я, к примеру, хочу обновить задачу, у которой айдишник равен трём. Тут можно код написать совершенно по-разному, но я напишу
Speaker A
наиболее простым способом. Я просто пойду циклом по всем задачам, которые у нас есть, range tasks, и буду смотреть, что если у очередной задачи айдишник равен трём, в таком случае я хочу у этой задачи, ну, давайте подумаем, обновить её заголовок,
Speaker A
описание и хочу ещё обновить поле completed и, соответственно, поле completed add. Ну, давайте это и сделаю.
Speaker A
В таком случае, если айдишник равен трём, тогда я у этой задачи возьму и на уровне языка программирования, то есть то, что я меняю на уровне языка программирования, то, что я меняю в гошке какую-то модель, это никак не влияет на базу данных. На базу данных
Speaker A
это повлияет только тогда, когда я эту изменённую модель сохраню в базу данных либо обновлю какую-то строчку. Ну, поехали. Task.title. Значит, я хочу у задачи с айдишником 3, вот она, поменять title. Ну, пускайт у меня будет покормить кошку. Значит, tйтle равно
Speaker A
покормить кошку. После этого давайте я обновлю описание в этой задаче. Task description равно отсыпать кошке 30 г корма. После этого task completed = true и task completed at = time. Но вот так вот сразу от результата функции мы не можем
Speaker A
взять указатель. Поэтому тоnow я вынесу в отдельную переменную. Time to. И тут уже я буду брать указатель на эту переменную. Всё. Таким образом мы с вами взяли и обновили вот эту вот нашу модель. Но мы сделали изменения лишь на
Speaker A
уровне языка программирования. В данный момент это никакого влияния на базу данных не оказывает. В принципе, мы даже можем сейчас запустить нашу программу и посмотреть, как она вообще работает. То есть мы просто создаём подключение, создаём таблицу, если её ещё нету, и
Speaker A
получаем все строки из базы данных. После этого эти строки у нас преобразуются в модели на уровне языка программирования. Слайс этих моделей у нас возвращается из функции selects. И мы просто здесь меняем поля у этой структурки, у которой адишник равен
Speaker A
трём. Но обратно в базу данных мы не перезаписываем это. Мы просто поменяли здесь на уровне языка программирования структурку, но на базу данных это никакого влияния не оказало. Давайте это сейчас мы с вами и посмотрим. Значит, смотрим сейчас в PGмине. Вот эта задача
Speaker A
с айдишником 3. Выгол собаки. Запускаю программу gounmain. Соксит. Опять получаю данные. Опять выгул собаки.
Speaker A
Ничего не поменялось. То есть, как мы с вами видим, изменения структурки в Гошке, они никак не влияют на данные в базе данных до тех пор, пока мы эту структуру обратно не перезапишем в эту базу данных. Давайте, собственно, перезапишем эту структуру, эту модель
Speaker A
обратно в нашу базу данных. Значит, я делаю simpleql тоupdat task. Передаю туда контекст, подключение к базе данных и передаю вот эту вот нашу новую модель, которую мы с вами обновили. Ну, буду получать ошибочку возможную.
Speaker A
Панику просто кину. Опять же, не буду сейчас как-то сильно заморачиваться над тем, как обрабатывать ошибки. Ну и всё.
Speaker A
То есть, если всё хорошо, тогда я просто завершу выполнение нашего цикла и завершу программу. Теперь давайте посмотрим, что произойдёт. В чём наша программа изменилась относительно того, что было на предыдущем запуске? Теперь мы не только меняем структурку на уровне
Speaker A
Гошки, мы не только меняем модель на уровне языка программирования, мы перезаписываем эту модель обратно в базу данных при помощи SQL-запроса через подключение к базе данных. В этом отличие. И теперь уже изменения в модели перезапишутся в базу данных, и мы
Speaker A
действительно увидим результат этого. Ну, тут только не returрн, конечно же, тут брейк, чтобы мы не завершили функцию main сразу, а просто прервали цикл и увидели вывод subсиit. Ну, давайте, запускаю программу gounmain. Опять suxit. И вот теперь уже смотрим в базу
Speaker A
данных. Опа. И видим, что выл собаки у нас поменялся на покормить кошку, отсыпать кошке 30 г корма. И время выполнения у нас выставилось. При том время создания, как было 18: 5 секунд, как и у других соседних записей, так и
Speaker A
осталось, потому что created мы у модели, которую мы потом перезаписали в базу данных, мы поле created не меняли, мы поменяли только completed, собственно, всё. Вот такое вот преимущество нам даёт модель при работе с базой данных. Теперь это не набор
Speaker A
каких-то мифических мап, пустых интерфейсов и кучи функций для обновления каждого отдельного столбца. Нет, теперь это вполне понятная работа с нашим репозиторием, с нашей базой данных. Эта работа представлена в виде конкретных определённых детерминированных сущностей. Мы можем всегда зайти посмотреть, как эта
Speaker A
сущность выглядит на уровне языка программирования. Мы можем потом зайти в методы для работы с репозиторием и посмотреть, как эта сущность преобразуется в строке в базе данных. И это нам сильно облегчает разработку, делает код более читаемым, более надёжным, более кратким. Мы можем
Speaker A
избегать повторений благодаря тому, что опять же как пример, мы не пишем миллион разных функций на обновление какой-то строки. Мы пишем одну универсальную и после этого ей пользуемся. Возможно, в каких-то случаях вот такая универсальная функция будет не очень удобная. Ну тогда
Speaker A
мы можем дописать какие-то необходимые. Но глобально мы уже чувствуем, насколько появление модели в нашем коде облегчает нам жизнь. Теперь работа с данными в языке программирования происходит так, как будто мы с вами просто работаем с какими-то переменными. Вот получили
Speaker A
слайс каких-то структурок. После этого мы циклом бежим по этому слайсу, получаем каждую отдельную структурку.
Speaker A
Можем в этой структурке поменять какие-то поля. После этого сохраняем структурку обратно в базу данных. То есть мы наглядно видим, с какими данными мы работаем и как эти данные мы преобразуем. Итак, незаметно. Мы с вами научились уже получать подключение к
Speaker A
базе данных. Мы с вами научились создавать таблицы в базе данных. Мы с вами научились вставлять в эту таблицу какие-то данные, получать различные строки из таблицы в базе данных, обновлять данные в таблице и и удалять какие-то произвольные строки. Мы с вами
Speaker A
поговорили о том, что такое модель, для чего она нужна, почему она обычно вводится в язык программирования и почему мы обычно не работаем с сырыми данными, которые нам приходят из базы данных. Потому что из базы данных нам приходит просто вот этот вот объект
Speaker A
ROVS. И как мы с ним уже дальше будем работать, это уже наша задача как программистов решить и выбрать наиболее оптимальный, наиболее удобный и надёжный путь. Ну и как обычно, ребята, практика, практика, практика и ещё раз практика.
Speaker A
Таким образом, мы с вами изучили самые-самые основы языка запросов SQL, но тема баз данных для кэнд-разработчика она гораздо шире, чем просто основы SQL.
Speaker A
Во-первых, сам SQL, он гораздо глубже и гораздо обширнее, чем то, что мы с вами успели рассмотреть. Но помимо SQLэля есть ещё некоторое количество тем, которые необходимо понять и изучить для того, чтобы можно было разрабатывать полноценные приложения. Ну и для того,
Speaker A
чтобы можно было уверенно проходить собеседование. Что это за темы? Значит, первая тема после СQэля - это индексы.
Speaker A
Далее необходимо будет ознакомиться с транзакциями и уровнями изоляции, после этого с блокировками. И после этого ознакомиться будет необходимо с шардированием и репликацией. Вот если все эти темы впитать хотя бы на каком-то среднем уровне, тогда уже можно спокойно и писать настоящее бэкэнд приложения,
Speaker A
которыми будут пользоваться настоящие люди. Ну и, конечно, можно идти проходить собеседование и устраиваться на работу. В рамках этой части мы с вами не будем рассматривать каждую из этих тем. Почему? Потому что, как мы видим, даже самые основы SQL заняли уже
Speaker A
довольно много времени. Если мы сейчас будем разбирать индексы, транзакции уровня изоляции, блокировки, шардирование и репликацию, то мы просто не поместимся не то что в третью часть, мы в 10 частей этого курса не поместимся, потому что каждая эта тема,
Speaker A
она требует отдельного внимания, она требует большого количества времени как на теорию, так и на практику. И просто нет целесообразно тратить наше с вами время в рамках этого курса для того, чтобы по каждой из этих тем пройтись. И что самое главное, в интернете много
Speaker A
качественных материалов по этим темам, поэтому я не буду просто пересказывать открытые источники. На данный момент в этом нету никакого смысла. Именно поэтому все эти темы находятся в блоке самостоятельный разбор. То есть вам будет необходимо самостоятельно изучить и попрактиковаться в этом материале. Вы
Speaker A
можете просто в интернете погуглить каждую из этих тем отдельно и доизучить её. Либо у нас в приватном сообществе я приведу конкретный список материалов, которые я считаю необходимыми пройти, материалов, которые я считаю качественными для изучения вот этих вот тем. И вы сможете просто пройтись по
Speaker A
списку этих материалов и уже довести до конца свои познания в базах данных, довести эти познания до того необходимого уровня, чтобы писать настоящие коммерческие проекты и для того, чтобы проходить собеседование.
Speaker A
Также важно понимать, что Puggre SQL - это не единственная существующая на планете база данных. Pastgre SQL - это лишь одна из самых популярных, так называемых, SQL баз данных. Что такое SQL-база данных? SQL база данных - это реаляционная база данных, для
Speaker A
взаимодействия с которой используется язык запросов SQL. И PGRE SQL - это одна из самых популярных SQL баз данданных, которая используется в backend приложениях, написанных на языке программирования GOL. Но помимо SQL баз данных есть ещё и большой пласт NO SQL
Speaker A
баз данных. Но SQL-базы данных - это такие базы данных, которые не являются реляционными и не используют для взаимодействия язык запросов SQL. Как пример таких, но SQL баз данных. Это Redis, это Mongo DB, это Elastic Search и также их большое множество этих NO SQL
Speaker A
баз данданных. Большинство NO SQL базданных не используют табличный формат хранения данных. Что это значит? Это значит, что данные в этих базах данных не хранятся в таблицах, а хранятся в каких-то других представлениях данных, в каких-то других форматах. Например, в
Speaker A
Редисе наиболее популярный формат сохранения данных - это Kvalue хранилище. То есть у нас как на уровне Гошки есть мапы, это Kvalue хранилища, также есть целая отдельная база данных, которая в основном используется как такое большое и многофункциональное Kvalue хранилище. Mono DB тоже не имеет
Speaker A
табличного формата, а скорее хранит некоторые документы, представленные в GSON виде. У Эластик серча тоже какой-то свой формат хранения данных. Каждая из этих и множество других, SQL баз данданных используется для решения какой-то своей задачи, где реляционные базы данных подходят не лучшим образом.
Speaker A
Я вам советую почитать какие-нибудь статейки про No SQL базы данных, почитать, для чего нужен, например, readyis или же Mono DB, просто для того, чтобы вам было что сказать на собеседовании и иметь какое-то общее представление за рамками обычных реляционных SQL-базданных, потому что на
Speaker A
гошных проектах очень часто используется Redis, очень часто используется Mongo DB, очень часто используется Elastic Search и ещё какие-то другие возможные, но SQL базы. данных. Все их изучать не надо, глубоко копать не надо. Достаточно просто знания, что они существуют, и
Speaker A
понимание, в каком формате тот же или же Mon DB хранят свои данные, понимать, что это не табличный формат и знать, какой именно, и понимать область применения этих NO SQL баз данданных. Ну и опять же в нашем приватном сообществе я дам
Speaker A
некоторые статейки и ссылки для того, чтобы можно было доизучить вот этот вот теоретический материал по NoSQL базам данных. Потому что для того, чтобы пройти собеседование и устроиться на работу, именно каких-то больших, глубоких практических навыков вSQL-базах данных не требуется. Но понимание того,
Speaker A
что это такое, для чего это используется, почему у нас помимо SQL баз данных есть ещё какие-то другие, вот это знание, понимание, конечно, нужно и требуется на собеседованиях. Ну, теперь возвращаемся к нашему коду. И всё, что нам тут осталось сделать - это добавить
Speaker A
произведённые изменения. Напоминаю, мы их производили в ветке Feature SQL. Добавить эти изменения в коit, запушить ветку и коit в удалённый репозиторий, потом создать из этой ветки пулреквест, потом провести самостоятельное кодрев этого пулреквеста и потом залить весь этот код в мастер ветку. Ну давайте это
Speaker A
и сделаем. То есть я захожу в терминал, пишу gitстаatus, вижу свои изменения. То, что я удалил файл feature1.go, то, что я удалил файл feather2.go, Go.
Speaker A
Поменял вот эти файлы и добавил ещё целый пакет Simple Scale. Ну, в принципе, это всё мне необходимо в моём комите. Я не хочу ничего упускать. Ну, единственное, что напишу ещё на всякий случай goй, чтобы у нас на всякий случай
Speaker A
привёлся в валидное состояние go. File. Всё. После этого я могу добавлять все эти файлы в commit. git add. Git commit - m. Ну, просто назову этот коit Simple SQL. Ничего особенного придумывать не буду. Всё. Пишу Git Push. Конечно же,
Speaker A
опять у меня показывает вот эту вот ошибку, что мне нужно написать set upstream, origin-ла, потому что я ещё эту ветку не создавал на удалённом репозитории. Копирую, вставляю, отправляю.
Speaker A
Всё, веточка с этим коммитом отправилась на удалённый репозиторий. Теперь можем открывать GitHub. Перехожу в наш Stud repositorй. И тут я вижу, что мне GitHub уже подсказывает, что я в ветку Feater SQL 22 секунды назад добавил и сразу могу создать пуревест. Здесь ничего в
Speaker A
описании я менять не буду, просто нажму create request. Pullrequest создан. Нажимаем file changes и посмотрим, что же мы вместе с этим полуреквестом привнесём в нашу мастер ветку. Значит, файлы фчер 1, 2, мы видим, что у нас просто удалены. Файл simple.
Speaker A
У нас вместо того, чтобы содержать функцию checknection, теперь содержит функцию create connection, которая создаёт и возвращает нам подключение. Ну да. Значит, модель мы с вами добавили.
Speaker A
Task model. Добавили файл simple create table и в нём функцию для создания таблицы. Ну, в принципе, да. Simple delete файл и в нём функция delete r.
Speaker A
Simple.go. Тут insert r. Мы уже на практике проверяли, что эти функции у нас работают, поэтому от нас сейчас просто требуется проконтролировать, что мы случайно ничего лишнего в этот комит не добавили. Значит, simple. Да, всё правильно. Ну, единственное, что у нас
Speaker A
тут вот есть функция print task, и она сейчас нигде не используется, потому что мы закомментировали её вызов. Вообще, по-хорошему, закомментированный код не должен попадать в полуреквест. Ну вот этот момент хорошо бы поправить. Давайте я сам себе напишу замечание. Нужно
Speaker A
удалить этот комментарий. Всё, это single. Так, смотрим дальше. Значит, simple update. Одна лишь только функция. Хорошо. Go сам можно, в принципе, не вникать. main.g. Потом у нас тут создание подключения, создание таблицы, получение всех задач из нашей таблицы. После этого мы идём циклом по
Speaker A
всем задачам и у задачи с айдишником три меняем её некоторые поля. Ну, в принципе, о'кей, да, меня это полностью устраивает, кроме опять же того единственного момента, где я сам себе оставил комментарий. Это у меня файл simple.
Speaker A
И сорок вторая строчка. Ну, поехали смотреть. Значит, simpleselect. Сорок вторая строчка. Вот он наш комментарий, который нужно удалить. Всё, комментарий я удаляю, саму функцию print task я оставлю, вдруг она в будущем ещё потребуется нам. Ну и всё, теперь я могу
Speaker A
посмотреть Gitдиv в прямо в-коде. Увижу, что тут только лишь удаление комментария. Да, меня это устраивает, поэтому я пишу git status. После этого git edit то git commit - mfix comment gitp.
Speaker A
Всё, запушил свои изменения в уже существующую ветку Feater SQL на удалённой репозитории. И теперь я тут могу посмотреть изменения в самом последнем комите. Вот фикс комент. Ну и тут я вижу, что действительно просто удалил вот этот ненужный комментарий.
Speaker A
О'кей, теперь я могу, в принципе, считать, что этот полурекст у меня валидный, поэтому нажимаю merch p request. Confirm merge. Всё, я залил все эти изменения в мастер ветку. Теперь, если я перейду в стади репозиторий, тут в main ветке я увижу действительно, что
Speaker A
у меня есть пакет feature postgess, в нём есть Simple SQL. И тут есть все вот эти наши файлы, весь наш код, который мы добавили вместе с веткой Feature SQL. Ну и опять же, мы можем сейчас удалить эту ветку, если она нам мешает, но вы уже
Speaker A
сами решаете, удалять вам эти ветки или оставлять. Ну и нам осталось локально переключиться на мейведку, сделать gitpol, чтобы подтянуть новые изменения в мейнвеке. Ну и всё. Теперь мы с вами синхронизировали состояние локальной ветки и мейнтки на удалённом репозитории. Теперь мы можем спокойно
Speaker A
начинать создавать новые ветки для наших новых фичей от этой мастер ветки. Ну а на этом тема базы данных по сг подошла к концу, и мы с вами можем идти дальше.
Speaker A
[музыка] Следующая наша тема - это миграции. Посмотрим, что такое миграции, для чего это нужно и как используется.
Speaker A
Предположим, мы с вами разрабатываем социальную сеть. И изначально у нас было задание, что нам необходимо уметь хранить пользователей. И у каждого пользователя должно быть ФИО и его опциональный номер телефона.
Speaker A
Соответственно, мы с вами взяли бы и описали примерно такую таблицу users. В этой таблице у нас было бы FIO, представленное warchar not. Почему?
Speaker A
Потому что warchar - это у нас строка. Нам было бы удобно хранить в строке и not означает, что должно быть точно задано и оно не может быть нал. Это первый столбец. И второй столбец - это телефон. Ну, телефон просто warрчаr и
Speaker A
всё. То есть просто мы бы хранили телефон в строке и не добавляли бы not, потому что телефон как раз-таки может быть на вшем вот первом приближении. То есть вполне о'кей, если юзер какой-нибудь не задал номер телефона. Ну о'кей, мы бы получили такую таблицу,
Speaker A
запустили бы это всё уже в продакш сервер. Нашей социальной сетью бы начали пользоваться люди, они бы начали регистрировать себе всё новые и новые аккаунты. То есть каждая вот эта красная строчка - это какой-то новый аккаунт. И вроде бы всё классно, но спустя какое-то
Speaker A
время мы начинаем понимать, что если пользователь не привязывает себе номер телефона, то, скорее всего, это бот.
Speaker A
Просто бот, который используется какой-то ботофермой. И нашей социальной сетью начинают пользоваться не для того, чтобы один человек с другим мог общаться, а для всяких мошеннических схем, накруток и всего прочего. Наш менеджер или же там наш Teamлид из этого
Speaker A
делает вывод, в принципе, довольно логичный, что телефон у нас вообще-то должен быть тоже NTN now, как и FO.
Speaker A
Почему? Да, потому что если пользователь будет обязан задать номер телефона при регистрации, тогда это уменьшит вероятность того, что новый зарегистрированный аккаунт - это какой-то бот или какой-то мошеннический аккаунт. Потому что у нас теперь обязательно для каждого аккаунта должен
Speaker A
быть номер телефона. У нас номер телефона not в таблице users. Ну хорошо, это всё здорово, конечно. Но в чём проблема? Проблема в том, что нам нужно как-то мигрировать между двумя схемами данных. Вот наша первая схема данных изначальная. Вот наша вторая схема
Speaker A
данных, где номер телефона уже notл. И нам каким-то образом нужно взять и между двумя этими схемами данных мигрировать.
Speaker A
Есть первый способ, который мы с вами уже рассматривали. Это просто удалить таблицу users, которая уже есть, и после этого создать новую таблицу users с уже новым описанием. Почему это плохо?
Speaker A
Потому что при удалении таблицы у нас удаляются все записи в этой таблице. Соответственно, все зарегистрированные пользователи у нас потеряют свои аккаунты. А это, как мы понимаем, ну, очень плохо и точно не может быть выходом. Ну какая это социальная сеть,
Speaker A
если в рандомный момент аккаунты всех пользователей удаляются? Просто потому, что разработчикам что-то там в базе данных нужно подправить. Ну, это бред.
Speaker A
Получается, этот способ нам не подходит. Этот способ может использоваться в целях тестирования, но если нам необходимо поменять таблицу в продакш сервере, тогда точно мы так делать не можем.
Speaker A
О'кей, у нас есть второй способ - это при помощи специальных SQL-команд мы с вами можем заставить в уже рамках существующей таблицы какой-то столбец, принять, что он теперь notл. Да, такое мы с вами можем сделать. И сейчас мы посмотрим, как это делается. Это
Speaker A
довольно хороший способ. И действительно, мы можем поменять свойства какого-то столбца при помощи SQL-запроса. И нам не придётся при этом удалять таблицу и пересоздавать её заново. Хорошо, допустим, мы с вами действительно умеем это делать. Мы с вами так сделали, и мы мигрировали с
Speaker A
старой схемы данных, где у нас телефон был необязательный, на новую схему данных, где у нас теперь телефон Not now. Nal. Хорошо. Вот мы с вами пожили так какое-то время с телефоном Not.
Speaker A
Вроде бы всё хорошо, но потом появилась новая проблема. Появилась такая проблема, что на один и тот же номер телефона у нас регистрируется множество аккаунтов, то есть множество записей в базе данных, номер телефона имеют один и тот же. Что это значит? Это значит, что
Speaker A
какой-то человек просто на свой номер телефона или же на чужой нарегистрировал огромное количество опять этих ботов.
Speaker A
Опять пошла у нас накрутка, опять пошло мошенничество, опять мы столкнулись с новой уже проблемой. То, что у нас хоть номер телефона при регистрации и обязателен, у нас нету на нём требования уникальности. А значит, что все желающие могут на любой абсолютно номер телефона,
Speaker A
даже на тот, который уже зарегистрирован в нашей системе, зарегистрировать себе новый аккаунт. И у нас в итоге тысячи аккаунтов с одинаковыми номерами телефона. Ну и тут приходит нашли и говорит, что теперь нам нужно снова мигрировать схему наших данных, а
Speaker A
конкретно на столбец телефон мы должны теперь добавить новый констraint уникальности, вот этот вот constстraint unqю. Соответственно, нам снова нужно каким-то образом с уже существующей схемы данных мигрировать на новую схему данных. То есть у нас с вами уже вырисовываются несколько версий нашей
Speaker A
схемы данных. Давайте я эти самые версии пронумерую. Вот это версия о, вот это версия 2, вот это версия 3.
Speaker A
О'кей. Допустим, мы опять же с вами воспользовались возможностями СQэля и задали столбцу телефон новое свойство, а конкретно unq constraint. Ну и всё.
Speaker A
Предположим, проблема мошенничества и накрутки у нас решена. Всё хорошо. Но теперь к нам приходит начальство и говорит, что мы хотим для каждого пользователя теперь уметь сохранять не только Fio и телефон, но так как у нас социальная сеть, то мы для каждого
Speaker A
пользователя ещё хотим уметь хранить графу о себе. То есть человек может буквально прийти и поставить себе в статус какую-нибудь строчку. Ну как это, например, во ВКонтакте можно сделать или в Телеграме. Просто био или же графа о себе. О'кей. Что это для нас значит? Это
Speaker A
для нас значит, что мы в очередной раз должны мигрировать схему наших данных, должны мигрировать схему таблицы users с версии 3 на вот эту вот версиючетыре, а конкретно тут у нас добавляется столбец о себе. Таким образом, мы можем видеть, что схема данных - это не что-то
Speaker A
статичное, это не что-то зафиксированное один-единственный раз и больше никогда не меняющееся. Нет, схема данных точно также при необходимости у нас может меняться со временем. А мы с вами уже изучили гит, то есть мы с вами уже понимаем, какие проблемы у нас могут
Speaker A
возникнуть, когда мы переходим от одной версии какого-то программного решения к другой версии этого самого программного решения. И просто так прийти в продакш сервер, взять столбец телефон и просто накинуть на него свойства not. Это может быть небезопасно. То есть у нас
Speaker A
множество вещей могут из-за этого сломаться, и мы даже можем не подозревать об этих самых вещах. А что в таком случае мы бы с вами хотели уметь делать? То есть мы с версией один нашей таблицы мигрируем на версию два нашей
Speaker A
таблицы. И вот в этой версии 2 у нас что-то берёт и ломается, короче, и у нас летят ошибки, продовый сервер начинает падать, приложение не работает. Что мы первым делом захотим уметь в таких ситуациях? Первым делом мы захотим уметь
Speaker A
откатывать нашу схему данных на предыдущую стабильную. И теперь мы можем видеть, что у нас в схеме данных тоже начинает появляться версионирование со всеми вытекающими. Мы хотим конкретно понимать, на какой версии нашей схемы данных мы находимся в текущий момент. И
Speaker A
мы хотим уметь между этими схемами данных переключаться с возможностью откатываться, если что-то вдруг пойдёт не так. И как раз-таки миграции - это тот самый инструмент, который нам помогает версионировать схему наших данных. У нас буквально в рамках проекта будет отдельная папочка, куда мы с вами
Speaker A
сможем зайти и конкретно увидеть различные версии нашей схемы данных. Мы сможем увидеть, каким образом схема данных у нас эволюционировала со временем. И при необходимости мы с вами сможем нашу схему данных откатывать на предыдущую или же на произвольную версию. Таким образом, мы просто получим
Speaker A
больше контроля и больше наглядности. В нашей схеме данных есть большое множество популярных инструментов для того, чтобы реализовывать миграции в базах данных. Но в гошных проектах довольно часто используется следующая библиотека Goleng Migrate. Вот она. Наша задача сейчас её установить и начать ей
Speaker A
пользоваться. В этот раз и во все последующие я уже не буду показывать, каким именно образом на ваше устройство устанавливается тот или иной инструмент или же та или иная библиотека. Потому что я искренне считаю, что если вы дошли уже до текущей минуты в рамках всего
Speaker A
нашего огромного курса по гошке, то вы уже вполне в состоянии сами разобраться, как установить тот или иной инструмент на вашу рабочую машину. Почему я так говорю? Потому что в будущем и на работе, и во время обучения будет миллион всяких вещей, которые необходимо
Speaker A
будет установить на свой компьютер. А мы этих вещей, может быть, даже не будем касаться в рамках нашего курса, потому что это какой-то суперу узкий, суперспецифичный инструмент, который используется там, условно говоря, в паре компаний. И ваша задача будет, как
Speaker A
разработчика узнать, найти соответствующие гайды и скачать, установить библиотеку или же какой-то инструмент, который вам нужно установить для продолжения работы. Именно поэтому библиотеку Goldeng Migrate и в дальнейшем какие-то инструменты, которые мы будем использовать в рамках этого курса, я уже не буду показывать на всех
Speaker A
трёх системах Windows, MacOS, Linux, как устанавливать. Отныне и впредь - это ваша задача научиться разбираться в том, как устанавливать какие-то библиотеки или же какие-то инструменты.
Speaker A
Соответственно, задача номер один для вас - это сейчас установить на ваши локальные машины вот эту вот библиотеку Goldeng Migrate. Можете открыть чат GPT, можете просто загуглить, например, Windows install Goleng Migrate или же MacOS Install Goleng Migrate. И в
Speaker A
принципе там миллионы гайдов в интернете, как устанавливается эта библиотека. Соответственно, в рамках этого курса мы уже на это время тратить не будем. Ну, переходим обратно в наш проект. И я хочу сразу же создать новую ветку для того, чтобы уже в рамках этой
Speaker A
ветки рассматривать миграции. Пишустатус на всякий случай. Делаю git pol, чтобы удостовериться, что моя локальная меain ветка, она находится в актуальном состоянии. Да, всё отлично. И теперь могу написать git branch feature migrations.
Speaker A
Git checkout feature migrations. Всё, я создал и переключился на ветку Feature Migrations. Теперь тут я могу начать рассматривать собственно сами миграции. Я уже буду предполагать, что вы успешно скачали и установили библиотеку Goeng Migrate. Как это проверить? Мы можем написать migrate
Speaker A
тире тире версия. Вот, соответственно, версия библиотеки Goeng Migrate, которая была установлена. У меня эта версия 4182. Всё, теперь мы с вами можем создать нашу первую миграцию. Как это выглядит? Мы пишем migrate. После этого ключевое слово create, то есть создаём
Speaker A
файлы миграции. После этого ext SQL. Что это значит? - это extension, то есть расширение. Конкретно нас сейчас интересует расширение SQL, потому что мы просто с другими базами данных пока ещё даже не знакомы. Мы с вами знакомы с Постгрессом. Постгресс - это реляционная
Speaker A
база данных, а для управления реляционной базой данных у нас используется SQL. Поэтому миграции мы будем описывать на языке SQL. Дальше DIR. И нам нужно передать, в какой папке будут храниться миграции. Ну, обычно стандартно принята папка migrations.
Speaker A
После этого тире сек, то есть sequence. Ну или же если вот на русский переводить, не совсем прямо, но именно по логике, то это просто название версии создаваемой миграции. У нас сейчас самое-самое начало, условно говоря, самая первая миграция, самая первая
Speaker A
версия нашей схемы данных, поэтому я это назову ИниIT. Всё, нажимаем Enter. И как мы видим, у нас создалось два файла в нашей директории stud в поддиректории Migrations. Вот она создалась. Создалось два пустых файла. Можем даже в Gitdди посмотреть, что действительно у нас
Speaker A
создалось два вот этих файла. Они пустые. Почему? Потому что саму схему данных мы должны описывать руками. На текущий момент библиотека Goeng Migrate всё, что нам сделала - это создала директорию Migrations. И тут два файла: Init up и init down. Почему у нас
Speaker A
создалось два файла? Файл app нужен для того, чтобы в нём описать SQL-команды, которые нужны для того, чтобы прийти в состояние версии 1 инит. Файл DV нужен для того, чтобы описать SQL-команды, которые нужны, чтобы выйти из этого состояния на предыдущую версию. Так как
Speaker A
у нас сейчас самая первая версия миграции описывается, версия миграции, которая инициализирует схему данных, то у нас в файле up будет описываться создание схемы данных с полного нуля, а в файле да будет описываться полное уничтожение всей схемы данных и
Speaker A
приведение базы данных в исходное пустое состояние. Ну то есть у нас вот здесь вот есть какое-то нулевое состояние базы данных, она вот пустая. И файл вот этот вот init up нужен для того, чтобы описать SQL-команды, которые приведут состояние базы данных, состояние схемы
Speaker A
данных вот к этой первой версии. А файл DAV нужен для того, чтобы описать SQL-команды, которые в случае чего обратно из этой первой версии приведут состояние схемы данных в нулевую версию, когда ещё ничего не было описано. Ну давайте мы сейчас в миграциях и
Speaker A
реализуем вот эту нашу схему. У нас будет таблица users, и у нас будут следующие версии. этой самой таблицы. То есть у нас по сути должно быть 1 2 3 4, то есть четыре файла миграции. У нас будет четыре файла up и четыре файла
Speaker A
down. То есть, ещё раз, когда мы вот из этой нулевой пустой версии базы данных, из нулевой пустой версии схемы данных будем переходить в первую версию, то есть первично инициализировать нашу схему данных, то это вот этот вот файл один и up, то есть в нём будут описаны
Speaker A
SQL-команды, которые приведут схему данных вот в это наше первичное состояние. Соответственно, один и даван нужен для того, чтобы описать SQL-команды, которые обратно из первой версии схемы данных переведут эту самую схему в пустое состояние. Ну, по сути, удалят все данные, вообще все созданные
Speaker A
таблицы и так далее. В дальнейшем, когда мы с вами будем создавать новые файлы миграций, файл up будет отвечать за то, чтобы перейти на следующую версию миграции, а файл down будет отвечать за то, чтобы перейти на предыдущую версию миграции. Ну и так дальше. Значит, файл
Speaker A
up, переходим на следующую версию. Файл down. Опускаемся на версию назад. Ну, поехали. У нас в первой версии нашей схемы данных будет всего лишь одна таблица users. У неё будут два столбца: Fio и телефон. Ну, давайте сейчас это и
Speaker A
сделаем. Соответственно, я в наших файлах миграции беру первую версию, вот эту вот init up, и в ней начинаю описывать SQLзапрос, который нужен для того, чтобы инициализировать первую версию нашей схемы данных. Здесь мы просто создаём таблицу. Вот я пишу
Speaker A
create table users. И тут я опишу, собственно, сами столбцы в этой таблице. Первое - это у нас FO, ну или же Full Name warchar, 200 символов ему задам.
Speaker A
Not null. О'кей. Второе - это у меня phone number, тоже Warch. Пускай я задам ему 100 символов, уж какой-нибудь суперогромный номер телефона, чтобы поместился. Собственно, всё. Это у меня миграция, которая приведёт состояние моей базы данных из вот этого
Speaker A
изначального пустого на первую версию. По сути, просто создастся таблица users и в ней вот эти вот два столбца. Phone number пока ещё у нас может быть нал.
Speaker A
Пока ещё он у нас не уникальный, просто потому что мы с вами ещё не дошли до следующих версий. Пока мы вот рассматриваем именно вот этот вот переход из нулевой версии, так сказать, в первую версию. Хорошо. Вот эта вот
Speaker A
стрелочка, которая у нас переводит нулевую версию в первую версию - это вот этот вот наш файл init 1 up. Теперь нужно разобраться с файлом Инит 1 down.
Speaker A
Что он должен делать? В нём должен быть описан такой SQL запрос или несколько SQL-запросов, которые схему данных из первой версии превратят обратно в нулевую, то есть откатят эту схему данных. Чтобы понять, что нам нужно откатить, всегда нужно смотреть в то,
Speaker A
что мы привнесли в этой версии. В этой версии мы с вами привносим создание таблицы users. Соответственно, чтобы нам откатиться от этой версии, нам нужно всего лишь удалить таблицу users. О'кей.
Speaker A
Значит, я перехожу в файл in 1 down. И здесь я пишу следующие команды: drop table users точка запятой. Всё, я описал миграции для первой версии нашей схемы данных. Давайте я здесь вот подпишу, что это версия ноль. Ну и ещё раз.
Speaker A
Т 1up у нас переводит состояние схемы данных из нулевой версии в первую версию. Ита у нас наоборот из первой версии переводит состояние схемы данных в нулевую версию. Таким образом, мы с вами описали самую первую нашу миграцию.
Speaker A
Ну а теперь давайте посмотрим, как это всё работает. Справа я открыл PG adдмин, который подключён к моему PSGSQL-серверу. И здесь, как мы видим, у нас осталась вот эта вот таблица tasks из предыдущих тем. Вообще мы можем её оставить, то есть она нам никак не
Speaker A
мешает, но для чистоты эксперимента я её удалю. Сейчас нажимаю правой кнопкой мыши. Delete. Delete. Всё, я удалил из моей паб схемы, из погress логического подраздела из studgress QL сервера таблицу Tasks. Всё, теперь у меня нету никаких таблиц. Могу нажать refфреш. Вот
Speaker A
тут нету никаких таблиц. Хорошо, получается, мы сейчас находимся как раз-таки в том самом нулевом состоянии базы данных. У нас сейчас в ней нету ничего, она пустая. Наша задача сейчас при помощи описанной миграции перейти от нулевой версии схемы данных к первой
Speaker A
версии схемы данных. Ну, соответствующая миграция у нас уже описана. Остаётся лишь только вызвать эту самую миграцию.
Speaker A
Как это делается? Команда на самом-то деле довольно длинная. Нам нужно написать migrate, после этого тире pass.
Speaker A
И тут мы пишем путь до папки с миграциями. У нас это вот просто migrations сразу же идёт, потому что мы находимся в директории stud, а здесь папка migrations у нас располагается в прямом доступе. Значит, migrate pass, migrations. После этого нужно написать
Speaker A
тире database и передать строку подключения к базе данных. У нас с вами уже есть эта строка подключения. Она находится в файле Simple Connection. Вот она, наша строка. Копируем её вместе с двойными кавычками и вставляем сюда после ключевого слова database вставляю.
Speaker A
Но в чём тут ещё есть нюанс? PGX библиотека, она под капотом умеет регулировать зашифрованное подключение у нас будет установлено или же незашифрованное подключение. В нашем случае у нас будет всегда установлено незашифрованное подключение, потому что трафик по Локоalсту не шифруется. Ну,
Speaker A
обычно, если для этого никаких специальных действий не предпринять. Что это значит? Это значит, что библиотека PGX из вот этой вот описанной строки подключения сама сможет понять, что ей не нужно использовать зашифрованное подключение. А вот библиотека Migrate не сможет этого сделать. И если мы будем
Speaker A
использовать вот такую строку подключения, то будет ошибка. Ошибка, вызванная тем, что наш Pastgre SQL-сервер не ожидает никакого зашифрованного подключения, а мы вдруг тут его начинаем шифровать.
Speaker A
Соответственно, будет конфликт, будет ошибка. Как этого избежать? Мы здесь в конце строки подключения должны написать вопрос. После этого слова SSL mod равно disable. Таким образом, мы выключим попытку установить зашифрованное подключение. Тем самым мы сможем установить подключение к погсерверу и
Speaker A
произвести наши миграции. Хорошо. На данный момент мы уже вызываем библиотеку Migrate, передаём путь к директории, где у нас описаны файлы миграций. После этого передаём строку подключения к базе данных. После этого нам нужно передать команду, которая будет воспроизведена библиотеками. Какие вообще есть команды?
Speaker A
Есть команда App. Что делает команда App? Команда app, она просто открывает переданную директорию с миграциями и устанавливает самуюсамую самую последнюю, самую свежую версию схемы данных. Есть команда Down. Она откатывает схему данных на нулевую версию. То есть какая бы у нас ни была в
Speaker A
настоящий момент версия схемы данных, если мы просто напишем команду DVН, то откатится схема данных прямо в самую нулину, в самую вот нулевую пустую версию. То есть получается, когда мы с вами пишем команду app. то вот какая последняя версия миграций у нас будет
Speaker A
описана в директории, на такую версию схемы данных мы и переключимся. Если у нас описана только лишь первая версия, то мы переключимся на первую. Если у нас описана, к примеру, четвёртая версия, то мы сразу прыгнем на четвёртую версию за
Speaker A
одну команду. Ну и, соответственно, то же самое с командой Dн. Если мы напишем просто Dн, то независимо от того, в какой версии мы находимся, мы в любом случае прыгнем сразу же на нулевую версию. Но иногда это оказывается не
Speaker A
сильно удобно. Иногда мы хотим контролировать конкретно на сколько версий мы прыгнем вперёд или назад. Для этого мы можем писать не просто down или же up, мы можем написать, к примеру, up 1. Что это значит? Это значит, что независимо от того, сколько у нас там
Speaker A
впереди ещё есть версий схемы данных, мы переключимся вперёд всего лишь на одну версию. Ну либо можем написать up 2.
Speaker A
Тогда мы переключимся на две версии вперёд. Ну, собственно, тут уже детали реализации этой самой библиотеки Goeng Migrate. Тут уже можно почитать документацию, чтобы конкретно, в вашем случае понять, какую функциональность библиотеки за использовать. Хорошо, возвращаемся сюда. Я напишу просто up.
Speaker A
То есть независимо от того, сколько у меня тут сейчас описано версия миграции, я в любом случае хочу перейти на самую последнюю, самую актуальную. Давайте ещё раз посмотрим. Вот здесь вот нажму рефреш. Посмотрим, что действительно нету никаких таблиц у меня сейчас. И
Speaker A
нажимаю Enter. вот на этой вот команде, то есть ввожу эту команду. Оп, я вижу, у меня показывается, что выполнение миграции заняло 5 мкунд. И теперь давайте я перезагружу наши таблицы и увижу, что у меня появилось сразу две таблицы. Во-первых, это таблица users.
Speaker A
Вот она. Давайте посмотрим на неё. Тут сейчас, конечно же, нет никаких записей, но вот наша таблица users, которую мы с вами описали в миграции. То есть мы благодаря миграции накатили вот эту самую таблицу. Мы благодаря миграции перевели схему данных с нулевой версии
Speaker A
пустой на первую версию. Мы это сделали благодаря миграциям. И как мы видим, тут появилась ещё какая-то таблица, схема migrations. Что это за таблица? В этой таблице, как мы видим, у нас есть всего лишь два столбца. Это версия и дёрти.
Speaker A
Что значит дёрти - это значит грязная версия. Такое бывает, когда мы с вами пытаемся применить миграцию, но миграция применяется не до конца. В таком случае мы можем получить грязную версию, и уже нам придётся из этого каким-то образом выкручиваться. Но в данный момент можем
Speaker A
даже не обращать внимания на эту таблицу. Можем воспринимать её просто как какую-то служебную таблицу для того, чтобы библиотека Migrate могла функционировать. Самое главное сейчас, что наша таблица users создалась при помощи миграций. Создалась именно так, как мы её описали, и мы можем её
Speaker A
использовать. Теперь давайте откатим нашу миграцию на версию назад. То есть мы сейчас переключились на самую последнюю актуальную версию, а сейчас давайте наоборот обратно переключимся на самую нашу нулевую, самую исходную версию миграций. Для этого я пишу всё то же самое, только в конце у меня не up, а
Speaker A
down. Нажимаю Enter, и мне тут пишется предупреждение. Вы уверены, что вы хотите сбросить абсолютно все миграции?
Speaker A
Ну, то есть мигрировать на нулевую версию. Я пишу, да, нажимаю Enter, всё, отмена всех миграций заняла 13 мсекунд.
Speaker A
И теперь давайте я вот нажму refреш на tables. Refresh у меня пропала таблица users. Осталась только вот эта вот служебная таблица. Всё, я откатил версию схемы данных с первой на нулевую. Ну и благодаря миграциям я могу туда-сюда переключаться при желании.
Speaker A
Соответственно, если я обратно напишу up, то я обратно переключился на первую версию и у меня обратно появилась моя таблица users. Вот она. О'кей, мы с вами успешно описали миграции для того, чтобы из пустой схемы данных переключаться на первую версию. Давайте далее пойдём и
Speaker A
опишем файлы миграции для того, чтобы с первой версии переключаться на вторую. Ну и, соответственно, обратно. То есть это у нас будет файл up, это у нас будет файл down. Ну, поехали. Для этого начинаем писать migrate create, то есть
Speaker A
опять создаём какую-то новую версию наших миграций, новые файлы миграции. Передаём Extension SQL. передаём директорию с нашими миграциями. И теперь нужно указать название нашей новой версии миграции. То есть изначально у нас было название Инит, потому что это самая инициализация нашей вообще схемы
Speaker A
данных. А теперь это уже не инит, это уже добавление not на столбец с номером телефона. Ну напомню, почему мы это хотим сделать. Для того, чтобы заставить каждого зарегистрированного пользователя в нашей социальной сети указывать номер телефона. То есть пользователь не сможет
Speaker A
зарегистрироваться, если у него нету номера телефона. Но эту версию можно как-то сокращённо назвать notфо.
Speaker A
Ну, что-то вроде этого. В принципе, всё. Нажимаем Enter. Мы создали вторую версию миграций. Вот эти файлы у нас появились в директории Migrations. И как мы видим, у нас первая версия так и осталась нетронутая. То есть мы её уже описали
Speaker A
когда-то, она вот есть, остаётся нетронутая. Дальше мы описываем вторую версию миграции. Она у нас уже начинается с циферки два. Дальше название, которое мы передали, и точно также два файла up и down. В этом случае файл up, вот этот вот 2 not up, будет
Speaker A
отвечать за то, чтобы из первой версии схемы данных переключиться на вторую версию. А файл 2 notal phone down будет отвечать за то, чтобы со второй версии обратно переключиться на первую. Давайте сейчас опишем эти файлы. Открываю файл 2 notal phone app. И здесь я напишу
Speaker A
SQLзапрос, который мне позволит перейти от первой версии нашей схемы данных ко второй версии. Эти две версии отличаются тем, что у нас столбец номер телефона not. Получается, в файле мы должны задавать столбец номер телефона как not.
Speaker A
А в файле down мы наоборот должны это свойство убирать. Таким образом, мы поддержим переключение между двумя версиями схемы данных, где в одной версии нету not, а во второй версии есть not. Соответственно, здесь мы включаем not, здесь мы выключаем not. Ну, давайте
Speaker A
сейчас включим этот not. Значит, открываю файл 2 notn up. И здесь пишу следующую SQL-команду. Alter Table. Это такая команда, которая говорит, что я сейчас буду менять какую-то уже существующую таблицу в базе данных.
Speaker A
После этого передаю название таблицы users. Дальше я пишу alter column. Это говорит о том, что я буду менять какой-то существующий столбец в рамках уже существующей таблицы. Я буду менять столбец phont number. Скопирую это название, вставляю сюда. После этого я
Speaker A
должен написать, что конкретно в этом столбце я буду менять. В этом столбце я буду выставлять свойство not now. То есть теперь я буду требовать, чтобы этот столбец был not. Соответственно, пишу set null. Всё. Таким образом вот эта вот
Speaker A
SQL-команда, она возьмёт уже существующую таблицу users, в ней найдёт существующий столбец phone number и сделает его not now. Теперь осталось описать обратную миграцию, то есть миграцию, которая отменит вот эти вот изменения. Это файл с припиской DН.
Speaker A
Опять пишу alter table users, alter column font number, конечно же. drop not n. Таким образом, я отменю вот это вот свойство not. Я сброшу свойство not со столбца number таблиц users. Таким образом, мы с вами, описав два вот этих
Speaker A
файла миграции, поддержали возможность переключения между первой и второй версией схемы данных, где меняется свойство столбца phone number, а конкретно свойство not now. Но теперь остаётся это всё проверить. Давайте я сейчас нажму refresh, нажму правой кнопкой мыши на таблицу users и
Speaker A
properties и увижу свойства этой самой таблицы. Какие в ней есть свойства? Нажимаю columns. И здесь, как мы видим, not null. У столбца phone number нету.
Speaker A
То есть он, по сути, может быть нал. О'кей. Давайте теперь запустим наши миграции и промигрируемся на самую последнюю актуальную версию. Самая последняя актуальная версия у нас сейчас версия 2. Где not phone number. Окей.
Speaker A
опять пишу эту сложную команду. Ну, по сути, я могу её найти в истории команд.
Speaker A
Для этого просто стрелочка вверх нажимаю 1раз, два. Вот она, наша команда. Migrate. Передаём путь к файлам миграций. Строка подключения app.
Speaker A
Напоминаю, app - это значит переключиться на самую последнюю актуальную версию, которая сейчас есть. Нажимаю Enter.
Speaker A
Всё, я переключился на вторую версию nalone. Теперь давайте я нажму опять refresh, нажму правой кнопкой мыши на таблицу users properties columns. И как я вижу, у меня теперь столбец phone number помечен как not. Всё, я успешно мигрировался с первой версии схемы
Speaker A
данных на вторую версию схемы данных. Ну давайте теперь проверим, как работает откат. Но откат я буду проверять не сразу на нулевую версию, потому что так неинтересно. Я хочу проверить откат со второй версии на первую. То есть я хочу
Speaker A
откатиться лишь на одну версию назад. Для этого я открываю опять наши команды. И тут в конце я напишу down 1, то есть переключиться на предыдущую версию, но не сразу в нулину всё сбросить, а лишь на одну версию назад откатиться. Ну
Speaker A
давайте сейчас я выполню эту миграцию, нажимаю Enter. Теперь опять делаю рефреш на наших таблицах, чтобы подгрузить свежую информацию. Нажимаю properties columns. И как я вижу, у меня теперь опять столбец phone number перестал быть not. То есть опять он может быть на. А
Speaker A
это значит, что я переключился на схему данных версии один. А теперь я могу обратно на одну версию вперёд перепрыгнуть. В конце этой самой длинной команды я пишу up 1 оп. И я обратно переключился на версию данных, которая у
Speaker A
нас самая последняя. Note nal fone. Делаю refреш, открываю таблицу users. И вот, пожалуйста, phone number у нас опять not now. А если я напишу в конце просто дан, не дан оди, а просто дан, это значит, что я переключусь на
Speaker A
самую-самую нулевую, самую исходную версию наших данных. Enter. Меня опять спрашивают, уверен ли я, что я хочу все миграции отменить. Я пишу да. Нажимаю Enter. И, пожалуйста, я отменил все миграции. Нажимаю рефреш. И у меня, в принципе, таблица users пропала
Speaker A
полностью. Но так как у меня всё версионирование схемы данных находится в миграциях, я могу написать up, и я возьму и сразу все миграции накачу до самой-самой последней свежей версии. А в самой свежей версии у нас столбец phone number not null. Таким образом, мы уже
Speaker A
можем видеть, как именно работают миграции. То есть, допустим, мы находимся вот в этой вот самой-самой нулевой версии, но файлы миграции у нас описаны для всех четырёх версий. Если я из этой нулевой версии просто напишу up без дополнительных чисел, просто up,
Speaker A
тогда у меня установится последняя версия миграции, последняя версия нашей схемы данных. Но как именно она установится? Она установится не сразу последняя, она пройдёт все предыдущие версии автоматически. Сначала накатится первый файл up, потом второй файл up, потом третий файл up и потом четвёртый
Speaker A
файл up. И таким образом мы дойдём до последней версии схемы данных. Если мы из этой четвёртой версии просто напишем down, мы точно также попадём в нулевую версию. Но не сразу, а мы пройдём все предыдущие версии. Сначала у нас
Speaker A
четвёртый файл down применится, потом третий файл dн, потом второй и потом первый. Таким образом работают миграции.
Speaker A
То есть для того, чтобы нам переключиться сразу на какую-то версию, мы хоть и не явно под капотом пройдём все предыдущие версии, потому что вот эта библиотека Migrate, она просто идёт по директории Migrations и смотрит, какие файлы UP, какие файлы DV есть. И в
Speaker A
зависимости от того, какую версию схемы данных мы хотим получить, такие файлы миграции у нас будут вызваны. О'кей, мы сейчас с вами описали миграции для переключения между нулевой и первой и первой и второй версии нашей схемы данных. Давайте теперь опишем файлы
Speaker A
миграции для переключения между второй и третьей версией схемы данных. Тут у нас, как мы видим, на столбец телефон или же Phone number добавляется constraint уникальности. Перехожу в VS-code, открываю терминал, опять хочу создать новые файлы миграции. Для этого я
Speaker A
использую команду, create SQL, de migrations, se передаю конкретно краткое описание миграции. Ну, тут у нас un phone number получается будет. Ну или же просто unqю font. Enter. Создались ещё одни файлы миграции для работы с третьей версией. Значит, сначала опишем up, то
Speaker A
есть переключение со второй версии схемы данных на третью версию. Выглядит это следующим образом. Я пишу alter table users, то есть опять я меняю таблицу users, но тут я уже делаю add constraint, то есть я добавляю constraint. Этот constraint желательно сразу назвать,
Speaker A
потому что по этому названию мы его потом будем удалять. Я назову это users phone number unq. [откашливается] И конкретный constraint мы пишем unq phone number. Всё. Эта SQL-команда, она у нас возьмёт таблицу users и в ней накинет свойство уникальности на столбец
Speaker A
Phone Number. И это свойство уникальности, этот UNQю Constraint назовёт users phone number unquю. А обратная миграция, то есть миграция, которая наоборот с третьей версии на вторую переключится, описывается в файле down и выглядит следующим образом. Мы пишем alter table
Speaker A
users и тут drop. constraint. И после этого передаём название констрейнта, которое мы задали. Вот это вот самое название. Всё. Благодаря этому мы с вами описали файлы миграции, которые нам помогут переключаться со второй версии схемы данных на третью версию и обратно.
Speaker A
Ну давайте проверим, накатим эти наши миграции. Допустим, я забыл, на какой версии миграции я сейчас нахожусь. На первой, на второй или же на третьей? Для этого я могу написать команду, которая похожа на то, что мы уже писали.
Speaker A
migrate, pass migrations, то есть передаём путь до файлов с миграциями, передаём строку подключения к базе данных, но в конце пишем не up, не down, а пишем версия. И нам выдастся версия нашей схемы данных. У нас сейчас версия схемы данных два. Это значит, что вот
Speaker A
это вот not. Соответственно, мы сейчас находимся на вот этой вот второй версии. Давайте переключимся на третью. Тут, в принципе, можно написать либо up, либо up1. это будет равносильно, потому что app у нас переключает на самую последнюю актуальную версию, это третья, а app1
Speaker A
просто переключается на версию схемы данных, которая на один больше, но это тоже третья версия. Ну давайте я для разнообразия воспользуюсь up1.
Speaker A
Соответственно, вот это наша длинная команда. И в конце я пишу up1, нажимаю Enter. Всё, у меня установилась третья версия нашей схемы данных Unqone.
Speaker A
Давайте опять нажму refresh, посмотрю свойства таблицы users constraints. И здесь меня интересует Uniq Constraint.
Speaker A
Вот он наш Unq Constraint добавился на столбец Phone Number. Называется он именно так, как мы описали в файле миграции. То есть вот он наш файл миграции, который добавляет уникальность на столбец Phone number. И вот название этого констрейнта. О'кей, мы сейчас
Speaker A
переключились со второй версии на третью. Ну давайте откатимся на одну версию назад. Для этого пишу всё то же самое, только в конце да. Нажимаю Enter.
Speaker A
Всё, я переключился на одну версию назад. Опять нажму refresh смотрю свойства таблицы users. Constraints un you. Ну и как я вижу, здесь нет никаких констрейнтов, потому что мы убрали constraint уникальности благодаря тому, что откатились с третьей версии схемы
Speaker A
данных на вторую версию. И таким образом мы можем дальше, дальше и дальше мигрировать схему наших данных, добавлять какие-то новые фичи в наше приложение, и наш проект будет развиваться. И каждая новая схема данных, каждая новая миграция будет появляться в этой нашей папочке
Speaker A
Migrations. Такой подход очень распространён. Вот так вот создавать таблицы через гошное приложение не принято, потому что нету явного разделения между описанием схемы данных и между использованием этой самой схемы данных. А когда у нас описание схемы данных находится в отдельной папке
Speaker A
Migrations, тогда мы можем чётко видеть, что из себя представляет схема данных на разных её версиях. ASQL-запросы, которые уже непосредственно используют эту схему данных, то есть добавляют какие-то данные, меняют, удаляют, получают уже сохранённые данные. Эти SQL-запросы лежат внутри приложения. Это гораздо
Speaker A
удобнее, чем если бы мы создавали таблицы прямо изнутри языка программирования, потому что в будущем, когда наш проект будет развиваться, крайне большая вероятность того, что придётся мигрировать схему данных на новую версию. А поддерживать миграции внутри гошного кода - это просто
Speaker A
неудобно, это чревато какими-то багами, это просто ненаглядно. А когда мы открываем проект, видим папку Migrations, мы сразу понимаем, что в этой папке описана вся схема данных и все миграции между различными версиями этой самой схемы данных. Таким образом, мы с вами рассмотрели написание файлов
Speaker A
миграций для первых трёх версий нашей схемы данных. А вот написать файлы миграции для того, чтобы была возможность уже переключаться на четвёртую версию схемы данных, где у нас в нашей таблички users добавляется ещё и столбец о себе. Это я уже оставляю вам в
Speaker A
качестве домашнего задания. Попробуйте сами создать себе новые файлы миграций. Попробуйте в них описать SQL команды, которые необходимы для того, чтобы к таблице users добавить столбец для файла миграции Up и наоборот удалить столбец для файла миграции Dн, чтобы можно было
Speaker A
успешно переключаться между третьей и четвёртой версией. После этого, конечно же, необходимо всё написанное протестировать посмотреть что действительно при переключении с третьей версии на четвёртую у вас в таблице Users столбец о себе добавляется, а при переключении, наоборот, с четвёртой
Speaker A
версии на третью у вас столбец в таблице Users о себе наоборот удаляется. Вот так мы с вами поговорили про миграции и посмотрели, как эти самые миграции работают на практике. Я понимаю, что сперва может показаться, что миграция - это какой-то бесполезный инструмент.
Speaker A
Ведь, казалось бы, зачем мне так запариваться, устанавливать стороннюю библиотеку, зачем мне описывать какую-то ещё директорию, описывать отдельно файлы миграций, если я просто могу вот так вот одной функцией в гошном приложении создать себе всю схему данных. Но на самом-то деле для каких-то маленьких
Speaker A
простых проектов может быть оно так и есть. Но как только ваше приложение начнёт развиваться, как только у него начнут появляться новые фичи, как только у вашего приложения начнут появляться настоящие живые пользователи, только тогда вы сможете по-настоящему оценить удобство механизма миграции по сравнению
Speaker A
с тем, чтобы создавать схему данных внутри приложения. Потому что в конечном счёте вы всё равно придёте именно к тому, что вам нужны миграции для того, чтобы совершенствовать уже существующую схему данных. Так зачем же тогда городить велосипед? если есть уже
Speaker A
готовое, проверенное временем решение. Давайте сделаем небольшое лирическое отступление и поговорим про выбор операционной системы для ээнд-разработки на языке программирования GONG. Я понимаю, что скорее всего на данный момент большинство из тех, кто смотрит этот курс, пользуется операционной системой Windows. И до настоящего
Speaker A
момента, то есть все предыдущие темы можно было довольно спокойно потренировать, можно было спокойно пройти именно на операционной системе Windows. Но вот с дальнейшими темами на винде могут возникать проблемы. Что это за проблемы и почему они могут возникать? Во-первых, необходимо
Speaker A
понимать, что Linux и Macos - это две родственные операционные системы, которые принадлежат семейству операционных систем Unix. А это значит, что очень много аспектов у этих двух операционных систем крайне похожи, если не являются, в принципе, одинаковыми. А вот винда стоит неким особняком от этих
Speaker A
двух операционных систем и уже не принадлежит семейству Unix. Далее давайте сыграем в небольшую игру. У нас есть четыре пункта, и каждый из этих пунктов нужно отнести к какому-то конкретному семейству операционных систем. Но у нас только два семейства -
Speaker A
это Unix и Windows. Ну, поехали. Первый пункт. Под эти системы заточено множество инструментов для разработчиков. Это относится кникс подобным системам. Хорошо. Второй пункт- необходимость использовать большое количество костылей для разработки. Это относится к винде.
Speaker A
Далее. больше комьюнити-разработчиков, ну, больше комьюнити-разработчиков именно на Unix подобных операционных системах. Далее, четвёртый пункт- огромное количество гайдов и форумов по вашей проблеме. Это тоже относится именно к Unix подобным операционным системам и является следствием третьего пункта. Так как у UNIК подобных
Speaker A
операционных систем комьюнити разработчиков просто больше, значит, и больше гайдов и форумов, на которых вы сможете найти решение какой-то своей проблемы. И тут уже начинает складываться такая картина, как будто бы на винде, в принципе, лучше не разрабатывать. И на самом-то деле так
Speaker A
оно и есть. На винде можно разрабатывать, на винде можно использовать большинство современных инструментов для разработки, но это всегда будет требовать каких-то костылей. Это будет очень неудобно. А когда вы столкнётесь с какой-то проблемой, то шанс того, что вы найдёте
Speaker A
её решение в интернете, он меньше, чем шанс того, что вы найдёте решение своей проблемы. если бы вы пользовались Unix операционными системами. Просто потому что коммьюнити разработчиков на Unix операционных системах больше. И что самое интересное, большинство костылей, которые призваны поддержать разработку
Speaker A
на винде, они работают следующим образом. Просто берётся Linux ядро и эмут в рамках Windows операционной системы. Ну, даже звучит на самом-то деле немножко смешно. То есть вы на винде должны будете установить себе эмуляцию ядра Linux и потом ещё на эту
Speaker A
эмуляцию накатывать 1.000 и1 костыль, чтобы хоть как-то приблизиться к тому опыту разработки, к тем возможностям, которые изначально из коробки есть на Linux и на MacOS. А всё вышесказанное означает, что наши дороги с виндой расходятся. Куда же тогда деваться
Speaker A
пользователям винды? Я вам советую установить себе на компьютер Linux. В этом нет ничего сложного. Вы можете поставить Linux рядом со своей операционной системой Windows, и во мне придётся её полностью удалять со своего компьютера. Просто выделите себе некоторое место на диске и установите
Speaker A
туда Linux. Я крайне советую выбрать один из этих двух дистрибутивов. Либо Уунту, либо Popс. Просто хорошие, понятные и надёжные дистрибутивы. Ну и тут я ещё от себя привёл несколько пунктов, почему всё-таки от винды мы отказываемся. Во-первых, если бы мы от
Speaker A
винды не отказались, то рассмотрение дальнейших тем превратилось бы в очень рваное повествование. Я бы рассказывал, как выглядит инструмент на Unix операционных системах. И каждый раз приходилось бы ставить на паузу и говорить: "Ой-ой-ой, подождите, а вот на винде, а вот на винде по-другому.
Speaker A
Смотрите, как на винде". Потом мы обратно возвращаемся на Unix подобную систему. Я продолжаю рассказывать и потом через 15 секунд объяснение опять: "Ой, подождите, а вот на винде, а вот на винде по-другому?" Нет, это нам не подходит. это не имеет никакого смысла.
Speaker A
Это испортить просто итоговый материал. Второй пункт - это излишнее увеличение хронометража курса. То есть мне придётся объяснять каждый материал чуть ли не дважды. Во-первых, мне придётся объяснять, как это используется на Unix операционных системах, а потом ещё дублировать это для винды. Это очень
Speaker A
сильно растянет хронометраж нашего курса. Но что самое обидное, было бы зачем растягивать. То есть смысл вот сейчас держаться за эту винду, пытаться под неё всё рассказать, если в конечном счёте вам, как разработчикам, рано или поздно придётся перейти либо на Linux,
Speaker A
либо на Macos. Потому что даже когда вы устраиваетесь работать в какую-то компанию и там выдают технику, то эта техника 90% будет либо на Линуксе, либо на Макуos. Поэтому лучше уже сейчас начать переучиваться, чем потом закатывать глаза на рабочем месте. А
Speaker A
почему же у меня не винда моя с костылями? Вот. Ну и третий пункт, почему всё-таки винду мы из этого курса выкидываем, потому что я сам не разрабатывал практически на винде. То есть я почти сразу понял, что это не
Speaker A
самая лучшая затея. А поэтому я не могу ручаться за достоверность информации, которую я вам буду давать, если бы я что-то рассказывал про разработку на винде. Что же это всё значит? А это значит то, что если у вас операционная
Speaker A
система Windows, то вам прямо сейчас нужно поставить этот курс на паузу и пойти и установить себе какой-нибудь Linuxдистрибутив. Опять же, я советую либо убунту, либо по пояс, но если вы хотите какой-то другой дистрибутив накатить, пожалуйста. И во всех
Speaker A
дальнейших рассматриваемых темах я буду считать, что вы уже сидите либо на Линуксе, либо на MacOS. И большинство из того, что мы будем рассматривать, оно не будет работать на винде, так как я показываю в этом курсе. А поэтому те,
Speaker A
кто сидит на винде, бегом устанавливать себе Linux. Ну а те, кто уже на Линуксе или же на Маку, предлагаю перейти к следующей теме.
Speaker A
Следующая рассматриваемая тема - это переменные окружения. Мы посмотрим, что такое переменное окружение, поговорим, для чего это нужно и как это используется. Значит, давайте сперва поговорим, какие у нас, в принципе, бывают окружения. Во-первых, у нас бывает системное окружение. То есть, как
Speaker A
мы видим, да, системное окружение - это самый большой круг. Это у нас целая наша операционная система. Ну, к примеру, целый наш Linux или целая MacOS. Вот эта система, то есть системное окружение.
Speaker A
Дальше есть пользовательское окружение, то есть у нас в рамках системы может быть несколько пользователей. Ну вот, например, здесь есть пользователь Саша.
Speaker A
Это уже пользовательское окружение. То есть раз пользовательское окружение, два пользовательское окружение, три пользовательское окружение. И в рамках каждого пользователя у нас есть ещё сеансовое окружение. Что такое сеансовое окружение? Сеансовое окружение - это окружение, которое существует только в рамках какого-то сеанса. Конкретный
Speaker A
пример, самый распространённый - это терминал. Я открываю терминал, а значит, я создаю какой-то новый сеанс. И если я буду создавать новые терминалы, это уже будут отдельные сеансы. Вот, к примеру, я создам себе три терминала отдельных.
Speaker A
Раз терминал два терминал три терминал. И это на картинке соответствует трём разным сеансам. Раз сеанс, два, сеанс, три сеанс.
Speaker A
Соответственно, у каждого сеанса своё окружение. Вот это раз окружение сеансовое, два окружения сеансовое, три окружения сеансовые. Хорошо, что же такое переменные окружения? Переменные окружения - это такая сущность, которая очень похожа на уже знакомые нам переменные в языке программирования.
Speaker A
Только переменные окружения существуют не в рамках какой-то программы, а в рамках целого окружения. То есть мы с вами можем задать такую переменную, которая будет существовать во всей операционной системе. И все программы, которые запускаются в этой операционной системе, смогут получить значение этой
Speaker A
переменной. Такая переменная окружения называется системной, то есть это системная переменное окружение. Дальше у нас есть пользовательские переменные окружения. Это уже кто-то мог догадаться, такие переменные окружения, которые существуют только в рамках какого-то пользователя. И все программы, которые запущены в рамках этого
Speaker A
пользователя, могут получить значение этой переменной окружения. Это пользовательская переменная окружения. И дальше у нас есть сеансовые переменные окружения. То есть это переменные окружения, которые существуют только в рамках какого-то сеанса. Ну и, соответственно, все программы, которые из этого сеанса запускаются, они тоже
Speaker A
могут получить значение этой переменной окружения. Мы в этом курсе с вами будем использовать только лишь сеансовые переменные окружения, потому что это наиболее частая практика.
Speaker A
Пользовательские переменные окружения или же системные переменные окружения, даже если и используются, то это уже скорее относится к какому-то курсу по операционным системам. Но именно в эээнд разработке чаще всего используются именно сеансовые переменные окружения, которые существуют только в рамках
Speaker A
какого-то сеанса, ну или же терминала. Давайте сейчас постепенно от теории перейдём к практике и посмотрим, как же задать переменную окружения уровня сеанса. Для этого я открываю терминал.
Speaker A
Давайте вот этот вот возьму произвольный и задам переменную окружения. Для того, чтобы мне задать переменную окружения, мне нужно написать ключевое слово экспорт. Экспорт. После этого ставлю пробел и пишу название переменной.
Speaker A
Например, я назову свою переменную абобабоба. Ставлю знак равно. И справа от знака равно я должен написать значение этой переменной. Ну, например, пускай это будет 1 2 3. Нажимаю Enter.
Speaker A
После этого я могу написать print ENV, пробел и название переменной. Нажимаю Enter, и я получил значение этой самой переменной. И тут стоит обратить внимание на такой момент. Мы с вами не задали нигде тип переменной. То есть, когда мы с вами создавали переменные в
Speaker A
Гошке, мы указывали, какой именно тип у этой переменной. Целое число, дробное число, булевский тип и так далее. Тут же мы с вами не указали никакой тип. Мы просто написали название переменной и положили в неё какое-то значение. Так вот, на самом-то деле, у переменных
Speaker A
окружения тип всегда строка. То есть даже если мы в переменную абобабо кладём 123, на самом-то деле мы кладём строку, которая содержит три символа: 1 2 и 3.
Speaker A
Вот эти вот три символа. Просто это стоит учитывать, что все переменные окружения, которые находятся у нас в системе, на самом-то деле имеют строковый тип. Именно поэтому нам не нужно указывать тип этой переменной. То есть я не пишу экспор абоob int = 123,
Speaker A
да? То есть я такого не пишу. Или наоборот абоobст string = 123. Нет, я просто пишу экспорт або ABба = 123.
Speaker A
Соответственно, слева название переменной окружения, справа её значение. Но значение всегда имеют строковый тип. То есть, по сути, сейчас в переменной абоба лежит строка 1 2 3.
Speaker A
Именно поэтому в переменную окружения по сути можно класть вообще что угодно, если это может быть представлено в виде строки. Я могу вот буков добавить, потом цифр, потом ещё буков, потом вот ещё две звёздочки. Нажму Enter. И после этого я
Speaker A
могу спокойно напечатать значение этой переменной. Вот оно. То, что мы задали. Это, по сути, всего лишь строка. Это важно понимать. То есть здесь я задал переменную, здесь я получил её значение.
Speaker A
Таким образом, я вот тут задал переменную окружения уровня сеанса. И в рамках этого сеанса данная переменная окружения будет доступна всем программам. Что это значит? Это значит, что если я из этого терминала буду запускать какую-то гошную программу, например, то эта самая гошная программа
Speaker A
сможет получить значение вот этой вот переменной окружения абоба. Но в других сеансах этой переменной уже не существует. То есть, если я скопирую вот эту вот команду print end абоobба и попытаюсь в рамках другого сеанса получить значение этой переменной, я
Speaker A
получу ошибку, потому что у меня в рамках этого сеанса не существует этой переменной окружения. Ну и, соответственно, в рамках этого сеанса её точно также не существует. Вот мы ничего не получаем. Красная стрелочка, значит ошибка. А здесь, если я посмотрю в
Speaker A
рамках того сеанса, где я создал эту переменную, то здесь она существует и её значение есть. Ну хорошо. То есть мы увидели, как мы можем в терминале задать переменную окружения и потом получить её значение. А как получить значение этой
Speaker A
переменной изнутри гошного приложения? Ну давайте перейдём в наш проект. И здесь я открою main.gooфайл. Всё закомментирую, что было до этого. Там работа с базой данных у нас. И дальше начну писать OS. И как мы видим, нам подсказывается пакет ОС. Что это такое?
Speaker A
Пакет О - это такой пакет на уровне Гошки, который позволяет работать с некоторой функциональностью операционной системы. Тут очень много функций, методов, структур, но нас сейчас конкретно интересует функция getv. Эта функция, как мы видим по описанию, принимает название переменной окружения
Speaker A
и возвращает её значение. Ну и, как мы можем видеть, функция gettf возвращает строку. Почему? Да, потому что вспоминаем, что все переменные окружения являются строками. Поэтому мы, когда переменную окружения получаем, мы получаем её значение, нам приходит строка. И также уточнение, что если у
Speaker A
нас переменное окружение не задано, тогда функция вернёт пустую строку. Ну, давайте это и проверим. То есть я буду здесь принимать переменную окружения, которое называется, к примеру, phone number, и получать её значение в переменную Wл. После этого буду смотреть, что если вал не равно пустая
Speaker A
строка, тогда буду выводить вал двоеточия и здесь значение этой переменной, иначе я буду выводить переменное вал не задано.
Speaker A
Всё, собственно, весь код нашей программы. Ну и давайте все эти изменения делать в рамках отдельной ветки. Git branch feature and war.
Speaker A
Назову эту ветку. Переключусь на неё. Ну и, соответственно, все незакомеченные изменения, они также со мной перенесутся на вот эту вот ветку. Ну всё, теперь мы создали и переключились на ветку Feature Nwar. Давайте смотреть, как работает наша программа. Давайте я её просто
Speaker A
запущу. Переменная Valдана. То есть у нас переменное окружение Phone Number просто не существует. Ну давайте её теперь зададим. Копирую её название и пишу expР number равно. Ну и, например, тут номер телефона какой-нибудь напишу: +7 999 88 776. Нажимаю Enter. И теперь
Speaker A
ещё раз запущу это гошное приложение go runmain. И как я вижу, я задал значение переменной Phone number изнутри терминала, изнутри сеанса и потом запустил гошное приложение изнутри этого же сеанса и смог получить значение этой переменной окружения. То есть, если мы
Speaker A
задаём значение какой-то переменной в рамках сеанса, то потом все запущенные приложения из этого сеанса смогут получить значение вот этой вот заданной переменной окружения. Хорошо. А что, если мы из другого терминала запустим это гошное приложение? Вот я здесь в
Speaker A
оско-коде создам ещё один терминал, ещё один сеанс и уже отсюда попытаюсь запустить это самое гошное приложение.
Speaker A
Нажимаю Enter. Переменная не задано. То есть смотрите, какой интересный момент у нас происходит. В зависимости от того, из какого терминала мы с вами запускаем гошное приложение, это самое гошное приложение начинает работать поразному, потому что в одном терминале у нас
Speaker A
задано переменное окружение, а в другом терминале это самое переменное окружение не задано. Но когда мы задаём с вами переменную окружение в рамках какого-то терминала, то эта переменная окружения, она существует только в рамках этого терминала или же, говоря более точным
Speaker A
языком, только в рамках этого сеанса. Когда мы запускаем другой сеанс, то в этом другом сеансе этой переменной окружения нету, потому что мы её не задавали. И, соответственно, гошное приложение, которое запускается из этого сеанса, не видит эту переменную окружения. Доступа туда нету. Ну,
Speaker A
примерно так работают переменное окружение уровня сеанса. То есть мы в каком-то терминале можем задать переменную окружение, после чего запустить какую-то программу, и эта самая программа сможет получить значение вот этой вот заданной переменной окружения. Ну, хорошо, мы с вами вроде
Speaker A
как разобрались, какие переменные окружения бывают, даже теперь умеем создавать переменные окружения уровня сеанса и даже умеем получать их значение изнутри гошного приложения. Но как вообще эти самые переменные окружения используются? Для чего они вообще нужны?
Speaker A
Я же могу создать переменную просто внутри гошного приложения. Зачем мне создавать переменную прямо в окружении?
Speaker A
Ну, переменное окружение много где используются. Мы сейчас даже прямо всё не будем перечислять. много увидим просто на практике, но один из популярных кейсов использования переменных окружения - это передача чувствительных данных. Какие у нас в приложении тут чувствительные данные? У
Speaker A
нас есть функция create connection и в ней строка подключения. А в этой строке подключения у нас логин и пароль для подключения к нашей базе данных. Чем это грозит? Это грозит двумя вещами. Первое - это если у нас проект в открытом
Speaker A
доступе на гитхабе лежит, тогда любой человек сможет вот так вот зайти в код и увидеть логин пароль от базы данных, с которой мы работаем. И если это у нас популярное приложение, которым пользуются люди, они туда могут добавлять какие-то свои персональные
Speaker A
данные, а у нас логин пароль от базы данных просто в открытом доступе лежит. Но это очень плохо с точки зрения безопасности, потому что любой человек может зайти, увидеть эти логин и пароль и просто получить доступ к целой базе
Speaker A
данных нашего приложения, нашей компании, украсть оттуда личные данные пользователя и потом каким-то образом их с шантажировать или нас как разработчика этого программного продукта. В общем, это очень плохо. Ну и второй кейс - это если мы работаем в какой-то компании, у
Speaker A
нас код может быть и закрыт внутри компании, но логины и пароли всё равно будут видеть все, у кого есть доступ к репозиториям. этой самой компании.
Speaker A
Получается, рандомный разработчик может просто зайти в репозитории компании, увидеть логины и пароли от базы данных и просто получить все персональные данные пользователей приложения которые разрабатывает эта компания. Представим, что это какая-нибудь компания, например, Госуслуги. Ну и теперь представляем, какого уровня персональные данные можно
Speaker A
просто потерять, слить, если вот так вот в открытом доступе держать логин и пароль. То есть это плохо. логины, пароли, всякие токены. Вот так вот прямо в коде задавать. Это плохо, это небезопасно, потому что это видно любому человеку, у кого есть доступ к вашему
Speaker A
репозиторию. И если пока вы разрабатываете один сам для себя, это может не быть такой большой проблемой, то когда вы начинаете разрабатывать в рамках какой-то компании большой, то это становится огромной проблемой, потому что данные могут утечь очень быстро.
Speaker A
Каким образом переменные окружение помогают решить эту проблему? Да, таким, что я вот эту строку подключения не буду держать внутри исходного кода, который потом попадёт у меня на удалённые сервера в репозиторий. Я эту строку подключения буду держать в рамках
Speaker A
переменной окружения и буду получать эту самую переменную окружения через уже знакомый нам пакет OS и функцию get.
Speaker A
Назову эту переменную окружения con string. Буду её класть в переменную уровня гошки con string. И потом эту самую строку подключения я буду просто передавать функцию connect библиотеки pgx. Таким вот образом. Соответственно, что сейчас я сделал? Я из моего
Speaker A
приложения просто взял и убрал чувствительные данные, конкретно логин и пароль от базы данных. Теперь тут нету нигде логина, пароля. Вот хоть обыщись.
Speaker A
Внутри приложения нигде нету логина пароля. А где этот логин пароль у меня будет задаваться? Он будет задаваться через переменную окружения.
Speaker A
Соответственно, если я сейчас просто попытаюсь запустить приложение, то у меня не получится подключиться к постгресу, потому что у меня не задано переменное окружение Констрин.
Speaker A
Соответственно, сюда вернётся пустая строка, и мы не сможем по пустой строке подключиться. Ну давайте посмотрим. Всё, случилась ошибка, потому что мы не можем по пустой строке никуда подключиться. Не получается получить ни логин, ни пароль, ничего. Хорошо. А теперь давайте я задам
Speaker A
переменную окружение. Exпост string равно. И вот это наша самая строка подключения. Ну, тут, в принципе, можно с кавычками, можно без кавычек.
Speaker A
Всё. Задаю эту самую переменную окружение. О'кей. И теперь я запускаю наше приложение. Всё, Саксит, получилось подключиться к базе данных. Почему?
Speaker A
Потому что строку подключения я задал в переменное окружение. Таким образом, я взял и просто сокрыл чувствительные данные в виде логина и пароля от базы данных из нашего приложения. Теперь я смогу точечно задавать эту самую строку подключения там, где посчитаю нужным, и
Speaker A
при этом я не буду её палить в исходных кодах приложения. Вот именно про это переменное окружение. Именно здесь они очень часто используются. Ну вот такая коротенькая темка была про переменные окружение. Мы можем идти дальше, ведь дальше мы будем эти самые переменные
Speaker A
окружения использовать очень часто. Далее мы с вами должны поговорить о такой вещи, как makeфай. Для чего он нам вообще нужен, как он используется, какие проблемы решает. Давайте посмотрим. Ну, работать я пока продолжу ветки feature Nv. И для того, чтобы убедиться, что у
Speaker A
вас makeфай есть на вашей системе, нужно написать слово make и через пробел 2 тире version. Если у вас выведется версия вашего йк-файла, это значит, что он у вас установлен. Если он у вас не установлен, значит, вам необходимо его
Speaker A
установить. Но я буду уже предполагать, что вы его самостоятельно установили на ваши компьютеры. Значит, для чего нужен makeфайл? Ну, смотрите, у нас уже на настоящий момент огромное количество команд, которые нужно держать в голове.
Speaker A
Во-первых, это запуск приложения gounmail.go. Во-вторых, это создание файлов миграций, там migrate пробел. У нас есть Create, есть у нас ещё app, есть Down, есть один, плюс там ещё нужно очень много дополнительных данных писать. Плюс у нас теперь ещё появилась
Speaker A
переменное окружение для того, чтобы подключаться к базе данных. И перед запуском нашего приложения мы должны ещё написать экспорт, connectionст string равно и так далее, так далее, и так далее. То есть очень большое количество команд нужно держать в голове. Эти все
Speaker A
команды нужно руками прописывать, их может быть много. Например, у нас, повторюсь, для запуска приложения нужно уже целых две команды прописать.
Speaker A
Во-первых, это задать переменную окружения конг. Во-вторых - это запустить само приложение. Это неудобно. Это нужно всё помнить и держать в голове. Решением является make обычно создаётся в корне проекта, он так и называется: makeфай. И в нём мы можем
Speaker A
с вами прописать так называемые таргеты для использования нашего проекта. Какие у нас могут быть таргеты? Во-первых, у нас может быть таргет сервис Run. Что такое сервис Run? Это запуск нашего сервиса. Что должно произойти в этом таргете? В этом таргете мы должны задать
Speaker A
переменную окружения constring. Вот это наша строка подключения. Задаём переменную окружение и перенос строки. И мы должны ещё, собственно, запустить наше приложение. Хорошо, описали таргет. А теперь как его использовать? Использовать таргет можно следующим образом. Пишем ключевое слово make в том же месте, где расположен
Speaker A
makeфа. Ну, обычно, повторюсь, это корень проекта. Потом пробел и потом название таргета. В нашем случае это севис Run. Servвис Run. Нажимаю Enter. И я вижу. Здесь я вижу именно команду, которая была исполнена, когда я ввёл этот таргет, и потом вывод самого
Speaker A
приложения. Если я не хочу видеть команду, которая была исполнена, я тут могу просто собаку поставить и всё. Я буду видеть вывод только лишь самого приложения. Что нам здесь дал Makeйфайл?
Speaker A
Makфайл нам здесь дал то, что мы вызов нескольких команд можем просто объединить под одним каким-то таргетом и потом писать make через пробел название таргета. И мы будем вызывать тем самым описанные ниже команды. Таким образом, мы эти сложные команды можем один раз
Speaker A
описать, повесить их на какой-то таргет и потом просто вызывать только лишь этот самый таргет. Многие могут задаться вопросом, что же это тут за два амперсанта и обратный слэш я поставил.
Speaker A
Что это такое? Для чего это нужно? Ведь казалось бы, и без этих двух символов.
Speaker A
Вот у нас есть две команды, которые объединены под каким-то таргетом. Ну давайте я верну эти два амперсанта и обратный слэш и покажу вам кое-какой пример. Допустим, у нас есть какой-то таргет произвольный, ну, назову его сам таргет. В этом таргете у нас выполняется
Speaker A
две команды. Пускай первая команда - это задание переменной окружения. То есть экспорт. Назову эту переменную окружение NV war = hello. Всё. То есть я вот здесь вот в седьмой строчки в рамках таргета сам таргет буду создавать переменную окружения NAR и класть в неё строковое
Speaker A
значение hello. Хорошо. Теперь следующей же командой в рамках этого таргета, сам таргет, я хочу напечатать значение переменной окружения ENVAR на экран. Для этого я пишу print env war. Ну и казалось бы, что такого? То есть у нас есть какой-то сам таргет. У нас в рамках
Speaker A
этого таргета описаны две команды. Первая команда - это выставить переменную окружения. Вторая команда - это показать значение этой переменной окружения. Ну и казалось бы, всё должно работать. Давайте попробуем запустить вот этот вот сам таarгет. Для этого я пишу make сам target. Нажимаю Enter. И я
Speaker A
вижу, что у меня ошибка. У меня не вывелось никакое значение hello. У меня не вывелось значение переменной окружения NAR, потому что его просто не существовала на момент вызова вот этой вот команды. Но почему его не существовало на момент вызова этой
Speaker A
команды, если мы буквально в предыдущей строчке задали значение этой переменной? Для того, чтобы понять, почему так произошло, нам с вами необходимо посмотреть на вот эту вот схемку. Вот наш какой-то таргет в рамках make-файла.
Speaker A
А вот команды этого таргета. первая команда, вторая команда, третья команда. Так, в чём прикол? Каждая отдельная команда в рамках какого-то makeфайл таргета запускается в отдельном сеансе.
Speaker A
То есть на каждую отдельную команду у нас запускается отдельный сеанс. К чему это приводит? К тому, что если мы в какой-то строчке, в какой-то команде Makeйфайл таргета задали переменную окружения, то мы её задали вот, например, в этом сеансе в первом. А если
Speaker A
мы уже следующей строчкой будем пытаться запустить какую-то программу, которая работает с этой переменной окружения, или просто попытаемся получить значение этой переменной окружения через print ENF, то эта уже команда по получению значения этой переменной окружения будет запущена в другом сеансе. А переменное
Speaker A
окружение у нас осталось вот в первом сеансе, а во втором сеансе у нас её не существует. И именно поэтому мы получаем такую проблему, что мы вроде как задали эту переменную окружения, но уже в следующей строчке её не существует,
Speaker A
потому что она осталась в другом сеансе. У нас на седьмую строчку запустился сеанс и на восьмую строчку запустился сеанс. И переменное окружение осталось в одном сеансе, а получить её значение мы пытаемся уже в другом. Ну хорошо, есть проблема. Как эту проблему решить?
Speaker A
Наиболее популярное решение этой проблемы выглядит следующим образом. Мы пишем все команды, которые нам необходимо с вами выполнить. Допустим, у нас это две команды, и объединяем их двойным амперсантом. Что это нам даёт?
Speaker A
Это нам даёт то, что, грубо говоря, на уровней-файла эти две команды будут восприниматься как одна команда. А, соответственно, эта одна команда будет выполнена в рамках одного общего сеанса.
Speaker A
И именно для этого мы ставим вот этот вот двойной амперсант. для того, чтобы вызываемые команды объединить в одну команду. И это объединение нам даст то, что эти две команды будут выполнены в рамках одного сеанса. Соответственно, если мы в рамках первой команды зададим
Speaker A
какую-то переменную окружения, то она задастся вот в этом вот сеансе. И следующая команда, которая будет выполнена в этом же самом сеансе, она сможет уже получить значение этой переменной окружения. Поэтому, если нам необходимо, чтобы вот эти вот две или же
Speaker A
более команд были выполнены в рамках одного сеанса, тогда мы ставим между ними вот этот вот двойной амперсант.
Speaker A
О'кей, давайте попробуем это сделать. То есть я здесь между экспор = hello и print nw поставлю двойной ампер. Теперь давайте попробуем запустить этот наш make file target. Make сам target. И я вижу, что теперь у меня нету никаких
Speaker A
ошибок, и я смог получить значение вот этой вот переменной NAR. Вот значение Hello. Вот оно вывелось у меня на экран.
Speaker A
Почему это произошло? Потому что я поставил двойной амперсант и тем самым я выполнил вот эти две команды в рамках одного сеанса. А это значит, что если я в рамках этого сеанса задал какую-то переменную окружения, то она у меня
Speaker A
будет доступна во всех последующих командах. Просто потому, что это один и тот же сеанс. О'кей. Но для чего нам вот этот вот обратный слэш? Для чего нам двойной амперсант? Мы разобрались. А вот обратный слэш для чего? Обратный слэш
Speaker A
просто продиктован правилами синтаксиса при написании shше shell команд. Shell команда - это какие-то команды, которые выполняются у нас в терминале. Так вот, обратный слэш используется тогда, когда я хочу перенести какую-то сложную команду многосоставную на несколько строчек. Почему я хочу перенести? Потому
Speaker A
что мне удобно, когда с каждой новой строки у меня начинается новая команда, и это чисто визуально более приятно и более понятно читать. То есть в случае с таргетом сам таргет, я хочу в отдельной строке задать значение переменной окружения и в другой строке уже работать
Speaker A
с этой переменной окружения. Это просто более наглядно, чем городить вот это вот всё в одну строку. Поэтому я хочу перенести вторую команду на новую строчку. Но вот так вот будет ошибка, потому что после двойного амперсанта makeфайл будет ожидать какого-то
Speaker A
продолжения, а этого продолжения на этой строчке нету. И чтобы сказать make йфайлу, что продолжение на следующей строчке, мы ставим обратный слэш, что означает, что посмотри на следующую строчку. Это будет продолжение текущей строчки. Просто этот обратный слэш нужен, чтобы многосоставную сложную
Speaker A
команду, состоящую из нескольких подкоманд, мы могли разделить на несколько строчек. Это просто визуально приятнее выглядит и проще читается. Ну вот мы с вами разобрали, для чего нам вот этот вот двойной имперсант и обратный слэш. То есть мы могли бы и без
Speaker A
них попробовать сделать, но тогда мы задали бы переменную окружения Connectionст string в одном сеансе, а гошную программу запустили в другом. К чему бы это привело? К тому, что в гошной программе не существовало бы переменное окружение Констрин. Она осталась в другом сеансе. Для того,
Speaker A
чтобы это переменное окружение Констринг была в том же сеансе, что и наша гошная программа, мы между ними ставим двойной амперсант. Но так как в одну строчку это довольно тяжело читать, ну, прямо какой-то огроменный набор символов, мы здесь ставим обратный слэш и переносим
Speaker A
на новую строку для большей читаемости и наглядности. Ну давайте ещё опишем таргет для того, чтобы накатывать самую свежую миграцию. Это у меня будет migrate up. И здесь что нам нужно сделать? Вот команда наша, да, и по сути только лишь её можно и выполнить.
Speaker A
Migrate pass migrations. Задаём строку подключения. Up. Всё, мы с вами сделали make target для того, чтобы накатывать миграции. Давайте посмотрим, как это работает. Я просто пишу в корне проекта make migrate up. Всё. И у меня накатывается самая последняя версия
Speaker A
миграции. Точно так же можно сделать вниз. Это у меня будет migrate down. И здесь всё то же самое, за исключением последнего слова в этой команде. Down.
Speaker A
Всё. Теперь я пишу make migrate. Down. У меня спрашивают, хочу ли я действительно откатить все свои миграции. Нажимаю да.
Speaker A
И они откатываются. И теперь обратно могу make migrate up и обратно миграции накатываются. Таким образом, я благодаря make-файлу могу не запоминать все эти сложные команды, могу не держать их в голове, могу не прописывать их каждый раз руками. Я просто их пишу в
Speaker A
мейк-файле, объединяю их под какими-то таргетами и просто вызываю потом эти самые таргеты. Это крайне удобно, это крайне полезно и это очень часто используется. Но кто-то может задаться вопросом: "Так, слушай, теперь у тебя логин, пароль от базы данных находится в
Speaker A
Ййк-файле. И опять любой человек сможет потом зайти в твой репозиторий и посмотреть логин пароль". Я отвечу: "Да, так оно и есть". Поэтому мы должны Connection string тоже передавать сюда именно в виде переменной окружения. Как обычно задаются переменные окружения вот
Speaker A
в таких вот серьёзных проектах? Как говорится, следите за руками. Создаётся файл тон. В этом файле задаются все переменные окружения, которые необходимы проекту. В нашем случае это вот connection string, переменное окружения.
Speaker A
И выглядит это следующим образом. Только принято их называть капsлоком con string. Следующим образом без ключевого слова экспорт, просто вот так вот мы задаём переменные окружение, которые нам нужны. Далее в самом make-файле в самом начале пишется следующее: include.
Speaker A
Export. Что это такое? Тут я написал, что я хочу взять из файла точка все переменные окружения и потом их применить для текущего сеанса.
Speaker A
Соответственно, вот эти вот переменные окружения, которые тут будут написаны, они при вызове какого-либо таргета в мейк-файле они подключатся и выставятся в текущем сеансе. А это значит, что тут уже экспор я могу не писать, и у меня при запуске через target service run
Speaker A
гошного приложения автоматически подсосётся переменное окружение из точка N файла. Ну и все переменные окружения, которые тут будут, они автоматически подсосутся в makeфайл, автоматически выставятся в текущем сеансе и потом каждый таргет, который мы запускаем, он будет запущен в сеансе, где выставлены
Speaker A
данные переменные окружения. Ну и, соответственно, только в гошном приложении мы уже должны использовать констлоком. Давайте simple connection. И здесь конing у нас капслоком. И точно так же внутри самого make йфайла. Здесь я не буду передавать эту переменную окружения хардкодом. Я просто напишу
Speaker A
следующим образом доллар, фигурные скобки и название переменное окружение. Всё, она здесь передастся вот таким вот образом. Ну и, соответственно, отсюда я тоже могу её убрать. Оп, оп, конструн.
Speaker A
Всё. Ну и так как у нас это констing, она используется и в гошном приложении, и в миграциях, то давайте сделаем её универсальной, чтобы она подходила и в гошное приложение, и в миграции. Ну для этого я тут в конце напишу SSL mode
Speaker A
равно disable. Всё, теперь у нас это переменное окружение, вот эта строка подключения будет подходить и в гошное приложение, и будет подходить в библиотеку Migrate. Ну давайте теперь всё вместе соберём, как это работает. У нас теперь вся работа с нашим проектом
Speaker A
происходит через мейк-таргеты. Мейк-таргеты у нас вызываются в рамках какого-то терминала, то есть в рамках какого-то сеанса. Когда мы пишем make, например, севис run, что происходит?
Speaker A
Когда мы пишем ключевое слово make, сразу же подгружается makeфайл. Makeфайл подгружается весь, начиная с первой строки. В первой строке make-файла у нас include. - это значит подключить вот этот вот точка файл. Мы его подключаем, но вот на момент только лишь первой
Speaker A
строчки ещё никакие переменные окружения не выставлены. Выставляются эти переменные окружения в текущий сеанс, когда makeфай наступает на вторую строчку, конкретно на слово экспорт. Вот здесь на второй строчке все взятые переменные окружения из точка N файла выставляются в текущий сеанс. Дальше
Speaker A
Makeфайл доходит до таргета севис Run. И на момент выполнения команд в таргете Servвиice run у нас уже выставлены все переменные окружения, описаны в DNF. А у нас тут описано connection string.
Speaker A
Соответственно, это connection string идёт и в миграции, когда мы вызываем миграции, благодаря чему библиотека Migrate может подключиться к базе данных. И это же самое Connection String у нас идёт в наше гошное приложение, благодаря чему гошное приложение может подключиться к той же самой базе данных.
Speaker A
Давайте проверим, как это работает. Вот я пишу make run suited, то есть у нас гошное приложение вполне себе может подключиться к базе данных. После этого пишу make migrate down.
Speaker A
Да, действительно, и make migrate up. Всё, миграции в обе стороны работают. Гошное приложение работает. Получается, мы в то N в файле задали эту самую строку подключения и потом она у нас пошла через переменное окружение и в библиотеку миграции, и в гошное
Speaker A
приложение. И в чём ещё преимущество таким образом задавать переменные окружения, когда они у нас описаны в точка N файле, а потом уже мы в make-файле подключаем этот точка N-файл и задаём эти переменные окружения. Это нам даёт то, что на все таргеты, на все
Speaker A
сеансы, которые будут запущены в рамках этих таргетов, даже если мы тут отдельно напишем несколько команд, там команда один, команда 2, команда 3 и так далее, на каждый сеанс, запущенный в рамках make йк-файла, у нас вот это вот переменное окружение будет
Speaker A
распространяться, потому что это переменное окружение задаётся в самом родительском сеансе, а потом уже выполнение таргетов, выполнение каких-то команд в рамках этих таргетов. будет считаться дочерним сеансом, а дочерний сеанс всегда наследует переменные окружения родительского сеанса. Это значит, что если мы таким образом в
Speaker A
мейк-файле зададим переменную окружения, она будет во всех дочерних сеансах существовать. Но кто-то скажет: "Так и что, что мы вынесли переменную окружения в точка Nфile? Всё равно она же здесь видна, да?
Speaker A
Вы будете правы". Поэтому это ещё не всё. Люди пошли дальше и придумали так называемый Gitignor файл. Вот я пишу Git и Ignore. В этом Git и Ignor файле я должен написать те файлы, которые я хочу, чтобы Git игнорировал. Я хочу,
Speaker A
чтобы Git игнорировал у меня точка Nфайл. Что мне это даст? Это мне даст то, что вот этот вот точка N файл со всем его содержимым для системы контроля версии Git будет абсолютно не виден. То есть этого файла для системы контроля
Speaker A
версии Git больше не существует. Если я посмотрю, например, в Git div, я здесь увижу Gitgnore, я увижу make file, я увижу Simple Connection, но я не увижу точка N файла. Соответственно, всё, что находится в точке N в файле, оно не
Speaker A
попадёт в gitпозиторий, потому что я добавил точка Nфile в Gitignore. Ну и мы можем сделать вывод, что всё, что находится в Git игноре, то не попадает в наш репозиторий. Ну а заместо точка N файла в репозитории обычно попадает
Speaker A
точка N в точка example. То есть это файл, в котором содержится список переменных окружения, которые необходимы для запуска приложения, но они тут не заданы. Соответственно, каждый человек, который себе локально клонирует этот репозиторий, он должен зайти в файл ENF
Speaker A
example, скопировать этот файл, вставить его, назвать его просто точка ENF и потом в нём уже все вот эти вот незаданные параметры взять и задать.
Speaker A
Таким образом, мы избегаем, наконец-таки, того, что чувствительные данные у нас попадают на удалённый гит репозиторий. Вот такая вот схема. Она может на первый взгляд показаться довольно сложной, но она используется практически везде, практически во всех экэнд-приложениях. Что у нас? Переменные
Speaker A
окружения, они не хранятся на удалённом репозитории, а на удалённом репозитории хранится лишь файлик example, в котором дан список тех переменных окружения, которые нужно задать, чтобы приложение запустилось. Но уже наша задача - руками скопировать этот ENF example, назвать его точка ENV и задать здесь все
Speaker A
необходимые переменные окружения для работы нашего приложения. Вся работа с проектом происходит через makeфаile, через make й таргеты. Ну и makeйк таргеты каждый, когда вызывается, он автоматически уже запускается в сеансе, где есть все переменные окружения, описанные в точ N файле. Таким образом
Speaker A
происходит передача и использование вот этих вот чувствительных данных, например, логин и пароль от базы данных.
Speaker A
Также это могут быть всякие токены Telegram ботов, это могут быть токены для доступа к каким-то приватным серверам компании и так далее, и так далее. Всё это передаётся через переменные окружение, которые каждый разработчик у себя локально на компьютере либо же на удалённом продовом
Speaker A
сервере задаёт руками сам, то есть сам создаёт точка Nфайл, сам задаёт вот эти вот все данные, если они у него есть. Но эти данные не попадут в Gitрепозиitoryй, потому что они содержатся все в файле точка ENV, а то у нас в Gitignгor, и
Speaker A
этот самый точка ENF не попадёт в наш gitпозиторий. Соответственно, все чувствительные данные, описанные в точка N файле, также не попадут на удалённый гиit репозиторий. Ну, собственно, и всё.
Speaker A
Мы с вами обсудили, что такое Makeфайл. Мы с вами посмотрели, для чего он нужен, посмотрели, как он работает. И самое главное, мы увидели, как этот makeфайл работает в связке с точка N файлом. В тока N файле задаются все переменные
Speaker A
окружения. Ну и, собственно, всё. Теперь давайте просто добавим все изменения в наш коit. git то git commitm file and nvws git push. Вставляю вот эту вот команду.
Speaker A
Ну и перехожу на git repository. Нажимаю создать request. Создаю его. быстренько могу посмотреть на изменения в файлах. И как я вижу, действительно у меня пропали из репозитория все чувствительные данные. У меня теперь логин, пароль от базы данных. Он у меня не представлен в
Speaker A
Гошном коде. То есть в гошном коде нету логина, пароля от базы данных. В Йк-файле нету логина, пароля от базы данных. И нигде в другом месте тоже этого самого логина пароля нету, потому что логин пароль настоящий, который действительно даёт доступ к базе данных,
Speaker A
находится в точке N-файле. А, точка Nфile у нас в Gitignгре. И всё. И, значит, этот точка N-файл, он хоть и есть у нас локально на компьютере, но он не попал в удалённый репозиторий. Всё чётко, мне всё нравится. Я нажимаю Merch
Speaker A
Pull request, подтверждаю. Всё, я залил ветку Feature Nwar в мастер ветку. Единственное, что важно понимать, что несмотря на то, что мы залили новый комит в наш репозиторий, где мы скрыли все чувствительные данные в точка N- файле, который не попал в наш
Speaker A
репозиторий, всё равно хитрые люди могут зайти в историю комитов, зайти в какой-нибудь предыдущий комит, например, где мы с вами добавляли миграции. Вот сюда вот я нажму. Можно нажать сюда вот browse files, то есть посмотреть все файлы репозитория на версии вот этого
Speaker A
вот комита. Нажимаю. И теперь здесь я уже смогу зайти в папку Feature Postgress, Simple Connection. И здесь вот она, наша строка подключения с логином, паролем. Так и осталось. То есть логин и пароль и чувствительные данные мы хоть в новом комите вроде как
Speaker A
убрали из репозитория, но эти все чувствительные данные, они остались в старых комитах, потому что у нас система контроля версий, а это значит, что мы просто можем на предыдущую версию переключиться. А в предыдущей версии у нас как раз-таки эти логины, пароли
Speaker A
есть. Что это значит? Это значит, что если, не дай бог, на удалённый репозиторий попали какие-то чувствительные данные, их нужно не только в новых комитах убрать, но ещё и по-хорошему либо заменить, либо вообще удалить старые комиты, где вот эти вот
Speaker A
чувствительные данные были на удалённом репозитории. Но это уже продвинутая тема, сейчас мы этого делать не будем, но я просто говорю о том, чтобы вы имели в виду, что какие-то новые комиты, которые вы добавляете в репозиторий, это хорошо, но это не исключает возможности
Speaker A
вернуться или же переключиться на предыдущие комиты вам, вашим коллегам или каким-то злоумышленникам и посмотреть, что же там было на предыдущих комитах. И если на предыдущих комитах чувствительные данные попали в репозиторий, то, несмотря на новые комиты, эти чувствительные данные в
Speaker A
старых комитах останутся. Это нужно учитывать и про это нужно не забывать. Теперь локально у себя переключаюсь на main ветку и делаю git pool для того, чтобы подтянуть новые изменения в мастер ветку. Это, собственно, добавление make-файла example и git ignore. Ну и
Speaker A
перевод гошного приложения с захардкоженной строки подключения на переменную окружения. Ну и то же самое мы сделали для миграций.
Speaker A
Следующая наша тема - это Докер. Эта тема, сразу скажу, вызывает большие проблемы у новичков, потому что, во-первых, сложно понять, для чего вообще этот докер нужен, а потом ещё нужно разбираться с тем, как его использовать. Но, к сожалению или, к
Speaker A
счастью, в наше время в коммерческой разработке без докера просто никуда. Докер решает огромное количество проблем. Докеer сильно упрощает деплой приложений на удалённый сервер. Докер сильно упрощает тестирование. Ну, сейчас мы об этом всём с вами поговорим. Ну, давайте сперва рассмотрим следующую
Speaker A
ситуацию. Мы на своём локальном компьютере разрабатываем какой-то проект. Этот проект имеет версию Го. Этот проект зависит от постгреса версии 18. И этот проект зависит от библиотеки Migrate версии 4. Что значит зависит?
Speaker A
Это значит, что нам необходимо, чтобы библиотека Migrate четвёртой версии была установлена на том компьютере, на котором мы запускаем наш проект. То же самое с Постгрессом версии 18. То есть мы хотим, чтобы Постгрес версии 18 был установлен на том компьютере, где мы
Speaker A
запускаем свой проект. Также наш проект работает на GO124, а значит, мы хотим, чтобы на том компьютере, на котором мы запускаем свой проект, была установлена гошка именно версией 124. И мы вот так вот это всё разрабатываем у себя локально на компьютере. Всё работает,
Speaker A
всё круто, всё запускается, миграции накатываются, данные сохраняются, проект работает без багов. Допустим, мы разрабатывали с вами приложение, и теперь мы хотим наше разработанное приложение, наш проект задеплоить на удалённом сервере. И вот нам выделяют какой-то рандомный продовый сервер, и мы
Speaker A
на нём должны задеплоить свой проект. Мы свой проект на этот сервер деплоим. И потом оказывается, что почему-то ничего не работает. Вот этот вот проект у нас локально работал на нашем компьютере.
Speaker A
Всё было круто. А потом мы задеплоили этот проект на удалённый сервер, и вдруг оказывается, что ничего не работает. И мы заходим на этот удалённый сервер и начинаем смотреть. Ага, Гошка вообще устарела. То есть там Гошка версии 1.8.
Speaker A
Постгрыс тоже устаревшей версии, версии, а библиотеки Migrate в принципе не установлена на удалённом продовом сервере. И отсюда проблема, что наш проект просто не запускается, не работает, в нём куча багов, в принципе, невозможно им пользоваться. Хорошо, мы такие умные. Ага. Ну давайте я просто
Speaker A
тогда установлю библиотеку Migrate и обновлю Постгрыз и Гошку до новой версии. А теперь к нам приходят наши коллеги и говорят: "Нет, нельзя так делать". Потому что у наших коллег свои какие-то проекты. И эти проекты уже требуют, чтобы у них была именно Гошка
Speaker A
1.8, именно Постгрос версии 6, ну, на библиотеку Migration плюс-минус всё равно. То есть нам просто не дают обновить наши зависимости до актуальной версии, потому что от этих старых зависимостей зависят другие проекты. И получается такой конфликт зависимости, что мы не можем на удалённом сервере
Speaker A
получить именно те зависимости, которые нам нужны. А даже если бы мы и могли это сделать, то представьте, как это неудобно каждый раз вот заходить на этот продовый сервер и следить за тем, чтобы там зависимости были всё те же, что и у
Speaker A
нас локально на компьютере. И эта проблема начинает приобретать ещё больший масштаб. Если у нас локально на компьютере один дистрибутив Linux, например, а на продовом сервере другой дистрибутив Linux, и оказывается, что на продовый сервер библиотеку Migrate не так уж и просто поставить или что
Speaker A
обновить Гошку до более свежей версии оказывается проблематично. И тут мы начинаем попадать в такую ловушку, из которой на самом-то деле сложно выбраться. У нас постоянно будут возникать ситуации, что вот локально-то у меня всё работает, а на продовом сервере нет. И если с постгресом или с
Speaker A
версией Гошки всё довольно очевидно, на практике могут возникать моменты не настолько очевидны. Какая-нибудь там незначительная, на первый взгляд, библиотечка имеет у нас локально одну версию, а на продовом сервере другую. И из-за этого у нас ничего не работает.
Speaker A
Это будет вечной проблемой. Я сам лично с этим постоянно сталкивался до того, как узнал, что такое докеer. Но что даёт нам докер? Предположим, мы на своём локальном компьютере разрабатываем наш проект, и у нас на компьютере есть докеer. Что мы можем сделать? Вместо
Speaker A
того, чтобы запускать наш проект, зависимости нашего проекта непосредственно на нашем локальном компьютере, вместо этого мы можем запускать и наш проект, и зависимости проекта в так называемых doкерконтейнерах. Раз. Docker контейнер, два docker контейнер, три doкеer контейнер. Что это нам даёт? Это нам
Speaker A
даёт то, что независимо от того, какие зависимости на удалённом сервере, на котором мы будем деплоить наш проект, нас это не беспокоит. Нас не беспокоит, что там версия Гошки 1.8. Нас не беспокоит, что там нету вообще библиотеки Migrate, нас не беспокоит то,
Speaker A
что там постгрыз вообще ненужной нам версии. Нас это не беспокоит. Почему? Потому что у нас весь проект в докерконтейнерах. А что это значит? Это значит, что если на какой-то машине, в том числе на удалённом сервере, есть докер, установленный, это значит, что мы
Speaker A
сможем просто вот так вот взять наш проект в докер контейнерах и перенести на удалённый сервер. Всё. И у нас сразу же получится запущенный наш проект на нужной версии Гошки. У него в зависимостях всё, что ему необходимо на нужных версиях. Почему? Да, потому что
Speaker A
мы изначально разработали это всё в докерконтейнерах и потом мы смогли вот так вот взять и перенести эти самые докерконтейнеры полностью наш проект со всеми его зависимостями на удалённый сервер, на котором просто тоже установлен докер. И теперь нас уже не
Speaker A
беспокоит, что тут вдруг Постгрес не той версии или что библиотеки в принципе нету на удалённом сервере или что там гошка устаревшей версии. Нас это не беспокоит. Мы наш проект разработали в докере, мы его в докере и задеплоили.
Speaker A
Соответственно, благодаря докеру мы получили изолированность нашего проекта, изолированность наших зависимостей от той среды, где запускаются проекты зависимости. Почему это важно? Ну, мы только что с вами обсудили, потому что состояние этой внешней среды может совершенно не соответствовать тому, что
Speaker A
нам требуется для корректного запуска нашего продукта. Вот такую проблему решает Docker. На самом деле внутри каждого докерконтейнера, что докер контейнера с нашим проектом, что докер контейнера с зависимостью Postgress, что докер контейнера с зависимостью migrate, что вообще любого абсолютно
Speaker A
докерконтейнера, внутри него есть своя файловая система. Что это значит? Это значит, что если вы у себя на компьютере создадите какой-то файл, то внутри докер контейнера этого файла не будет. И наоборот, если вы внутри докер контейнера создадите какой-то файл, то
Speaker A
на вашем компьютере это никак не отразится. То есть на вашем компьютере этого файла не появится. Почему? Потому что у Docker контейнера своя файловая система. И независимо от того, какие папки и файлы вы создадите внутри докер контейнера, это не попадёт на продовый
Speaker A
сервер или же на локальную машину, если Docker контейнер запускается на локальной машине. Что это нам даёт? Это нам даёт то, что если вдруг наше приложение для себя создаёт какие-то там служебные файлы, то эти служебные файлы у нас не будут замусоривать продовый
Speaker A
сервер. Вот на продовом сервере этих файлов не будет. Они будут у нас оставаться в рамках того контейнера, в котором они были созданы. Также внутри контейнера не только своя файловая система, но ещё и своя сеть. Что это значит? Это значит, что если мы
Speaker A
запустили наш проект внутри докер контейнера и мы хотим, например, отправить запрос на local host, чтобы получить доступ, ну, к примеру, вот к базе данных, да, мы же на том же самом хосте её запустили, но нет, так не получится, потому что внутри докер
Speaker A
контейнера своя сеть. Если мы из Docker контейнера, из нашего приложения, которое находится в Docker контейнере, делаем запрос на local host, то мы попадём не на компьютер, на котором запущен Docker, а мы попадём обратно в свой же контейнер. Вот это будет запрос
Speaker A
на local host. Получается, Docker нам позволяет изолировать не только файловую систему, Docker нам позволяет изолировать ещё и сеть. Ну, соответственно, внутри докер контейнера свои системные пользователи. То есть, если у вас на вашем компьютере или же на вашем сервере были созданы какие-то
Speaker A
пользователи операционной системы, то внутри докер контейнера будут другие пользователи. Docker контейнеer не копирует вашу хостсистему, то есть Docker контейнеer не копирует то окружение, на котором он запущен. Docker контейнер - это изолированное окружение.
Speaker A
Ну, это я и написал в последнем пункте, своё окружение. Это значит, что независимо от того, какие там библиотеки у вас установлены на локальном компьютере или же на продовом сервере, независимо от того, какие там у вас версии всяких утилит инструментов,
Speaker A
независимо от того, какие у вас там пакеты были установлены, или независимо от того, какие файлы или папки были на хостсистеме, если вы запускаете Docker контейнер, то в Docker контейнере это всё будет своё. Вот как вы настроите докерконтейнер. Такие версии библиотек,
Speaker A
такие версии всяких утилит, такой вид файловой системы, такие настройки сети будут внутри этого докер конконтейнера.
Speaker A
То есть, по сути, Docker контейнер - это некоторая такая изолированная среда, которая очень похожа на первый взгляд на виртуальную машину. То есть реально с первого взгляда кажется, что мы запускаем, ну, настоящую виртуальную машину отдельную, в которой может быть
Speaker A
своя версия операционной системы. То есть у вас продовый сервер может быть один дистрибутив Линукса, а в докер контейнере может быть совершенно другой дистрибутив Линукса. Своя сеть внутри докер конконтейнера, своя файловая система действительно очень похожи на виртуальную машину. То есть у нас такая
Speaker A
сильная степень изолированности. Но важно понимать, что Docker - это не то же самое, что и виртуальная машина.
Speaker A
Почему? Потому что у них совершенно разный принцип работы. Мы сейчас не будем прямо глубоко в подкапотку залезать. Так, чисто по верхам пройдёмся, когда мы с вами запускаем виртуальную машину на своём компьютере.
Speaker A
Ну, я думаю, кто-то из вас уже это делал. То есть, буквально можно скачать Virtual Box и на вашей Windows-машине можно запустить Linux Distriбутив. Ну, то есть благодаря виртуализации. Так вот, эта самая виртуализация - это не то же самое, что запуск какого-то
Speaker A
докерконтейнера, потому что при виртуализации, при запуске полноценной виртуальной машины идёт виртуализация ядра запускаемой системы. То есть, когда вы на винде запускаете в виртуальной машине Linux, то винда виртуализирует Linux ядро. Это создаёт крайне большое количество накладных расходов просто для
Speaker A
того, чтобы запустить вот эту вот изолированную среду виртуальной машины. Do же работает по-другому. Doer не виртуализирует ядро операционной системы, которую он запускает. Docker лишь пользуется функциональностью операционной системы, на которой он запущен для того, чтобы создавать такую некоторую иллюзию полной виртуализации.
Speaker A
Но на самом-то деле нету никакой полной виртуализации. Docker лишь пользуется функциональностью ядра Linux системы, на которой он работает. То есть вот уже есть какая-то существующая Linux система, которая работает на каком-то компьютере. У этой Linux системы есть своё ядро, и Докеer пользуется уже этим
Speaker A
существующим ядром. Docker не создаёт новое виртуальное ядро и не поддерживает эту самую виртуализацию ещё одного ядра, в отличие от виртуальной машины. То есть Докеer просто пользуется уже существующим ядром той операционной системы, на которой он запущен. Docker лишь пользуется функциональностью Linux
Speaker A
ядра для того, чтобы запускать какие-то процессы в изолированной среде. Но тут не происходит виртуализации ядра запускаемой системы, благодаря чему доке работает гораздо быстрее, докер требует гораздо меньше ресурсов, чем при полноценном запуске настоящей виртуальной машины. То есть Docker - это
Speaker A
не то же самое, что виртуальная машина. Хотя с первого взгляда может быть очень похоже. Ну то есть ещё раз всё вместе.
Speaker A
Doer тем, чтобы задеплоить наш проект на удалённый сервер. Без Докера это делать довольно сложно. И тем более, чем больше компания, которая разрабатывает какой-то продукт, тем больше этой компании нужны различные сервера. У неё может быть много этих продовых серверов, их может
Speaker A
быть у неё десятки, может быть сотни. И чтобы не поддерживать на каждой из этих сотен серверов нужный версии зависимостей, нужные библиотеки, трястись за то, что там что-то не запустится, мы просто локально разрабатываем всё в докере и потом деплоем всё также в докерконтейнерах на
Speaker A
продовом сервере. Таким образом, мы создаём ещё на этапе разработки для нашего проекта, для наших зависимостей некоторую изолированную среду. И потом в этой самой изолированной среде мы переносим наш проект на удалённый сервер, благодаря чему мы снимаем огромное количество проблем, которые
Speaker A
могли бы возникнуть при деплое нашего проекта. Ну и также важно помнить, что Docker контейнеer - это изолированная среда, в которой работает какой-то процесс. Например, наш проект может там работать или же какая-то библиотека или база данных целая может работать в
Speaker A
рамках Docker контейнера. И каждый докеerконтейнер - это какая-то своя изолированная файловая система, своя изолированная сеть, свои системные пользователи, в целом своё окружение.
Speaker A
Говорю, доходит до того, что буквально у вас продовый сервер может быть на одном Linuxдистрибутиве, а внутри doкеer контейнера другой Linuxдистрибутив. Ну и когда вы попадаете в Docker контейнер, то есть в него буквально можно зайти руками, когда мы с вами попадаем в
Speaker A
Docker контейнер, это буквально выглядит как отдельный компьютер, как отдельная файловая система, как отдельная сеть, потому что это просто сильно изолированная среда. Но при этом важно помнить, что Docker - это не то же самое, что виртуальная машина. Нет, при
Speaker A
запуске виртуальной машины у нас идёт полная виртуализация ядра запускаемой системы. При запуске докер контейнера у нас лишь идёт такое некоторое создание иллюзии изолированной среды. Хотя по факту процессы в Докере, они точно также запускаются прямо на компьютере. Просто они делают это в некоторой изолированной
Speaker A
среде, но при этом не идёт виртуализация целого ядра запускаемой системы. То есть, если бы мы вот это вот всё запускали на виртуальных машинах, то нам для запуска вот этого проекта потребовалось бы запускать три виртуальные машины. раз виртуальная машина, два виртуальная машина, три
Speaker A
виртуальные машины. Это крайне дорого по ресурсам и просто не целесообразно. А запуская в Докере, мы очень мало накладных ресурсов тратим для того, чтобы создать целую отдельную изолированную среду. В этом и преимущество докера. Мы сейчас не будем спускаться в подкапотную реализацию, как
Speaker A
докер этого добился. Если вам крайне интересно, вы можете почитать об этом в интернете. Нам же сейчас важно лишь понимание, для чего вообще Докер нужен и что он из себя вообще представляет.
Speaker A
Давайте немножечко поговорим про самую такую базовую теоретическую основу докера. Докер состоит из нескольких сущностей. Работа с докером состоит из нескольких сущностей. Что это за сущности? Сперва мы с вами описываем так называемый докер файл. Это самый обычный файл, который описывается в нашем
Speaker A
проекте, который из себя представляет инструкцию для создания заготовки. Сейчас мы с вами поймём, что за заготовка. То есть мы вот описываем некоторую инструкцию. И потом, если мы пропишем в терминале Docker build, тогда эта инструкция у нас выполнится, и из
Speaker A
неё соберётся заготовка некоторая. Эта самая заготовка - это заготовка для запуска контейнера. У нас есть Docker файл, то есть инструкция, которая говорит, как создать заготовку для запуска контейнера. Мы пишем Docker Build, и у нас создаётся эта самая заготовка для запуска контейнера. Ну, её
Speaker A
официальное название - это Docker Image или же Docker образ. Вот это Docker образ, это заготовка для запуска контейнера. И потом мы можем написать Docker Run. И из этой самой заготовки у нас запустится целый Dockerконтейнер.
Speaker A
Запустится изолированная среда, в которой будут те зависимости, те версии, те файлы, те настройки сети, которые мы задали на этапе написания вот этой вот самой инструкции. Всё основывается на том, что именно мы написали в инструкции для создания заготовки. Почему идёт
Speaker A
такое разделение? По сути, это некоторого рода оптимизация. Мы можем заранее описать инструкцию для создания заготовки и создать эту самую заготовку.
Speaker A
А потом все желающие по необходимости из этой самой заготовки могут сразу запускать себе готовые докерконтейнеры.
Speaker A
И им, тем, кто хочет запустить готовый докерконтейнер из заготовки, не придётся руками прописывать вот эту вот инструкцию для создания заготовки.
Speaker A
Кто-то, кто отвечает за разработку проекта, описал инструкцию для создания заготовки, потом эту самую заготовку создал, и при необходимости все желающие могут запускать из этой заготовки сразу готовый докер контейнер. Ну, а так вот мы с вами рассмотрели три основные
Speaker A
сущности докера. Это Dockerфайл, что является, по сути, инструкцией для создания заготовки. это Docker Image или же Docker образ, что по сути является заготовкой для запуска контейнера. То есть вот это уже скомпилированная такая заготовка, из которой мы с вами можем
Speaker A
брать и запускать контейнеры, запускать изолированные среды с нашим проектом или с нашими зависимостями. Ну, например, мы в докерконтейнере можем запустить постгрыс. Но обычно весь этот путь от Docker файла до контейнера руками мы проделываем только для каких-то рукописных образов. Что такое рукописные
Speaker A
образы? Например, когда мы свой проект, именно свой написанный код хотим запустить в докер контейнере, тогда, да, нам нужно описывать свой Docker файл, из этого Docker файла строить свой Docker образ, и из этого докер образа уже запускать контейнер. Но довольно часто
Speaker A
случается так, что мы хотим запустить в докер контейнере то, что до нас запускалось уже миллионы раз, и для этого есть готовые образы. То есть нам не нужно описывать свой докер файл. Нет, готовые образы уже есть, к примеру, на
Speaker A
Докерхабе. DockerHub - это такое место, в котором собрано огромное количество готовых образов. Что это значит? Это значит, нам уже не нужно руками описывать Docker файл и строить этот самый образ. Нет, образ уже построен и хранится на докерхабе. Вот, к примеру,
Speaker A
образ с PGR SQL базой данных. То есть нам не нужно руками описывать Docker файл, чтобы создать себе образ с погрез базой данных. Мы просто можем написать docker runstgress, и докеer сам посмотрит, есть ли вообще где-то в доступе у него иджи или же докебразы,
Speaker A
которые называются постгрыз. Если он находит такой doкеer image, а он его на докеer хабе найдёт, тогда он просто его скачает, этот готовый докеer образ, и сразу же сам из него запустит готовый Docker контейнер. Это очень удобно, потому что крайне много зависимостей у
Speaker A
нас может быть. всякие базы данных, всякие библиотеки, всякие утилиты, для которых мы просто не хотим руками описывать dockerфайл, билдить потом Docker image. Нет, они уже готовы лежат на докерхабе. И Docker, когда мы пишем docker run, что-то там, он сначала
Speaker A
посмотрит, есть ли у нас локально такой собранный имидж. Если нет, пойдёт смотреть на DockerHub. Если на докерхабе найдёт, он его скачает и потом запустит из него готовый Docker контейнер. Ну, я предлагаю начать с простого. Давайте опишем небольшую программку. Это у нас
Speaker A
будет самый-самый простой HTTP сервер на 1 endpint. Запустим сначала эту программку просто через уже знакомое нам gounmain. И тогда эта самая программка у нас запустится просто на локальном компьютере. А потом мы запихнём эту нашу программку в Docker контейнер и запустим
Speaker A
уже изнутри докер контейнера и посмотрим, как это вообще происходит, как это работает. Ну, поехали. Перехожу в наш проект. Перед тем, как мы перейдём к практике, нужно проверить, что необходимые инструменты установлены на вашем компьютере. Первое, что необходимо проверить - это установлен ли докер и
Speaker A
какая у него версия. Для этого в терминале необходимо написать doкеer веersр, если выводится версия вашего докера, установленного на компьютере, в таком случае докер у вас установлен.
Speaker A
Если он не установлен, значит, необходимо его вручную установить. Также прошу обратить внимание на версию докера, которая будет использована в этом уроке. Далее необходимо проверить, установлен ли у вас Docker compos. Для этого пишем Docker пробел compos пробел.
Speaker A
Если выводится версия, это значит, что Docker compos у вас установлен. Ну и по классике, если он у вас не установлен, если пишет, что Docker compos не найден, в таком случае его необходимо самостоятельно установить. Также прошу обратить внимание на версию Докер
Speaker A
Композа, на которой мы будем работать в рамках этого урока. И последний шаг, как точно проверить, что Docker запускается у вас на компьютере - это написать в терминале Docker run hello тире world. И если вы видите сразу или через какое-то
Speaker A
время ожидания такой вывод: Hello from Docker и так далее, это значит, что Docker у вас не только установлен, но и работает корректно. А значит, мы с вами можем идти дальше. Я создам пакет, назову его HTTP сервер. В этом пакете я
Speaker A
создам файл httpserver.go, пакет HTTP сервер. И в нём одна простая функция старт http сервер. Функция возвращает ошибку. И что она делает внутри себя? Через пакет HTTP, который встроен в Goang, я вызываю функцию handle funk. И по паттерну/pink я буду
Speaker A
обрабатывать следующий endpint. Я просто в responsс writре буду писать ответную строку следующим образом. Hello from Docker. Вот так вот. Ну и, собственно, запущу сам сервер Listen and Surf на порту пускай будет 5050. И какого-то отдельного хендлера у нас сейчас с вами
Speaker A
нету. Ну, кто-то из предыдущих уроков, может быть, помнит, что функция listen and serve из пакета HTTP, она всегда возвращает ошибку. И нам нужно проверять, что это конкретно за ошибка.
Speaker A
И тут я буду смотреть, если erors is, то есть я буду проверять, что конкретно возвращаема ошибка, является ли она экземпляром сеver clost. В таком случае я должен вернуть NIL, потому что если мне из listen and serve функции пакета HTTP
Speaker A
вернулась ошибка сеver, это означает, что сервер просто был корректно завершён. А вот иначе, если возвращённая ошибка - это не y sererver clost, тогда, значит, какая-то действительно ошибка была, и я её возвращаю. Ну вот такая простая функция. Она регистрирует у нас
Speaker A
на паттерне/ping следующий endpint, который просто в responsт Docker. Всё, я запускаю этот http сервер на порту 5050 и потом отлавливаю возможные ошибки. Всё, что мне остаётся сделать - это в мейне вызвать эту функцию старт http сервер. Для частоты
Speaker A
эксперимента я удалю весь код, который тут был написан. После этого из пакета HTP сервер нашего я буду вызывать функцию старт HTTP сервер, получать ошибку возможную и проверять, что если у меня ошибка не равна. В таком случае я выведу на экран. Во время работы сервера
Speaker A
произошла ошибка и буду саму эту ошибочку выводить. Иначе выведу на экран. Сервер завершился успешно следующим образом. И вначале я хочу писать, чтобы было прямо наглядно, запуск HTTP сервера. То есть мы конкретно будем понимать, когда, наконец-таки, наша программа запустилась, и потом будем видеть, если
Speaker A
вдруг какие-то ошибки произошли. Ну и плюс давайте ещё сюда я добавлю вывод на экран, когда у нас будет вызываться endpint/pink.
Speaker A
Обработка запроса на паттерне. Всё, в принципе, вот такая минимальная простая программка у нас готова. Мы описали HTTP endpoint, мы запустили HTTP-сервер. Мы проверяем возможные ошибки, которые у нас могут быть возвращены, и мы вызываем эту функцию из мейна. В принципе, всё хорошо. Мы сейчас
Speaker A
можем просто локально у себя на компьютере запустить эту программу и проверить, как она работает. То есть мы сейчас запускаем нашу программу не в докере, а просто у нас на компьютере. Ну давайте это и сделаем. Открываю терминал, пишу main. запуск HTTP
Speaker A
сервера. Ну и теперь я могу сюда отправить какие-нибудь запросики. На самом деле можно открыть Postman и через Postman отправить эти запросы. Но когда у нас запрос не требует какой-то сложной логики, какого-то сложного тела запроса, то можно воспользоваться не постманом, а
Speaker A
утилитой консольной Курл она называется. Давайте посмотрим, как она выглядит. Я здесь нажимаю Split терминал. И в новом терминале я пишу local host двоеточи 5050/pink.
Speaker A
То есть я буквально отправляю самый простой HTTP запрос на local host на 5050 порт на pattern/pink. Отправляю. И как я вижу, что действительно я получил ответ. Этот запрос он, во-первых, дошёл до HTTP сервера. Во-вторых, я получил ответ. Ну давайте я тут в конце ещё
Speaker A
напишу магический символ такой сN. Что такое сN? Слшн - это перевод на новую строку, потому что здесь у нас нет перевода на новую строку. Я хочу, чтобы этот перевод на новую строку всё-таки был. Перезапускаю программу. Снова отправляю HTTP запрос. Всё, теперь у
Speaker A
меня есть перевод на новую строку. Таким образом, благодаря вот этой утилити можно довольно просто тестировать вот такие вот незамысловатые энд endпоинтики, просто чтобы понимать, доходит у нас вообще HTTP запрос или нет. И для этого не обязательно открывать вот этот вот большой громозкий
Speaker A
постма. Соответственно, на данный момент мы видим, что мы описали простейший HTTP сервер. Он у нас работает, он обрабатывает запросы и отдаёт HTTP ответы. О'кей, мы описали этот проектик, запустили локально на компьютере. Теперь давайте попробуем запустить всё это чудо
Speaker A
в докер контейнере. Ну, давайте вспоминать весь путь, который мы должны пройти, чтобы запустить что-то в Докерконтейнере, если это не какой-то готовый имидж, который уже существует на докерхабе, а если это какое-то наше кастомное приложение. Ну, хорошо. Первым делом мы должны описать инструкцию для
Speaker A
создания заготовки. То есть мы должны описать dockerфайл. Ну, давайте это и сделаем. Перехожу в проект, пока скрою терминал и создам в корне проекта dockerфайл.
Speaker A
Таким вот образом. Я сейчас вспомнил, что мы всё ещё в мей ветке. То есть мы не создали себе никакую ветку. Ну, это не проблема. Мы можем создать сейчас ветку и переключиться в неё, потому что вот этот вот весь наш git div, он не
Speaker A
закомичен, а поэтому он спокойно перенесётся в новую ветку, когда мы на неё переключимся. Ну давайте я создам новую ветку. Gitbench feature/docker.
Speaker A
И теперь я на неё переключусь. Git checkout feature docker. Всё. Ну и, как мы видим, мы теперь на новой ветке, но весь gitди он перенёсся на новую ветку, потому что он не был закомичен за мей веткой, поэтому всё хорошо. Так, ну
Speaker A
продолжим. То есть мы сейчас с вами описываем Docker файл для того, чтобы потом из этого Docker файла мы смогли создать заготовку для запуска контейнера и потом из этой заготовки запустить, собственно, контейнер. А контейнер этот будет запущен вместе с нашим гошным
Speaker A
приложением. То есть мы приложение наше хотим запустить в Docker контейнере. Ну хорошо. Описываем Docker файл. Первой строчкой в Docker файле идёт слово from.
Speaker A
И после этого нам нужно написать название так называемого базового имиджа. Давайте я сейчас напишу, потом расскажу. Geng двоеточие 124. Что это значит? Это значит, что на Докерхабе есть уже базовый образ Goleng 124. Это какая-то изолированная среда, в которой
Speaker A
уже установлены базовые необходимые пакеты и сама гошка версии 1.24. То есть мы на базе вот этого вот базового имиджа будем строить уже дальше свой имидж.
Speaker A
Почему это удобно? Потому что нам не нужно руками создавать базовое окружение, в которое мы будем скачивать Гошку нужной версии, все необходимые для работы голенга пакеты. Нет, у нас уже есть базовый образ, на основе которого мы будем делать свой. Мы будем делать
Speaker A
свой образ на основе готового окружения с гошкой версии 1.24. Этот базовый имидж находится на Dockerхабе, и оттуда он, собственно, и скачается. То есть, кому интересно, буквально можно загуглить DockerHhub и зайти на сайт hub.doker.com.
Speaker A
На этом сайте содержится огромное количество базовых образов, в том числе, как мы видим, для питона есть базовые образы, да, вообще практически для чего угодно. Но мы в поиске можем написать goeng и увидеть, что тут есть огромное количество различных поставщиков базовых
Speaker A
имиджей с гонгом, но нас интересует именно официальный поставщик. Вот, Docker official Images. Переходим сюда.
Speaker A
И тут мы можем увидеть огромное количество различных версий этих самых базовых иджей. Тут отличается не только версия самой Гошки, которая будет в этом базовом имидже, но отличаются ещё и постфиксы. Как мы видим, тут есть постfix Bookworm, есть postfix trixi,
Speaker A
есть postfix Alpinносервер. Каждый из этих постфиксов, он означает какую-то конкретную конфигурацию базового docker иджа. Как мы видим, даже для версии 1.24 у нас их тут огромное количество. Вообще у каждого поставщика вот эти вот постфиксы, они могут означать различные вещи. конкретного
Speaker A
официального поставщика докер образов для Гошки. Postfixbookm означает самую стабильную, самую полную версию Docker иджа, но из минусов она достаточно большая. Если мы хотим получить самый вот минимально возможный, который меньше всего места занимает базовый Docker Image с гошкой, мы можем взять версию
Speaker A
1.24 Alpin. Но нам куда-то особенно ужиматься не нужно в данный момент, никакой потребности нету. Поэтому давайте выберем версию 1.24 Bookorm.
Speaker A
Чтобы узнать, что означают эти постфиксы у конкретного поставщика Docker Imagй можно просто загуглить или же спросить у нейронки. Вот самый простой способ - это спросить у нейронки. Просто написать docker image, goleng, trixi, vs bookm, vs alpin, vs nсеer vs, Windows server
Speaker A
core и так далее. И вам буквально неронка покажет сравнительную таблицу этих всех версий. Ну вот, давайте я покажу. Я в нейронке в чат ГП пишу Docker базовый образ Goleng. И тут поехал трий VS nносервер vs Alpin, VS BookworM, ну и какие-то другие, если нас
Speaker A
интересуют. И мне сейчас Неронка просто покажет, в чём отличие этих базовых докеробразов конкретно в случае с Гошкой. Значит, - это более свежие пакеты, но это тестовая ветка. То есть XI - это ещё не стабильные пакеты. Они самые свежие, но нестабильные. Nanсервер
Speaker A
- это вообще для винды, по всей видимости, подходит и работает только с Windowsконтейнерами, несовместим с Линуксом. Ну, о'кей. Alpin Alpin использует самый маленький вообще возможный образ. То есть Alpin - это максимально мало пакетов, максимально мало утилит, но зато самый маленький
Speaker A
образ получается. Ну и, как мы видим, могут возникать некоторые проблемы с компиляцией каких-то библиотек. Просто потому что Альпин - это максимально ужатый образ, прямо очень сильно ужатый.
Speaker A
Ну и есть BookWorm, это стабильная ветка. Ну и я могу от себя добавить, что BookWorm содержит наибольшее количество всяких утилит для работы, для тестирования, для развёртывания. На самом деле, если нам не нужно специально как-то гнаться за минимальным размером
Speaker A
докероза, нам не нужно использовать Alpin, нам не нужно использовать какие-то нестабильные версии вида, просто можем взять bookm и всё, и с ним работать и проблем не знать. Ну, поэтому давайте я возьму версию конкретно 1,24 и postfix Bookworm. Перехожу обратно в
Speaker A
Visual Studio Code. И здесь я в докер файле в первой строчке пишу from Goleng 124 тире bookworm. Всё. Это значит, что я беру базовый ид конкретно версии 124 Bookworm и на нём я буду строить уже дальнейший свой Docker image. И из этого
Speaker A
Docker image я буду запускать уже своё приложение, буду запускать docker контейнеer. То есть ещё раз конкретно в первой строчке мы говорим на основе какого базового докер иджа, на основе какой базовой докер заготовки, которая уже была кем-то сделана, мы будем
Speaker A
строить свою заготовку. Для чего это нужно? Да, просто для того, чтобы нам самим не строить это базовое окружение.
Speaker A
Зачем нам тратить время на то, чтобы создать базовое окружение, в котором будет запущено гошное приложение, если это самоокружение уже готово и лежит на гигибе? Нам не нужно будет внутрь докерконтейнера своими руками устанавливать гошку. Эта гошка уже установлена в базовом doкеer Image
Speaker A
Goldeng 124 BookWorm. Хорошо, с этим разобрались. То есть мы на основе этого docker иджа, на основе этого окружения будем дальше собирать уже свой docker image. То есть мы в этой инструкции в первой строчке описали базовый идж, то есть основу для построения уже нашей
Speaker A
собственной заготовки. Мы сказали, на основе какого докер иджа мы будем строить свой doкеer image. Зачем это нужно? для того, чтобы положить в основу нашего doкер иджа тот docker imageд, где уже за нас установлена гошка, установлены все необходимые библиотеки
Speaker A
для корректного запуска гошных приложений. Хорошо, взяли основу. После этого мы указываем, в какой папке внутри докерконтейнера у нас будет располагаться наше приложение. Почему так? Да потому что я напомню, что внутри докерконтейнера у нас своя файловая система. И те файлы, которые находятся
Speaker A
внутри докерконтейнера, они не имеют никакого отношения к тем файлам, которые находятся у нас на локальном компьютере.
Speaker A
И наоборот, те файлы, которые находятся у нас на локальном компьютере, они не имеют никакого отношения к файлам, которые находятся внутри докер конконтейнера. Именно поэтому мы сейчас на этапе описания вот этой вот инструкции для создания заготовки мы указываем разметку файловой системы
Speaker A
внутри докерконтейнера, необходимую для запуска нашего приложения. Ну, обычно ничего сложного не делают. Обычно пишут ключевое слово worker. И здесь сup. Что это значит? Это значит, что внутри dockкер контейнера, вот прямо внутри докер контейнера, создастся папка сup. И в этой папке будет производиться вся
Speaker A
дальнейшая работа. Я создал папку сшup, и прямо в этой папке будет производиться дальнейшая работа. После этого я пишу copy точкаточка. Что это значит? На самом-то деле команда копи в докеer файлах. нужна для того, чтобы на этапе создания Docker и Imджа с хост системы
Speaker A
прокинуть какие-то файлы внутрь этого самого Docker иджа. А конкретно мы можем сказать, какие файлы с хостсистемы при сборке попадут в Docker контейнер. Ну, для того, чтобы внутри этого Docker контейнера расположить наш проект, потому что, повторюсь, в который раз,
Speaker A
Docker контейнер - это изолированная среда, соответственно, файловая система тоже изолированная. Соответственно, чтобы внутри докер контейнера появился код с нашим проектом, нам нужно этот код туда вручную скопировать. Для этого есть команда copy. Что она делает? Она копирует какой-то файл или какую-то
Speaker A
директорию с хостсистемы внутрь докерконтейнера. Но нам необходимо скопировать весь проект. Почему весь проект? Потому что нам каждый файл проекта нужен внутри нашего docker контейнера. Нам нужны все пакеты. Нам нужен main.goo файл. Нам нужен makeфайл, нам нужен goфайл, нам нужен точка Nфайл.
Speaker A
Все эти файлы нам нужны внутри docker контейнера, поэтому пишется следующим образом: точка точка. Это значит, что скопируй все файлы, которые находятся в текущей директории с хостсистемы, то есть все вот эти вот файлы. Скопируй их в текущую рабочую директорию внутри
Speaker A
docker контейнера. А внутри докерконтейнера у нас рабочая директория/up. Соответственно, все файлы нашего проекта с хост системы, с нашего локального компьютера скопируются в папочку с/up внутри docker контейнера.
Speaker A
О'кей. После этого файлы скопированы, и нам перед тем, как запускать наше приложение в Докерконтейнере, по-хорошему установить все необходимые зависимости. Потому что по умолчанию, когда мы просто взяли базовый образ Golden 124, он содержит только стандартные библиотеки Гошки. он не
Speaker A
содержит, например, библиотеку PGX. Библиотеку PGX, то есть какую-то стороннюю зависимость, нужно уже нам установить своими руками. Но менеджер пакета в Гошке довольно умный, поэтому нам достаточно написать runй.
Speaker A
Что это значит? Это значит, что в директоorи App, в уже скопированном проекте, мы просто в корне напишем ID, и это установит все необходимые зависимости, описанные в файле go.
Speaker A
Крайне удобно. То есть при локальной разработке мы стали зависимо, к примеру, от библиотеки PGXV5. И следующая команда на этапе сборки заготовки, на этапе сборки докер иджа запустится и установит все необходимые сторонние зависимости прямо внутрь нашего докер иджа. После
Speaker A
чего, когда мы из этого докер иджа будем запускать какой-то свой doкеer контейнер, то этот запущенный Docker контейнер уже будет содержать все необходимые установленные зависимости. И всё. И последняя команда у нас CMD - это уже сам запуск приложения. Запуск
Speaker A
приложения у нас выглядит довольно просто. Мы можем напрямую запустить gounmain.go. Ну а можем через makeфайл это сделать.
Speaker A
Make севис RAM. У нас какое-то жёлтое подчёркивание. Что это значит? Это значит, что для точности вызова команд dockerфайл требует, чтобы мы каждое отдельное слово в команде писали в виде такого массива строк. Это может выглядеть довольно странно, но вот такие
Speaker A
вот требования Docker файла. Всё, мы с вами описали готовый Docker файл. Мы с вами описали готовую инструкцию для создания Docker иджа. Вот наш Docker файл, готовая инструкция. Что в ней есть? Тут мы говорим, на основе какого базового имиджа мы будем дальше строить
Speaker A
свой имидж. Мы говорим, что мы дальше свой идж будем строить на основе базового Goleng 124 Bookworm. Тут уже есть установленная гошка, то есть нам не нужно руками устанавливать гошку и всякие сторонние утилиты для удобной работы, для удобного тестирования. То
Speaker A
есть, в принципе, хороший базовый образ. Дальше мы говорим, что внутри докер контейнера нужно создать директорию с/up и скопировать туда наш проект с локальной машины, с текущей директорией.
Speaker A
Я копирую в текущую рабочую директорию все файлы внутри докер контейнера. Соответственно, весь наш вот этот вот проект, он просто возьмёт благодаря вот этой команде и скопируется внутрь директории/up. Почему именно сшаup? Тут же стоит точка. Потому что я здесь
Speaker A
указал, что моя рабочая директория внутри doкеer контейнера - это сшup. То есть эта директория и создастся, и автоматически выберется как текущая рабочая. Соответственно, вот эта точка означает текущую рабочую директорию. Ну, соответственно, сшаup. После этого я устанавливаю все необходимые сторонние
Speaker A
зависимости, которые у меня описаны в go. Файле, потому что этот go. Motфайл вместе со всем проектом остальным скопируется внутрь doкер контейнера здесь. А здесь я просто запущу через стандартный пакетный менеджер Гошки установку всех необходимых пакетов.
Speaker A
После этого, вот на этом этапе, считайте, наш Docker Image уже полностью готов. И последняя команда будет выполняться только при запуске этого Docker иджа. То есть вот эта последняя команда у нас будет выполняться, когда мы будем писать docker run и когда мы из
Speaker A
нашей заготовки, то есть из нашего Docker иджа, будем уже запускать настоящий Docker контейнер. О'кей. Вот это наша инструкция. Вот этот наш Docker файл. Теперь мы по этому Docker файлу должны собрать себе docker image. То есть мы по этой инструкции для создания
Speaker A
заготовки должны, собственно, создать заготовку. Делается это следующим образом. Переходим в наш проект и в терминале я пишу docker build. После этого я пишу точку. Что значит точка?
Speaker A
Это значит, что Docker файл находится прямо в директории, из которой я вызываю команду Docker Build. То есть, если я напишу ls, вот он наш docker файл, то есть я сейчас нахожусь прямо в корне проекта. И в этом же корне проекта лежит
Speaker A
dockerфайл. Значит, я могу написать dockerbild точка. И тогда возьмётся именно вот этот вот doкеer файл, именно вот эта инструкция для создания заготовки. Вот эта инструкция для создания докерораза. После этого пишу пробел тирет. Что это значит? Это значит тег. То есть я могу создаваемую
Speaker A
заготовку, создаваемуй докедж как-то обозвать, чтобы просто потом его было удобнее искать. Ну давайте я это назову studp image. Всё. То есть я сейчас из той инструкции, которая находится у меня в корне этого проекта, буду билдить, то есть буду собирать docker image. И этот
Speaker A
docker image - это docker образ. Я назову HTTP image. Нажимаю Enter. у меня начинает скачиваться стандартный базовый идж Goorum, так как он ещё у меня не был до этого скачан, он скачивается, и нам нужно тут просто ждать. То есть сейчас у
Speaker A
нас идёт скачивание именно вот этого базового образа, базового окружения, на основе которого будет строиться наш Docker Image. Поздравляю, мы собрали наш первый Docker Image, и теперь, чтобы его найти, мы можем написать docker image ls. Опа. И вот он, наш Docker image. Мы
Speaker A
можем видеть, когда он был создан. Мы можем видеть его айдишник, вот его название и его размер. То есть наш Docker Image весит 1,35 ГБ. О чём это нам может говорить? О том, что иногда полезно писать вот эту вот команду
Speaker A
Docker Image LS и удалять какие-то уже ненужные Docker иджи. Но мы с вами на это чуть позже посмотрим. Хорошо, мы с вами уже описали инструкцию для создания Docker Imджа. И мы, собственно, создали Docker Imagй. И теперь мы из этого
Speaker A
docker иджа можем взять и, наконец-таки, запустить Docker контейнер. Да, не просто Docker контейнер, а Docker контейнер с нашим приложением. Ну, на данный момент наше приложение из себя представляет простенький HTTP сервер на один endpint. Ну, давайте его запустим.
Speaker A
Я пишу docker run и после этого передаю название имиджа, который я хочу запустить. То есть мы, когда из инструкции создавали конкретный docker image, мы этому конкретному Docker Imджу дали имя. И теперь по имени Docker иджа мы можем запустить конкретный Docker
Speaker A
контейнер. Ну, собственно, мы это и написали. Теперь я нажимаю Enter. Вижу вывод gounmain. А что это за вывод? Это вывод мейк-файла, потому что мы здесь не написали в начале вот эту вот собаку, которая предотвращает вывод вызываемых команд мейк-файлом и оставляет только
Speaker A
лишь вывод самой программы. Ну, ничего страшного, в принципе, нам это никак не мешает. В итоге мы с вами видим, что мы запустили наш Docker image и получили из него Docker контейнер. И в Docker контейнере уже вызвался Make Target
Speaker A
Service Run. А конкретно это произошло благодаря вот этой вот строчке. И мы запустили нашу программу. Мы это можем понять благодаря выводу запуска HTTP- сервера. Вот теперь мы запустили наш проект внутри Docker контейнера. Вот мы взяли наш проект и поместили его в
Speaker A
Docker контейнер. Теперь у нас в Докерконтейнере запущено наше приложение, и оно вроде как готово принимать HTTP запросы, но тут всё не так просто. Давайте посмотрим. То есть, по сути, может показаться, что если я сейчас в соседнем терминале напишу local
Speaker A
host двоето 55050/pink, то я спокойно сделаю запрос на вот этот вот http сервер, который работает на пятидесят50том порту, и попаду на pattern/pink и вызову вот этот вот endpint, получу ответ: Hello from Docker. Кажется, так. Но на самом-то деле, что произойдёт? У меня не
Speaker A
получится подключиться к этому серверу. Просто мой локальный компьютер не видит никаких программ, которые бы работали на пятьдесятсятом порту. Почему так? Вот же у нас наше Docker приложение запущено.
Speaker A
Запуск HTTP сервера. Он запущен на пятьдесятся порту. Почему доступа-то туда нету? Ну и тут нужно вспомнить о том, что Docker контейнер - это изолированная среда. В том числе это своя сеть. Что это значит? Это значит, что когда я на своём local компьютере
Speaker A
пишу local host, я на самом-то деле делаю запрос на самого же себя, но я не попадаю внутрь докерконтейнера. Нет, потому что это изолированная среда, у неё своя файловая система, своя сеть. И когда я со своего локального компьютера делаю HTTP запрос на local host, я делаю
Speaker A
запрос на local host в рамках сети своего локального компьютера. Но это не имеет отношения к сети внутри докер конконтейнера. Для того, чтобы наглядно на это посмотреть, давайте сейчас зайдём внутрь Docker контейнера и изнутри Docker контейнера сделаем запрос на
Speaker A
local host для того, чтобы зайти внутрь doкеer контейнера. Во-первых, я хочу посмотреть, какие, в принципе, у меня Docker контейнеры сейчас запущены. Для этого я могу написать docker ps, и я увижу, какие контейнеры у меня запущены.
Speaker A
Тут есть айдишник запущенного контейнера, есть команда, которая приводит контейнер в действие, а это у нас Make Servвиice Run. Есть информация о том, сколько контейнер уже работает, есть название заготовки или же говоря более научным языком. Есть название докер иджа, докер образа, из которого
Speaker A
был запущен данный контейнер. Ну и название контейнера, которое присваивается контейнеру случайным образом. Мы при запуске контейнера можем вручную задать ему название, но если мы этого не делаем, то это самое название присваивается случайным образом. Хорошо, как мне зайти внутрь докер контейнера?
Speaker A
Как мне зайти внутрь вот этой вот изолированной среды? Для этого я пишу docker exeg тире it. После этого название контейнера пробел bin баш. Что это значит? Это значит, что я внутри этого докерконтейнера. Конкретно у нас он сейчас одине-единственный работает. Это
Speaker A
наш dokerкеerконтейнер, который мы запускали. Я внутри этого докерконтейнера хочу запустить новую сессию эмулятора терминала баш. А IT - это значит, что я хочу сразу же подключиться к этому эмулятору терминала. Нажимаю Enter. Оп. И я зашёл внутрь докер контейнера. Как мы видим,
Speaker A
мы сейчас находимся конкретно в директории App, которую мы с вами задавали как рабочую директорию на этапе создания инструкции dockerфайл вот в этой строчке. И вот мы оказались внутри этой директории. Если я напишу ls, я здесь увижу все файлы директории нашего
Speaker A
проекта. Хорошо, я оказался внутри doкерконтейнера. И теперь, если я изнутри этого самого докер контейнера сделаю HTTP запрос на local hostl local host 5050/pink, то я достучусь до своего HTTP сервера. Почему? Потому что когда я внутри doкер контейнера делаю HTTP
Speaker A
запрос на local host, то я уже делаю запрос в рамках сети Docker контейнера. Значит, я попаду на HTTP сервер, который работает в рамках этой сети. Ну хорошо, это всё замечательно. То есть получается, что мне каждый раз для того,
Speaker A
чтобы как-то провзаимодействовать с приложением, которое находится внутри докера контейнера, мне нужно вручную внутрь этого самого Docker контейнера заходить. На самом деле нет. Тут есть несколько различных подходов для того, чтобы взаимодействовать с сетевыми приложениями внутри докерконтейнера.
Speaker A
Значит, подход первый. Мы берём пятьдесятся порт внутри докерконтейнера, на котором работает наше приложение внутри, собственно, докерконтейнера. И этот порт изнутри докерконтейнера мы пробрасываем на локальную машину. Таким образом, 50 порт у нас на локальной машине будет связан с пятьдесятся портом
Speaker A
внутри докерконтейнера. И таким образом, когда мы с нашей локальной машины будем делать запросы на local host50 порт, мы будем попадать, собственно, на local host пятьдесят50 порт. А этот порт, он связан с пятьдесятся портом внутри докерконтейнера. Таким образом, мы
Speaker A
сможем с нашей локальной машины делать напрямую HTTP запросы внутрь нашего приложения, которое находится в докер контейнере. Давайте посмотрим, как это выглядит. Перехожу обратно в V-cд, пишу exit, чтобы выйти изнутри докер контейнера. Всё, я попадаю обратно себе на локальную машину. Опять пишу Docker
Speaker A
PS. И я хочу вот этот вот контейнер взять и остановить. Почему? Потому что мне нужно запустить новый контейнер, у которого уже пятьдесят псятый порт будет связан с пятьдесят псятым портом на локальной машине. Для этого я могу написать следующую команду: Docker kill,
Speaker A
то есть я хочу убить какой-то контейнер. Конкретно я передаю имя контейнера, который я хочу убить. Нажимаю Enter.
Speaker A
Всё, контейнер убит. Для этого я могу написать ещё раз docker PS и увидеть, что действительно никаких контейнеров нету. Но если я напишу dockкеer psa, а, тогда я увижу, что этот контейнер он всё-таки ещё существует на моей локальной машине, но он остановлен. Ну,
Speaker A
хорошо, пускай пока будет, пока он нам не мешает. Ну, остановленный, остановленный. Хорошо, мы просто запустим новый. Для того, чтобы запустить Docker контейнер с пробросом портов, нужно написать следующим образом. Я напишу Docker Image LS. Вот он наш единственный docker image. И пишу
Speaker A
docker run тире p 5050 двоеточие 5050. После этого название docker иджа. Что это значит? Это значит, что я хочу через docker запустить image stud http image, но при этом в получившемся докерконтейнере я хочу сделать проброс пятьдесят псятого порта на пятьдесятся
Speaker A
порт на моей локальной машине. Таким образом, пятьдесятсяй порт внутри докерконтейнера и пятьдесятсяй порт на моей локальной машине будут связаны. Это то, что нам нужно. Нажимаю Enter. Всё, у нас из уже ранее созданной заготовки запустился Docker контейнер. То есть мы
Speaker A
взяли заготовку, которую мы описали ранее, и из неё уже снова запустили новый докерконтейнер. О чём это нам может говорить? Это говорит о том, что если мы даже в проекте у себя что-нибудь поменяем, например, я тут ещё один вывод
Speaker A
добавлю, новый вывод, например. После этого захочу перезапустить docker контейнер запущенный. Для этого я копирую название контейнера, пишу Docker kill. Всё, убил этот контейнер. Тут он тоже, видите, остановился. И я заново хочу перезапустить этот контейнер из уже существующего иджа, то я не увижу
Speaker A
никакого нового вывода. Почему? Потому что я запускаю контейнер из уже существующего иджа, из уже существующей заготовки. И все изменения в проекте, они не будут видны в итоговом докерконтейнере. Почему? Потому что мы запускаем этот контейнер из уже существующей заготовки. Если мы хотим
Speaker A
увидеть какие-то изменения, нам нужно пересобирать эту заготовку. Ну, в принципе, давайте это и сделаем. Тут вот я в мейк-файле добавлю собаку, чтобы не было вывода вызываемой команды. Снова мне нужно убить работающий контейнер.
Speaker A
Docker kill. Docker ps. Действительно, нету сейчас запущенных контейнеров. Docker image ls. Вот они наши все имиджи. Сейчас этот имидж у нас один.
Speaker A
Давайте создадим новый. Я пишу dockerbild тоt study http image 2. Нажимаю enter, и у меня собирается новый идж, новая заготовка на основе уже новой версии нашего проекта. Готово. Теперь пишу Docker Image. LS. И я вижу два разных имиджа. Первый и второй, две разные
Speaker A
заготовки. И в зависимости от того, какую заготовку мы запустим, такой doкеer контейнер у нас и запустится. Ну, давайте я запущу новую заготовку. Docker run сразу с пробросом порта и название этого имиджа. Всё. Во-первых, у нас пропал теперь вывод вызываемой
Speaker A
команды в мейк-файле и добавился вывод, новый вывод. Почему? Потому что мы после изменений в нашем проекте мы пересобрали новый doкеer Imagй. То есть мы пересобрали новую заготовку и запустили уже новую заготовку. Если я запущу старую заготовку, там всё будет
Speaker A
по-старому. Ну давайте я прямо в этом терминале нажму Ctrl C несколько раз. Всё, я прекращаю интерактивную сессию с запущенным контейнером, но сам контейнер продолжает работать, поэтому его нужно убить. Docker killill. Название контейнера. Всё, давайте запустим старую заготовку. Docker Image ls docker run. И
Speaker A
вот она старая заготовка. Ну пока не буду пробрасывать порты, потому что нету в этом смысла. Как мы видим, во-первых, есть вывод из самого йк-файла, и нету вывода новый вывод. Почему? Потому что мы запустили старую заготовку, а в старой заготовке у нас лежит старая
Speaker A
версия нашего проекта. Именно поэтому после каких-то изменений в проекте важно помнить, что перед запуском Docker контейнера нужно пересобирать Docker образ для того, чтобы не столкнуться с проблемой, что вроде как я у себя в проекте всё меняю, а при запуске
Speaker A
Doкероза у меня старая версия проекта. Ну, потому что вы запускаете старый Docker образ. Вы запускаете старую заготовку. Нужно создать сначала новый Docker образ и уже из нового Doкероза новый Docker контейнер. Ну хорошо.
Speaker A
Останавливаю интерактивную сессию с этим Docker контейнером и убиваю его. Docker kill. Название контейнера. Всё хорошо. А теперь давайте, наконец-таки, запустим новую версию нашей заготовки и уже пробросим порты, чтобы наконец-таки увидеть, как проброс портов влияет на связывание сети локального компьютера и
Speaker A
docker контейнера. Docker Image LS. Вот она, наша Docker Image версия 2. Docker run. После этого пробрасываю пятьдесятся порт изнутри doкеer контейнера на пятьдесятся порт нашей локальной машине.
Speaker A
После этого название имиджа пишу, из которого я буду запускать docker контейнер, и нажимаю Enter. Всё, у меня запустился HTTP сервер. И теперь, если я буду писать local host 5050/pink, то я буду попадать этим запросом внутрь Docker контейнера. Почему? Потому что
Speaker A
при запуске Dockerконтейнера я явно указал, что хочу пятьдесятсятый порт изнутри Dockerконтейнера пробросить на пятьдесятся порт на локальной машине.
Speaker A
Соответственно, я беру этот пятьдесятся порт, пробрасываю на локальную машину, и между ними устанавливается связь. После чего, когда я пишу local host на своём локальном компьютере, я делаю запрос на local host на пятьдесятся порт. И я, попав на пятьдесятся порт на локальном
Speaker A
компьютере, автоматически попадаю на пятьдесят псятый порт внутри докер контейнера, потому что они связаны. Вот это первый способ, как нам получить сетевой доступ к приложению, которое работает внутри докер конконтейнера. Ну давайте ещё парочку запросов кинем и увидим, что действительно мы попадаем
Speaker A
внутрь докер контейнера. Но на самом деле может быть не всегда удобно вот так вот блокировать терминал и переключать его на сессию внутри докерконтейнера. То есть я вот, например, здесь не хочу блокироваться на этой сессии. Как не блокироваться при запуске? Ну я просто в
Speaker A
этой же команде вначале добавлю ещё тире D. Что это значит? Это значит detched. Что значит detched? Это значит не подключаться к запускаемой внутри докера сессии. Что это нам даст? Ну, во-первых, нужно убить уже запущенный контейнер.
Speaker A
Вот его название. Docker kill. И теперь я могу запустить новый контейнер в detched моде. Запускаю. Оп. И я вижу, что у меня был запущен новый контейнер.
Speaker A
Но при этом я могу дальше продолжать пользоваться тем же самым терминалом у себя на локальном компьютере. Но при этом, если я пишу Docker PS, вот он наш запущенный 10 секунд назад новый Docker контейнер. Это на самом деле более
Speaker A
удобно. Давайте я ещё отправлю парочку HTTP запросов. 1 2 Мы всегда получаем ответ. И если я хочу увидеть вывод, который у меня был в докер контейнере, я могу скопировать название контейнера и написать docker log и вставить название контейнера. Нажимаю Enter, и я вижу
Speaker A
вывод, который был внутри этого докер контейнера. Тоже достаточно удобно. Вот если я здесь ещё пару запросов проведу, то, соответственно, эти два запроса, вывод Docker контейнера об этих двух запросах у меня также появится. Ну, также нам никто не мешает пятьдесятся
Speaker A
порт внутри докерконтейнера пробросить не на пятьдесятсяй порт на нашей локальной машине. То есть мы можем пробросить ну например на пятьдесят0тый порт. И тогда на local host мы будем делать запрос на пятьдесятшедесятый порт. И этот порт будет связан с пятьдесятся портом внутри
Speaker A
докер контейнера. Ну, давайте быстренько я вам покажу. Ну, для начала давайте остановим docker контейнер, который у нас уже запущен. Docker kill.
Speaker A
Останавливаю его. Пишу docker image ls, чтобы посмотреть все имиджи, которые есть. Вот он наш актуальный идж. И тут я пишу docker run. Запускаю в detched моде и проброс портов, как говорится. Следите за руками 5060 двоеточие 5050. То есть
Speaker A
справа у нас указывается порт, который будет замаплен внутри докер контейнера, а слева у нас указывается порт, который будет замаплен на нашем локальном компьютере. Ну и после этого я пишу название иджа, из которого я хочу запустить docker контейнер. Всё,
Speaker A
запустили наш Docker контейнер в detched моде. Пишу Docker PS. Как мы видим, когда мы пишем Docker PS, у нас показывается ещё и возможный проброс портов между хост системой и докер контейнером. Тут мы наглядно видим, что у нас пробросился порт 5060 на локальной
Speaker A
машине на порт 5050 внутри докер контейнера. И теперь, если я буду писать local host 5050 ping, я не буду никуда попадать. Почему? Потому что у меня на локальной машине на порту 5050 ничего нету. Но если я буду делать запрос на
Speaker A
пятьдесят0тый порт, то я попаду внутрь докерконтейнера. Почему? Да потому что на локальной машине у меня смаплен порт 5060, и я делаю запрос на него, и он уже мапится на порт 5050 внутри докер конконтейнера. Ну, собственно, вот так вот выглядит маппинг портов между докер
Speaker A
конконтейнером и локальной машиной. И это первый способ, как мы можем достучаться до сетевых приложений, которые запущены внутри докерконтейнера.
Speaker A
Ну и, казалось бы, всё классно. Мы теперь изнутри докерконтейнера можем пробрасывать порты на локальную машину и таким образом поддерживать сетевое взаимодействие между локальной машиной и чем-то внутри докерконтейнера. Но тут, на самом деле, может быть большое количество проблем. Какие могут быть
Speaker A
проблемы? Предположим, что у нас в докерконтейнере запущено не только наше приложение, но ещё и база данных Постгрос. О'кей. И как нам изнутри докер контейнера с нашей программой получить доступ сетевой внутрь докерконтейнера с постгресом? Ну, наверное, первое, что могло прийти в голову - это же Posgс. Он
Speaker A
у нас на 5432 порту работает. Мы можем этот 5432 порт просто пробросить изнутри контейнера на локальную машину. Да, мы пробросили, но мы пробросили этот 5432 порт на локальную машину. Мы его не пробросили внутрь докерконтейнера с нашей программой. То есть наша программа
Speaker A
всё ещё не знает ни о каком 5432 порту. В изолированном окружении нашей программы 5432 порт никем не занят, на нём ничего нету. И вот тут начинаются проблемы, потому что сделать сетевой запрос с локального компьютера на замапленный порт внутрь докер
Speaker A
контейнера, это можно сделать довольно просто. А вот сделать сетевой запрос изнутри докер контейнера на локальную машину уже сложнее, и это далеко не всегда в принципе возможно сделать. Так как же нам тогда изнутри докер контейнера с нашим приложением получить
Speaker A
доступ к базе данных постгрес, которая находится в другом докерконтейнере? Вот как нам это сделать? А тут обычно поступают более хитро. Поступают следующим образом. Все запущенные докерконтейнеры, которые относятся к одному проекту, их объединяют в одну докер сеть. Что это даёт? Это даёт то,
Speaker A
что все эти докерконтейнеры существуют в рамках одной сети. А это даёт то, что они спокойно могут получать сетевой доступ друг к другу напрямую, без необходимости пробрасывать какие-то там порты на локальный компьютер. Плюс это даёт большую изолированность, то, что
Speaker A
эти контейнеры, они существуют в одной докер сети. Они между собой по сети могут общаться, но при этом с локального компьютера туда нету прямого доступа.
Speaker A
Иногда это бывает очень полезно, но конкретно сейчас нас интересует то, как объединить вот эти несколько контейнеров в одну докер сеть. И тут нам на помощь приходит Docker Compos. Что такое Docker compos? Docker Compos - это инструмент для удобного управления
Speaker A
Dockerконтейнерами. Благодаря докеркомпозу мы можем очень удобно управлять всеми контейнерами, которые относятся к нашему проекту. И как раз-таки этот Docker compos может автоматически создавать общую докер сеть для всех этих докер-контейнеров. И нам не нужно как-то специально запариваться с тем, чтобы вручную создавать эту сеть,
Speaker A
вручную связывать все эти контейнеры в одну докер сеть. Нет, Docker Компос это сделает за нас. И в дальнейшем большинство работы с докером и с докерконтейнерами мы с вами будем производить именно через Docker Compos, потому что это просто удобнее и более
Speaker A
наглядно. Ну, давайте сейчас рассмотрим самый базовый примерчик работы с Docker контейнером через Docker compos, а потом уже полноценную работу с Dockркомпозом, когда мы в Докеркомпозе будем запускать сразу целый проект из нескольких контейнеров. Мы с вами посмотрим в конце
Speaker A
этого видео, когда уже будем практиковать все полученные знания. И там-то мы и запихнём весь наш проект в Docker Compos. Ну а сейчас давайте посмотрим небольшой базовый примерчик.
Speaker A
Переходим в наш проект. И здесь в корне нашего проекта я создаю dockercos.yyaml. Так вот он выглядит. В первой строчке этого файла пишется ключевое слово севиes, двоеточие. И потом на новой строке описываются все необходимые сервисы, которые существуют в рамках
Speaker A
нашего проекта. Ну, сейчас у нас будет один-единственный сервис. Это наше приложение. Назову этот сервис application. И здесь я опишу правила для запуска этого приложения в докере.
Speaker A
Первым делом я пишу build. Что это значит? Тут я должен передать путь до do до do докеer файла, который описывает этот севис application. Docker файл, который описывает севиice Appliccation, у нас лежит в корне проекта. Вот он.
Speaker A
Поэтому я просто могу написать точку. Далее давайте я задам имя для контейнера, в котором будет запускаться моё приложение. container name двоето application container и в принципе этого уже хватит для того чтобы запустить наше приложение через Docker Compos. Я
Speaker A
перехожу в терминал. Давайте проверю, какие контейнеры у меня уже запущены. Я их пока остановлю.
Speaker A
Docker PS. Всё, нету никаких контейнеров запущенных. Хорошо. Теперь я пишу Docker compos up. И через пробел я пишу название сервиса, который я хочу запустить. Я хочу запустить севис Application. Нажимаю Enter, и у меня он заново начинает билдиться, потому что
Speaker A
Docker Compos билдит свои Docker иджи, с которыми он будет работать в рамках этого проекта. Всё, у меня запустился мой HTTP-сервер в рамках Docker контейнера через Docker Compos. Но, как мы видим, мы опять приклеились к терминальной сессии внутри этого Docker
Speaker A
контейнера. Мне это не нравится. Я нажимаю Ctrl C. Останавливается контейнер, запущенный через Docker Compos. После того, как я нажал Ctrl C, контейнер остановился. И теперь, как мы видим, если я напишу Docker PS, у меня тут не будет никаких контейнеров.
Speaker A
Почему? Потому что Docker Compos сам следит за тем, чтобы убить контейнер, который мы больше не используем. Теперь я давайте опять запущу этот севис Appliccation, то есть наше приложение через Docker Compost, но уже в detched виде. Делается точно так же, как и при
Speaker A
запуске, через обычный docker. Docker compost appliccation, но перед названием сервиса пишу тире D, что значит detched mode. Нажимаю Enter.
Speaker A
Оп, всё, у меня запустился мой сервис просто и легко. Я могу написать Docker PS. И вот он, мой работающий сервис.
Speaker A
Также я могу его спокойно остановить через Docker compos. Пишу docker compos d и название сервиса application.
Speaker A
Остановка идёт этого самого контейнера. Всё, остановился контейнер. И, как мы видим, ещё какая-то стадия default networк удалена. Это как раз-таки та самая автоматически созданная Docker сеть, которая создаётся docker композом.
Speaker A
Ну, я могу написать docker PS, и я тут увижу, что нет никаких запущенных контейнеров. Хорошо, обратно давайте запустим наше приложение. Docker Compost Application. Вроде приложение запущено.
Speaker A
DockerPS пишу, приложение запущено. Но если сделаю запрос на local host 5050/ping, то опять нету никакого ответа. На 5060 тоже, если сделаю, тоже нет никакого ответа. Почему? Да потому что мы, когда через Docker compos описали наш сервис, вот описание севис
Speaker A
Application, мы здесь не указали никакого мапинга портов. При желании и при необходимости мы точно также можем замапить порты через Docker Compos.
Speaker A
Делается это следующим образом. Пишется секция поs двоеточия. И здесь я пишу маппинг портов. Ну давайте для интереса вот 5070 двоеточие 5050. Всё. Вот так вот выглядит наш dockerc compost.yjamфайл dockerps. Запущенный вижу контейнер. Ну давайте его остановим. Docker compos d
Speaker A
application. Всё, наш сервис остановился. И давайте заново запустим наш сервис. Но теперь уже будет проброшен порт, потому что это указано в docker compos yaml файле. Пишу docker compost application.
Speaker A
Всё, запущен наш сервис DockerPs. Вот он наш сервис. И вот мы видим, какие порты были замаплены и проброшены. Теперь я на этот порт 5070, который у нас на локальной машине, могу сделать HTTP запрос. Lo local host 5070/pink.
Speaker A
Тадам! Наш запрос доходит внутрь докерконтейнера, запущенного через Docker Compos. Отныне и впредь мы будем пользоваться докером преимущественно через docker compost, потому что это просто удобней и наглядней. Гораздо нагляднее вот так зайти в файл dockercost.yaml и увидеть описание сервиса, какие порты у него
Speaker A
пробрасываются и какой doкеer файл используется. Чем руками выписывать докер image ls, чтобы смотреть, какие же там имиджи у нас есть, выбирать руками нужный, потом писать докер, runн, пробрасывать порты, не забывать, конечно же, название имиджа, не забывать указать detched mode. Ну, как вы видите, слишком
Speaker A
много всего нужно помнить и знать. И вместо этого просто я могу написать docker compos updcation.
Speaker A
Всё. А могу ещё себе упростить задачу и вынести вот эту команду в уже знакомый нам makeфайл. Servвис run у нас есть. А есть ещё севис deпloy. Что такое севиice deploy? Это запуск нашего сервиса внутри docker контейнера. И тут обычная команда
Speaker A
Docker Compost Application. Ну и давайте добавим ещё сервис Undeploy. Это у нас Docker Compost down Application. Всё.
Speaker A
Теперь я могу писать docker PS, видеть какие у меня контейнеры запущены. Ну вот пишу Docker PS, вижу, что у меня запущен этот сервис, пишу make севис undeploy.
Speaker A
Всё, у меня останавливается сервис, запущенный в Docker контейнере. Остановился, и теперь я могу обратно написать Make Servвиice deпло и запустился обратно. И как мы видим, работа с докерконтейнерами, работа с сервисами, которые запущены внутри докера, сильно упрощается. Когда мы
Speaker A
используем docker compostфайл в связке с йк-файлом у нас буквально всё упрощается до одного makeфайл таргета. И этот makeфай таргет уже будет вызывать docker composт запускать или же останавливать описанный сервис со всеми описанными правилами. И это гораздо удобнее, чем
Speaker A
писать do run. D P 5050. Потом ещё вспоминать название docker иджа, которое нужно запустить. Это неудобно. Гораздо удобнее написать Make Service Deploy или make service und у нас либо задеплоится наш сервис, либо наоборот остановится. Теперь давайте рассмотрим такое понятие, как Docker
Speaker A
Volume. Что вообще такое Docker Volum? Ну, мы помним, что мы, когда обсуждали с вами контейнеры, мы говорили, что каждый контейнер - это некая изолированная среда. И у этой изолированной среды своя файловая система. Что это значит? Это значит, что если, к примеру, ваше
Speaker A
приложение во время своей работы внутри контейнера создаст какой-то файл, то этот самый файл не попадёт на хостсистему. Этот самый файл так и останется внутри контейнера. И это довольно часто является плюсом, потому что создаваемые вашим приложением или какими-то сторонними приложениями файлы
Speaker A
не загрязняют наш локальный компьютер и просто остаются внутри докерконтейнера. И когда мы с вами docker контейнер прибиваем, эти самые созданные файлы просто исчезают. Но бывают случаи, когда это наоборот плохо. Какой это может быть случай? К примеру, у нас вот в Docker
Speaker A
контейнере работает Postgress, база данных, которая сохраняет все данные, которые мы в неё положили, внутри каких-то файлов на файловой системе.
Speaker A
Соответственно Postгress когда работает внутри контейнера, для того чтобы сохранять данные, которые мы в неё кладём, это самая постгрыз создаёт какие-то файлы там в файловой системе, каким-то образом эти файлы наполняет. Ну и существует постгрез внутри контейнера.
Speaker A
Все эти файлы тоже существуют внутри контейнера. Казалось бы, всё классно. Но что произойдёт, если каким-то образом у нас докерконтейнер с постгресом выключится? Ну, по множеству причин это может быть. Даже самое банальное перезагрузка компьютера или перезагрузка сервера. Это приведёт к тому, что
Speaker A
докерконтейнер с постгресом просто перестанет существовать. Мы, конечно, можем потом поднять новый Dockerконтейнер, но в новом Dockerконтейнере уже не будет этих файлов, потому что эти созданные файлы, вся сохранённая информация, которую мы клали в базу данных, она осталась в предыдущем контейнере, который у нас
Speaker A
исчез после перезагрузки компьютера. Ну или ещё множество причин может быть, почему у нас докер контейнер может перестать существовать. Мы его можем сами руками выключить, мы можем попытаться обновиться на новую версию постгреса и так далее, и так далее, и
Speaker A
так далее. То есть миллион сценариев, почему у нас докер конконтейнер с Постгреса может перестать существовать.
Speaker A
А это значит, что и все файлы, и все данные, которые были сохранены постгресом, они просто вместе с контейнером исчезнут, потому что у каждого контейнера своя файловая система. И теперь уже нам это свойство контейнера, что у него своя файловая
Speaker A
система, начинает нам только мешать. Мы вроде с вами подняли себе в докер конконтейнере базу данных для того, чтобы надёжно сохранять в ней информацию. Но по факту у нас эта информация опять сохраняется ненадёжно, потому что эти файлы создаются внутри
Speaker A
докер контейнера. Эти файлы, в которых сохраняется вся информация, которую мы кладём в постгрес. А если эти файлы существуют только внутри докер контейнера, то вместе с тем, когда Docker контейнер перестаёт существовать, перестают существовать и файлы, которые этот Docker контейнер создал.
Speaker A
Соответственно, вся инфа, которую мы в базу данных положили, она тоже просто исчезает. Это проблема. Каким-то образом хочется эту проблему решить. Проблема решается как раз-таки при помощи вот этого вот docker Volume. Что же такое Volum? Volume - это такой механизм
Speaker A
докера, который нам позволяет связывать произвольные папки или же файлы внутри докер контейнера на хостсистему.
Speaker A
Работает это примерно так же, как и проброс портов. Вот мы с вами порты пробрасывали для доступа к нашему гошному приложению внутри докер конконтейнера. Точно также мы можем пробрасывать какие-то директории или файлы. Как это работает? Буквально идёт связка выбранных файлов или директорий
Speaker A
внутри докер конконтейнера. с теми же файлами и директориями на хостсистеме. Соответственно, если мы завели вот этот вот так называемый Docker Volume и связали какую-то определённую папку внутри докер контейнера с какой-то определённой папкой у нас на хостсистеме, в таком случае, если эта
Speaker A
папка внутри докер конконтейнера будет наполняться какими-то файлами, то соответствующая ей папка на хост системе будет автоматически заполняться теми же самыми файлами. То есть это по сути вообще одна и та же папка. Мы буквально мапим какую-то директорию внутри докер
Speaker A
контейнера с какой-то директорией на хостсистеме. И это, по сути, становится одной директорией. И работа происходит так же, будто бы это реально одна директория. То есть это работает с другой стороны, даже если мы с хоста, с хостсистемы в эту самую смапленную папку
Speaker A
положим какие-то файлы, эти файлы автоматически появятся в этой папке внутри докер контейнера. И как раз-таки благодаря этому механизму, благодаря этому механизму Docker Volume, мы с вами можем пробрасывать какие-то папки изнутри докер контейнера на хостсистему.
Speaker A
К чему это приводит? Ну, допустим, Постгрыс сохранял все свои данные вот в эту папку. И мы эту папку пробросили на hostсистему. То есть мы воспользовались механизмом Docker Volume. Мы вот эту вот папку, в которой Постгрес сохраняет свои данные внутри Docker контейнера, мы её
Speaker A
смапили на какую-то папку, которая находится у нас на хостсистеме. После этого Постгресс во время работы сохранял какие-то файлы в эту папку, и эти файлы сразу же оставались у нас на хостсистеме в смапленной директории. После этого, если вдруг по каким-то причинам
Speaker A
докерконтейнер с постгрессом перестанет существовать, то файлы, которые Постгрес сохранял внутри своей папки, внутри докер контейнера, они у нас останутся в папке на хостсистеме, потому что мы эту самую папку пробросили изнутри докер контейнера. И что это нам даёт? Это нам
Speaker A
даёт то, что мы не теряем данные, если у нас вдруг докер контейнер перестаёт существовать. Потом у нас, к примеру, перезагрузился компьютер или ещё что-нибудь. Мы запускаем заново докер контейнер с постгресом, и он сразу же видит все те файлы, которые предыдущий
Speaker A
Docker контейнер с постгресом успел сохранить. То есть пока предыдущий Docker контейнер с Постгресом работал, он наполнял своими данными вот эту директорию. Соответственно, у нас смапленная директория на хостсистеме тоже наполнялась данными. Потом этот предыдущий докерконтейнер у нас завершился, и мы когда новый подняли, то
Speaker A
так как эта директория это считайте как одна директория, она просто смаплена. Смаплена от английского слова map, то есть сопоставлять. Так как эта директория у нас смаплена, то когда новый докер контейнер поднялся, то в него уже в эту директорию попали все те
Speaker A
файлы, что ранее у нас остались на хосте. И как вывод, благодаря механизму Docker Volume, мы с вами смогли сохранить важные файлы, важные папки, создаваемые внутри докер контейнера, себе на хостсистему. Что это нам дало?
Speaker A
Это нам дало то, что эти самые важные файлы, важные папки, они могут существовать даже тогда, когда докерконтейнер прекращает своё выполнение. А это нам крайне важно, опять же, если посмотреть на пример с базой данных. То есть какой смысл от
Speaker A
базы данных? Если в случае, когда докерконтейнер перестаёт существовать, мы теряем все данные, но это крайне ненадёжно. А вот если мы при помощи механизма Docker Volumм эти самые данные пробросим себе на хостсистему, в таком случае это повышает надёжность хранения
Speaker A
данных в нашем приложении. Ну и отсюда все вытекающие плюсы. О'кей, в теории мы с вами поговорили, что такое Docker Volлюum. Давайте посмотрим простенький пример на практике. Ну вот, я возвращаюсь к нам в проект. И тут давайте будем делать следующее. Мы с
Speaker A
вами напишем какое-то простенькое приложение, которое будет просто создавать какой-нибудь один файл. После этого мы попробуем запустить это приложение внутри Docker контейнера. И при помощи механизма Docker Volume, создаваемый нашим приложением внутри doкеer контейнера файл, мы попробуем получить нам на хостсистему. О'кей. То
Speaker A
есть нам сейчас нужно создать просто какую-то программку, которая будет создавать рядом с собой какой-нибудь файлик. Ну давайте я сделаю следующим образом. Я у нас прямо в проекте создам директорию, назову её out. И в этой самой директории наша гошная программка
Speaker A
будет создавать свои файлы. Ну, поехали. Вот этот вот весь код я пока удалю. И всё, что я тут буду делать - это из пакета OS вызывать функцию create. Эта самая функция создаст мне файл с переданным именем. Ну, я буду создавать
Speaker A
файл в директории out/ и назову этот самый файл new file.txt. Всё, функция create нам возвращает созданный файл, но нам это не нужно, просто будем получать ошибку. И если ошибка не равна нил, тогда просто панику кину. Ну, в принципе, и всё. Давайте сейчас просто
Speaker A
локально на нашем компьютере запустим эту гошную программу, посмотрим, что она делает. Вот я пишу.
Speaker A
И я вижу, что в директории out создался файл NewФай. Вот он. Могу его удалить.
Speaker A
Вот, видите, его здесь нету. Ещё раз запущу нашу гошную программу, и он тут появился. Снова. Хорошо, мы с вами написали гошное приложение, которое умеет создавать какой-то файлик. Давайте теперь засунем это самое приложение внутрь Docker контейнера. Посмотрим на наш Docker файл. В принципе, этот Docker
Speaker A
файл нас полностью устраивает. Он подойдёт нам даже под это наше самое простенькое приложение, потому что мы на основе докероза, в котором уже установлена Гошка. Создадим себе рабочую директорию, скопируем туда проект, установим необходимые зависимости и после этого запустим наше приложение. Ну
Speaker A
хорошо. То есть нам нужно новую версию нашего приложения запустить в Docker контейнере. Для этого нам нужно создать новый Docker Image, в котором будет вот это вот наша новое гошное приложение. Ну хорошо, давайте я сейчас без помощи Docker compмпоза всё просто сделаю на
Speaker A
обычном докере. Я пишу Docker build точка. Это значит я буду использовать вот этот вот docker файл dockerbild тотег. И тег я хочу задать volume test.
Speaker A
Вот так вот будет называться созданный docker образ. Нажимаю Enter и жду, пока у меня создастся этот самый Docker образ. Docker образ создан. Теперь мы из этого Docker образа, из этой созданной заготовки можем запустить docker контейнер. Ну давайте пока для частоты
Speaker A
эксперимента я здесь удалю вот этот вот файл NewФай. Всё, у нас директория out на host системе пустая. О'кей, давайте запустим наше приложение в Docker контейнере. Ну, схематично это выглядит так, что мы запускаем наше приложение внутри docker контейнера. Давайте это
Speaker A
сделаем. Для начала я напишу Docker image ls. Вот все Docker иджи, которые у меня сейчас локально на компьютере присутствуют. И вот он наш docker image, который мы создали минуту назад.
Speaker A
Volumeest. Ну давайте его я и запущу. Пишу docker run. Нажимаю enter. Gounmain. Всё. То есть наше приложение было запущено внутри docker контейнера. Оно там внутри Docker контейнера создало себе файл и завершилось. Вместе с завершением приложения у нас завершился и наш самый
Speaker A
Docker контейнер. Вот я пишу Docker PS. Нету здесь никакого рабочего контейнера, но, правда, ради могу написать Docker PS - A, и тогда я увижу все Docker контейнеры, которые даже были завершены.
Speaker A
Ну, и вот мы видим, что 25 секунд назад завершился наш Docker контейнер, который мы запустили из имиджа Volume Test. К чему привёл запуск этого docker контейнера? Ну, на самом-то деле, ни к чему, потому что в Directory Out у нас
Speaker A
никаких файлов не появилось. Почему? Да, потому что у Docker контейнера своя файловая система. Если какое-то приложение внутри Docker контейнера создало себе какой-то файл, то этот файл остаётся внутри Docker контейнера до тех пор, пока мы не пробросим Docker Volume,
Speaker A
пока мы не воспользуемся инструментом Docker Volume. Ну давайте воспользуемся этим самым инструментом. Как это сделать? Воспользоваться инструментом Docker Volume можно несколькими разными способами, но я воспользуюсь, наверное, одним из самых таких прямолинейных.
Speaker A
Давайте посмотрим, как он выглядит. Как же нам запустить вот этот вот наш Docker контейнер с использованием Docker волюма? Выглядит это следующим образом.
Speaker A
Для начала пишем точно такую же команду запуска контейнера из докераза, которую мы с вами уже писали. Docker, run, volume, тест. Но перед названием иджа, перед названием doкеer образа пишем тире тире вом пробел. И теперь нам нужно указать, какую директорию на хост
Speaker A
системе, на вот этом вот нашем локальном компьютере, мы мапим на какую директорию внутри докер контейнера. На хостсистеме я буду мапить вот эту вот директорию out. Ну давайте я это напишу. Я пишу точка/out.
Speaker A
Что значит точка/out? Это значит, что я хочу замапить вот эту вот директорию out, которая в относительном пути от нас, в относительном пути от той директории, из которой я запускаю эту всю большую команду. находится прямо в корне, то есть просто прописываю
Speaker A
относительный путь до директории out. Вот эта директория - это та директория, которая у меня будет смаплено в докер волюме на моей хостмашине, на моём локальном компьютере. Хорошо. Потом пишем двоето. И справа от двоеточая нужно написать директорию, которая будет
Speaker A
смаплена уже внутри докер контейнера. Ну, у нас внутри doкер контейнера наше приложение лежит в директории/upp. Ну и так как мы копируем полностью наш проект в эту самую директорию США, то в директории будет скопированная вот эта вот папочка out. Хорошо./out.
Speaker A
Что это мне даёт? Что даёт мне вот такой проброс докер волюма? Он мне даёт то, что у меня на hostсистеме есть папочка сшut и внутри докер контейнера у меня есть папочка сшу. Вот она на схеме. И я благодаря тому, что написал следующую
Speaker A
команду, я взял и смапил две эти директории. Теперь это, по сути, одна и та же директория. Или же, говоря другим языком, это теперь Docker Volume. Я сделал так, что папочка/Up/out внутри Docker контейнера у меня смапилась на папочку out на моей
Speaker A
хостсистеме в корне моего проекта. И если я запущу этот Docker контейнер, внутри докерконтейнера моё гошное приложение в папочке создаст файл, и этот файл автоматически у меня создастся в папочке на моей локальной машине.
Speaker A
Давайте проверим, как это работает. Вот смотрим в папке нет никаких файлов. Запускаю Docker контейнер с Docker волюмом. Контейнер отработал и в папочке появился файл. Почему он там появился?
Speaker A
Потому что я смапил папку Out внутри doкерконтейнера. на папку на моей хостсистеме, благодаря чему, когда какой-то файл в папке внутри docker контейнера создался, этот самый файл создался и на моей хостсистеме, благодаря чему даже когда наш контейнер остановился, файлы, которые этот
Speaker A
контейнер создал при помощи механизма Docker Volume, остались у меня на хостмашине. И вот он, этот самый new файл прямиком изнутри Docker контейнера.
Speaker A
Возможно, у кого-то из вас при запуске этого примера будет такая ошибка Operation not permitted. Что это значит?
Speaker A
Это значит, что у гошного приложения внутри докер контейнера по каким-то причинам не хватило прав, чтобы создать в папке новый файл. Почему это могло произойти? Это могло произойти потому, что мы эту самую папку Out на локальной машине создали своими руками. И потом,
Speaker A
когда эта папка с локальной машины была смаплена в рамках Docker волюма внутрь Docker контейнера, и в последствии наше гошное приложение попыталось в этой папке изнутри докер контейнера создать какой-то файл, то из-за того, что мы эту папку создавали руками, то права на
Speaker A
создание новых файлов в этой папке есть только у нашего пользователя. Но когда мы в докере запускаем какое-то приложение, там уже, как я могу вам напомнить, другой пользователь, потому что Docker - это изолированная среда.
Speaker A
Соответственно, у нас локально есть права, чтобы что-то добавлять в эту папку, но у докерконтейнера, у пользователя, который создаётся внутри докерконтейнера, нету этих прав. Что можно с этим сделать? На самом-то деле, тут можно просто эту папку out удалить.
Speaker A
Ведь всё равно, когда мы с вами пробрасываем doкеer Volume, Docker может за нас создать вот эту вот самую папку, которая задействуется в волюме. И уже у этой папки будут все нужные права для того, чтобы и Docker контейнер, и наш
Speaker A
пользователь могли полноценно с этой папкой работать. Как мы видим, у меня нет сейчас никакой папки на моей локальной машине. Давайте я соберу новый Docker образ и заново запущу Docker контейнер. Docker build тоteg volumeте.
Speaker A
То есть, если я создам ещё один docker образ, который называется точно так же, как тот Docker образ, что у нас уже существовал, то тот предыдущий Docker образ перезапишется новым. Ну давайте мы это и сделаем. Ждём, пока сбилдится наш
Speaker A
Docker образ. Docker образ готов. И теперь давайте ещё раз запустим наш Docker контейнер с пробросом волюма. У меня эта команда есть в памяти терминала. Я просто нажимаю стрелочку вверх два раза. Вот это моя команда.
Speaker A
Давайте ещё раз запустим наш Docker контейнер с пробросом докер волюма. Как мы видим, папка out создалась автоматически. Docker, когда мы пробрасывали Volume, сам нам создал эту папку. И уже теперь все необходимые права будут и у нас, как у пользователя
Speaker A
хостсистемы, и у пользователя внутри докерконтейнера. Соответственно, ошибки Permission denй больше не будет. Повторюсь, эта ошибка могла быть не у всех, но если у кого-то вдруг случилось, то теперь знаете, почему это произошло.
Speaker A
Ну давайте я удалю этот newfile.txt и попробую ещё раз запустить этот же самый Docker образ, который мы с вами создали, но уже без использования Docker Volume. Вот просто запущу Docker контейнер с нашим приложением. Запускаю.
Speaker A
Приложение отработало, но в папочке ничего не появилось. Почему? Потому что мы не пробросили Docker Volume, потому что без докеer волюма то, что было создано внутри докер контейнера, остаётся внутри докер конконтейнера, потому что Docker - это изолированная среда со своей файловой системой. Но
Speaker A
стоит нам только пробросить Docker Volume, так сразу же мы видим, что файл, созданный внутри Docker контейнера, появляется у нас на хостсистеме. Ну, в общем-то, в этом и суть докер волюма. И повторюсь, наиболее часто это используется, когда у нас внутри
Speaker A
докерконтейнера запущена какая-то база данных, и мы просто хотим, чтобы файлы, которые эта самая база данных используют для сохранения информации, не потерялись вместе с остановкой Docker контейнера.
Speaker A
Мы хотим эти файлы сохранить более надёжно, а для этого используется Docker Volume, и тем самым эти файлы у нас остаются на хостсистеме даже тогда, когда dokerкеer контейнер перестаёт существовать. Перед тем, как мы пойдём дальше, я бы хотел вернуться в наш
Speaker A
Docker файл и немножечко его улучшить. В целом, сейчас у нас довольно простой Docker файл, и, казалось бы, он выполняет наши требования, а именно построить docker image с нашим гошным приложением и при запуске этого самого иджа создавать docker контейнер, в
Speaker A
котором выполняется make file Target Service Run. Казалось бы, всё хорошо, но проблема кроется в том, что мы при запуске нашего Dockerконтейнера, при запуске приложения внутри Docker контейнера используем Target make Service Run. В чём же тут проблема?
Speaker A
Давайте посмотрим на наш make Target. Вот он target севи. И под собой он имеет командуmain.
Speaker A
Эта команда нам верой и правдой служила всё это время, пока мы с вами изучали Гошку. Но при помощи этой команды запускать гошный сервис внутри докерконтейнера - это не самая лучшая идея. Почему? На самом деле, когда мы с вами пишем одну команду runmain.
Speaker A
Капотом выполняются две команды. Раз и два. Что это за команды? Первая команда - это скомпилировать исполняемый файл из нашей программы. Вторая команда - это скомпилированный исполняемый файл, собственно, запустить. Получается, когда мы с вами пишем go runmain. То под
Speaker A
капотом сначала вызывается gobild main.go, то есть наша программа собирается в какой-то исполняемый файл, и этот исполняемый файл где-то там в нашей файловой системе создаётся. Он как-то называется. И потом, когда он будет всё-таки создан, сразу же после этого данный исполняемый файл, вновь
Speaker A
создан и будет запущен. А что вообще такое исполняемый файл? исполняемый файл - это бинарный код. То есть команда gobild, она берёт наше гошное приложение и компилирует это самое гошное приложение в бинарный код. Бинарный код, понятный нашему компьютеру, бинарный
Speaker A
код, который сможет выполнить наш процессор. Ну хорошо, допустим, действительно всё так, как я говорю, но почему же тогда, сколько бы раз мы с вами не писали main.go, никакого исполняемого файла мы с вами не наблюдали. А всё на самом деле довольно
Speaker A
просто. Когда мы с вами пишем go runmain. То этот самый исполняемый файл, он создаётся где-то в таком месте нашей файловой системы, где мы даже никогда и не бываем с вами. Обычно это что-то типа сtmp/gobuild с/ там что-то какой-то хэш может быть
Speaker A
присвоен созданному исполняемому файлу. Но мы с вами этот самый исполняемый файл даже никогда и не видим. А когда этот самый исполняемый файл отрабатывает, то есть когда наше гошное приложение завершается, то созданный во время команды gounmain.
Speaker A
Файл просто удаляется. Поэтому, когда мы с вами пишем gounmain. На самом-то деле под капотом происходит целые две вещи.
Speaker A
Первое - это из нашей гошной программы скомпилировать исполняемый файл. И второе - это данный исполняемый файл запустить. И как только гошная программа отработает, то этот исполняемый файл у нас будет удалён. Ну хорошо, с этим мы вроде как разобрались, но что же тогда
Speaker A
плохого в этой команде gounmain. На самом деле в этой команде нет ничего плохого, но когда мы при помощи этой команды запускаем наш сервис внутри докерконтейнера, то тут уже возникают две проблемы. Проблема первая. Так как у нас gounmail.
Speaker A
При запуске Docker контейнера, это значит, что при каждом запуске Docker контейнера у нас будет заново собираться наш исполняемый файл, и только после этого он будет запускаться. А если у нас гошная приложуха довольно большая, то этот самый исполняемый файл может
Speaker A
собираться довольно длительное время, несколько десятков секунд, может быть даже несколько минут. И это на мощном железе. Ну, как-то не круто получается, что мы каждый раз, когда будем запускать из уже готового докер иджа, мы из уже готового Docker иджа запускаем наш
Speaker A
Docker контейнер, запускается наш сервис, и этот самый сервис каждый раз будет компилироваться заново. Как можно выйти из этой ситуации? Можно своими руками на этапе сборки докер имиджа вызвать команду GoBild, скомпилировать исполняемый файл, а при запуске Docker контейнера просто всего лишь-навсего
Speaker A
вызывать уже скомпилированный исполняемый файл. Ну хорошо, давайте посмотрим, как это выглядит. После того, как я написал modйid и установил все свои необходимые сторонние зависимости, я могу опять написать run go build о.
Speaker A
Положу создаваемый исполняемый файл в папку app/x. То есть после флажка О мы пишем конечный путь, куда нужно положить наш созданный исполняемый файл. Ну а после этого передаю нашу гошную программу для компиляции. И в cmd, то есть в команде,
Speaker A
которая будет у меня запускаться при запуске самого Docker контейнера, я уже буду вызывать не Make File Target Service Run, который под собой имеет командуmain.
Speaker A
Я в директиве CMD в Docker контейнере буду писать app/x. Таким вот образом. Что же тут теперь произошло? Теперь тут произошло то, что нашу гошную программу в исполняемый файл, в бинарный код я буду компилировать на этапе сборки докер иджа. Таким образом, мой Docker Image,
Speaker A
может быть, будет чуть-чуть подольше собираться. Но зато потом каждый раз, когда я из этого докер иджа буду запускать doкеer контейнер, запуск докер контейнера будет происходить гораздо быстрее. Почему? Потому что у меня уже внутри docker иджа есть готовый собранный исполняемый файл. И мне на
Speaker A
этапе запуска Docker контейнера нужно всего лишь этот исполняемый файл привести в действие. Раньше же, когда мы вызывали здесь в cmd make file Target Service Run, то у нас каждый раз при запуске Docker контейнера начинала наша гошная приложуха сначала компилироваться
Speaker A
и только потом уже запускаться. Ну, а так как мы компиляцию исполняемого файла из нашей гошной программы вынесли в директиву Run, а директива Run выполняется именно на этапе сборки докер иджа, то мы, собственно, компиляцию исполняемого файла вынесли на этап
Speaker A
сборки Doкер иджа. Повторюсь, во время самой сборки докер иджа, может быть, чуть-чуть побольше времени потратим, но зато потом во время запуска Doer контейнера мы будем этот самый Docker контейнер запускать гораздо быстрее, потому что исполняемый файл уже готов, он уже был ранее скомпилирован, и нам
Speaker A
остаётся его только лишь запустить. Ну, давайте проверим, что у нас действительно всё работает в main. Файле я сейчас верну опять запуск нашего HTTP сервера. У нас тут просто выводится HTTP сеer Started. Ну и запуск вот этого нашего HTTP сервера, который мы уже с
Speaker A
вами описывали, который просто по паттерну спит возвращать Hello from Docker. Окей, перехожу в терминал.
Speaker A
Нахожусь я сейчас в корне нашего проекта. Тут же у меня лежит и Docker файл. Ну и теперь давайте из этого Docker файла скомпилируем себе docker image. Docker buildт t. Ну и тег я передам пускай my up. Точка. Точка
Speaker A
означает, что нужно взять docker файл, который находится в той же директории, что и вызываемая команда. Нажимаю Enter.
Speaker A
Всё, наш docker image готов. Теперь мы можем его запустить. Docker run. Сразу порт проброшу 5050 изнутри нашего Docker контейнера, потому что на этом порту запущенно HTTP сервачок наш. My goeng up Enter. И у нас моментально сразу же запускается наша гошная приложуха при
Speaker A
старте докерконтейнера. Почему? Да, потому что этап компиляции нашей гошной приложухи в исполняемый файл мы вынесли на этап сборки Docker иджа. И потом при запуске Docker контейнера у нас уже готовый исполняемый файл моментально запускается. И нам уже не нужно каждый
Speaker A
раз, когда мы запускаем Docker контейнер, ждать ещё и компиляцию нашей приложухи. Ну давайте просто проверим, что у нас всё работает. Я сейчас в наш этот HTTP сервачок отправлю простенький HTTP запрос local host 5050/pink.
Speaker A
И я вижу, что мой HTTP запрос успешно попал в наше гошное приложение внутри докер контейнера, и я со своей клиентской стороны получил HTTP ответ, что может говорить о том, что наше гошное приложение внутри докерконтейнера успешно развернулось и работает,
Speaker A
обрабатывая пользовательские запросы. Ну, это вот такой первый момент, почему всё-таки по-хорошему наш Docker файл нужно было чуть-чуть переделать, уйти от использования makeфай таргета севи Run в момент запуска Docker контейнера и разнести компиляцию гошной программы и запуск итогового исполняемого файла на
Speaker A
две разные команды. Но я изначально говорил, что у нашего нового подхода есть два преимущества. Первое преимущество мы уже с вами обсудили, это скорость запуска Docker контейнера из уже готового Docker иджа. А какое же второе преимущество? А второе преимущество мы с вами сможем оценить,
Speaker A
когда изучим тему системные сигналы. Поэтому мы с вами к данному моменту в этом видео ещё вернёмся. Ну а пока мы можем идти дальше. Сейчас мы с вами рассмотрели, как внутрь докер контейнера можно запихнуть нашу гошную приложуху, как можно изнутри этого Docker
Speaker A
контейнера пробросить порты себе на локальную машину и как из этого Docker контейнера можно пробросить себе на локальную машину некий Docker Volume.
Speaker A
Теперь давайте посмотрим, как запустить в Docker контейнере базу данных PGRSQL. Точно также мы её запустим внутри Docker контейнера. Потом попробуем изнутри этого Docker контейнера пробросить себе порт на локальную машину и также прокинем Docker Volume. Посмотрим, как это работает в случае с Docker
Speaker A
контейнером с PGRSQL. Порт мы будем пробрасывать 5432, потому что PGRS SQL работает именно на этом порту. Пробросим этот порт на локальную машину. А Docker Volume, который мы будем пробрасывать - это будет директория внутри Docker контейнера с PassGSQL, которая содержит
Speaker A
в себе данные, которые PASGSQL сохраняла. описание таблиц, описание схем данных. Сами сохранённые данные, они все будут лежать в этой папке. И мы эту папку, мы эту директорию в качестве докер волюма пробросим себе на локальную машину. И таким образом мы увидим, что у
Speaker A
нас данные, что у нас описание схемы данных сохраняются даже при перезагрузке Docker контейнера с Passgrql. Ну давайте посмотрим, как это всё работает, как это выглядит на деле. Первым делом я напишу Docker PS, увижу, что у меня сейчас никаких запущенных контейнеров нету. Ну
Speaker A
и по сути мы сейчас готовы запустить Docker контейнер с постгресом. Нужно ли нам описывать Docker файл для того, чтобы запустить Docker контейнер с Постгрессом? Нет. Почему? Потому что Docker файлы мы с вами описываем, когда хотим в докер контейнере запустить
Speaker A
какие-то свои кастомные сервисы, какие-то своинд-приложения, какие-то свои утилиты. Но когда мы с вами хотим запустить в докер конконтейнере какой-то сервис, который достаточно популярный и много где используется, то, скорее всего, для этого сервиса уже есть готовый docker image. А значит, нам не нужно описывать
Speaker A
свой собственный Docker файл, чтобы на основе этого Docker файла построить docker image, чтобы потом запустить этот Docker Image. Нет, скорее всего, Docker Image под какой-то популярный сервис уже есть готовый. Как это проверить? Ну, переходим в DockerHehub, уже знакомый
Speaker A
нам, и пишем в поиске названия сервиса, который мы хотим найти. Ну, допустим, у нас это, в нашем случае постгress. Я пишу Postgress, и я вижу здесь огромное количество различных поставщиков готовых Dockerй с постгресом. Ну, я выберу официальное. Вот, Docker Official
Speaker A
Images. Нажимаю. И тут я могу сразу увидеть, какое большое количество версий Docker иджа с базой данных Postgress есть в наличии. Есть постгрес версии 14, есть постгрес версии 15, 16, 17, 18.
Speaker A
Каждая версия ещё и делится на какие-то свои подверсии. Например, у нас вот есть восемнадцатая версия Latest, Trixi, Bookorm, Alpin. Уже нечто похожее на то, что мы с вами видели, когда смотрели на версии Docker Imagy от официального поставщика Docker Imд с голгом. Ну, тут
Speaker A
далеко ходить не будем. Я выберу одну из таких самых популярной версий Постгреса. Это 18 Bookworm. Довольно стабильная, довольно надёжная версия. Перехожу обратно в VS-код, и мне, чтобы запустить Посгрыз версии 18 BookworM, мне не нужно описывать Docker файл свой. Мне не нужно
Speaker A
какие-то дополнительные действия предпринимать. Я могу просто написать docker runstgress двоеточие и написать через двоеточие выбранную мною версию.
Speaker A
Это 18 bookm. Как отработает в этот раз докер? Докер посмотрит на название иджа, который я хочу запустить. Докер увидит, что локально у меня на машине такого иджа нету. Я его не создавал. Значит, Докер пойдёт смотреть на наличие этого
Speaker A
иджа на Dockerhub. Он придёт на DockerHub, и он увидит этот самый идж, что действительно он на Докерхабе существует. Тогда Docker возьмёт, скачает этот Docker image мне на локальную машину и сразу же его запустит. Это всё произойдёт вот этой
Speaker A
вот одной командой. Ну давайте я нажму Enter, посмотрим, что будет происходить. У меня Docker локально не нашёл Docker image postgress 18 Bookorm, поэтому Docker пошёл собирать этот самый имидж с Докерхаба. И вот мы сейчас наблюдаем за процессом, как этот самый имидж
Speaker A
скачивается. После того, как идж скачался, он сразу же запускается. У нас вот запустилась база данных, но мы видим, что какая-то случилась ошибка.
Speaker A
Что нам говорит ошибка? База данных не проинициализирована, и для суперюзера не задан пароль. И нам предлагают варианты, как мы можем этот пароль задать. Что это значит? Это значит, что когда мы с вами запускаем Docker image с постгрысом от
Speaker A
официального поставщика Постгрыс, то по умолчанию для базы данных не выставлено никакого пароля, и нам нужно этот самый пароль выставить самим. Как это делается? При помощи чего это делается?
Speaker A
Пишется docker runstgress двоеточие 18 bookm. Но перед названием иджа пишется тире пробел постress password равно и какой-то пароль надо задать. Ну я задам пароль вот passс. Что тут происходит? Почему я передаю какой-то флажок E и потом ещё пишу Postgress
Speaker A
Password равно pass. На самом-то деле флажок Е означает, что при запуске внутри докер контейнера необходимо задать какие-то переменные окружения. В нашем случае мы будем задавать переменную окружения Postgress Password и значение мы в неё положим pass. Docker контейнер, который запустится из этого
Speaker A
имиджа, первым делом пойдёт смотреть на доступные ему переменные окружения. Когда он увидит, что переменное окружение Postgess Password задано, тогда он возьмёт её значение и сделает значение этой переменной паролем для базы данных. Вот таким вот образом это работает. Давайте ещё раз попробуем
Speaker A
запустить. Нажимаю Enter. Опа. И как я вижу, во-первых, я не ждал в этот раз, когда же у меня скачается Docker Image Postgess 18 BookWorm, потому что этот Docker image уже был установлен ранее.
Speaker A
А, во-вторых, я теперь вижу, что у меня база данных запустилась, пошли какие-то выводы этой самой базы данных. И мы видим в конце вывод Database system ready to accept connections. То есть система управления базой данных Посрыс, которая только что у нас была запущена в
Speaker A
Docker контейнере, готова принимать входящие подключения. Я могу открыть дополнительный терминал, написать тут Docker ps, и я увижу запущенный свой Docker контейнер с постгресом. И казалось бы, всё хорошо. Я запустил себе докер контейнер с постгресом. Я могу к нему подключиться. Ну давайте откроем
Speaker A
ПГДМН и попробуем подключиться к этому самому докерконтейнеру с постгресом. Нажимаю в PGмине правой кнопкой по севергистр сервер. Назову это Docker Postgress connection local host по 5432. Database по умолчанию Postgrress username по умолчанию Posgress. Пароль passс, который я задал тогда при запуске doкер
Speaker A
контейнера. Нажимаю save. И вроде как я подключился к этой самой базе данных. Но если мы посмотрим чуть глубже, я провалюсь дальше в схемы пабли таблицы.
Speaker A
И тут я увижу довольно странную картину. Здесь у меня есть таблица tasks, таблица users, то есть те самые таблицы, которые мы с вами при помощи миграции создавали в предыдущей теме. О чём это может нам говорить? Это может нам говорить лишь о
Speaker A
том, что мы подключились не к базе данных, которую мы запустили внутри докер контейнера. Мы с вами подключились просто к базе данных, которая у нас была до этого запущена прямо локально на нашей машине. То есть та база данных Постгрес, которую мы с вами до этого
Speaker A
скачали и установили себе на локальной машины, она запущена и работает на 5432 порту. И мы как раз-таки к этой самой базе данных и подключились. Но это не имеет ничего общего с той базой данных, которую мы запустили в Docker контейнере
Speaker A
только что с вами. Эта база данных, запущенная внутри Docker контейнера. Она тоже работает на 5432 порту, но так как Docker контейнер - это изолированная среда со своей изолированной сетью, то 5432 порт, который внутри докерконтейнера обслуживается, он не имеет никакого отношения к 5432 порту,
Speaker A
который у нас на локальной машине. Как же нам тогда подключиться к этой самой базе данных Постгрыз, которая запущена внутри докерконтейнера? Но казалось бы, мы просто должны взять, да и пробросить 5432 порт изнутри докерконтейнера на 5432 порт себе на локальную машину, и мы
Speaker A
тогда из нашего гошного приложения смогли бы к этому 5432 порту подключиться. Но в чём проблема? в том, что 5432 порт он уже занят той базой данных, которую мы с вами запускали локально на компьютере вне рамок докерконтейнера. К чему это приведёт? К
Speaker A
тому, что при попытке пробросить 5432 порт изнутри докерконтейнера на 5432 порт на локальную машину, у нас произойдёт конфликт портов, и мы просто не сможем прокинуть этот самый порт, потому что он уже занят постгресом, запущенным локально на компьютере. Тут
Speaker A
есть два выхода из этой ситуации. Первый выход - это 5432 порт изнутри докерконтейнера пробрасывать на какой-нибудь другой порт на нашей локальной машине, например, 5431. Тогда конфликта портов не будет, всё хорошо, и мы сможем из нашего гошного приложения, из ПГадмина, откуда угодно, подключиться
Speaker A
к постгресу, которая работает внутри докерконтейнера, на 5431 порт, и тогда мы попадём вот в эту самую базу данных.
Speaker A
Но почему такое решение плохое? Потому что лучше по возможности всегда придерживаться стандартных портов, которые задаются какими-то сервисами.
Speaker A
Вот 5432 порт для постгреса - это стандартный порт. Соответственно, очень много где при работе с этим самым постгресом ожидается, что он будет занимать именно 5432 порт. А если мы на локальную машину себе этот порт пробрасываем на какой-нибудь другой,
Speaker A
например, 5431, то это уже кого-то может ввести в заблуждение. Мол, что за 5431 порт? Непонятно, к чему он относится или, наоборот, почему мы к базе данных подключаемся на 5431 порту. Это может внести дополнительную путаницу и неразбериху. Поэтому нам хотелось бы,
Speaker A
если уж мы и подключаемся к базе данных погре, то делать это именно на 5432 порту. Но что тогда делать с конфликтом портов? Да всё довольно просто. Мы должны взять и базу данных погре SQL, запущенную у нас локально на машине,
Speaker A
просто остановить. Тогда она перестанет занимать 5432 порт, и мы сможем на этот порт спокойно пробрасывать базу данных изнутри докерконтейнера, потому что отныне и впредь мы постгрес будем запускать именно внутри докерконтейнера.
Speaker A
Это крайне удобно. Мы в любой момент можем запустить постгрес, если нам нужно. В любой момент можем просто остановить докерконтейнер с постгресом.
Speaker A
Нам всегда удобно контролировать сохраняемые постгрессом данные, потому что если мы, например, хотим полностью очистить постгрес, мы можем просто Docker Volume с постгрысом очистить, и тогда у нас по сути будет чистая свежая база данных. А когда у нас база данных
Speaker A
работает на локальной машине, это ещё попробуй найди, где она сохраняет эти свои данные. Поэтому давайте сейчас изнутри докерконтейнера попробуем пробросить 5432 порт. Мы увидим, конечно же, конфликт портов и потом пойдём и остановим базу данных, которая работает у нас локально на машине, чтобы она
Speaker A
освободила 5432 порт и мы смогли им свободно пользоваться. Возвращаюсь в VS-код, возвращаюсь в терминал, где у меня в докере запущено Позгрыз, и просто нажимаю Ctrl C docker PS. И всё, я вижу, у меня контейнер с постгресом остановился. Давайте теперь я попробую
Speaker A
запустить этот самый Docker контейнер той же самой командой, только дополнительно проброшу 5432 порт. Ну, порты мы с вами пробрасывать уже умеем.
Speaker A
P 5432, двоеточие 5432. Нажимаю Enter, и я вижу ошибку. Порт недоступен. Адрес already in use. То есть этот адрес, этот порт уже кем-то используется, и мы не можем повторно этот 5432 порт использовать. Хорошо, тогда нам нужно взять и остановить ту PGS SQL, которая
Speaker A
работает вне рамок докерконтейнера у нас локально на машине. Если вы устанавливали постгрес по гайду из этого видео на MacOS, тогда работа с этим самым постгресом происходит через утилиту PGCL. Команда сейчас будет довольно длинная, поэтому приготовьтесь.
Speaker A
Я пишу судо/library PostgressQL. После этого версию постгреса, которую вы скачали. восемнадцатое у меня сbг нижнее подчёркивание CTL пробел тире D опять library pasgr 18/dataстаus вводим пароль от вашего пользователя на maos и что мне дала эта команда я обратился к утилите PG CTL которая нужна
Speaker A
для того чтобы управлять базой данных я передал место сохранения данных в моей базе данных и я попросил статус мне дали ли статус базы данных постгрес, запущенной у меня локально на машине.
Speaker A
Мне пишется сервер is running, то есть сервер в данный момент запущен. Мне нужно теперь этот сервер остановить. Для этого пишется такая же самая команда, только в конце стоп. Enter. Сервер стоп.
Speaker A
Теперь могу опять посмотреть статус, и я вижу, но север running. Всё, я остановил базу данных Постгрыс, которая у меня работала локально на машине. Если вы устанавливали постгрес по гайду из этого видео на Linux, тогда для того, чтобы этот самый постгрес остановить, нужно
Speaker A
зайти в терминал и написать следующую команду сум CTL status. И после этого passг SQL, нажимаем Enter. Вводим пароль от вашего пользователя операционной системы. И как я вижу, сейчас мне пишется, что у меня статус PGress Active. Соответственно, PassG SQL у меня включена и в данный
Speaker A
момент запущена на моей локальной машине. Как мне остановить эту самую PGS SQL? Нажимаю Q, чтобы выйти из этого режима. Пишу снова сум CTL stopgr SQL. Нажимаю Enter. Теперь ещё раз смотрю статус. И как я вижу, теперь у меня погрыз и то есть всё, я
Speaker A
остановил базу данных Posгс, которая работала у меня локально на машине. Но мы видим, что у нас PGress всё ещё enйabled. Что это значит? Это значит, что при перезагрузке вашего компьютера Постгрыс может опять запуститься автоматически. Для того, чтобы это
Speaker A
поведение выключить, нам нужно написать сум CTL disable PGRE SQL. Нажимаем Enter. После этого снова пишем суда System CTL status PGR SQL. Enter. И теперь я вижу, что у меня постгрес и в данный момент выключен и выключен автозапуск службы с постгресом. То есть
Speaker A
теперь у меня при перезапуске системы Postgress не будет автоматически запускаться операционной системой. Поздравляю, мы с вами остановили работающую у нас локально на машине PGRS SQL. Соответственно, 5432 порт теперь снова свободный, и мы можем идти дальше.
Speaker A
И теперь, когда мы с вами освободили 5432 порт на нашей локальной машине от запущенного локально Pustgr SQL сервера, мы теперь можем запустить Passgre SQL внутри doкерконтейнера с пробросом 5432 порта на нашу локальную машину. Значит, смотрю Docker PS, вижу, что сейчас у
Speaker A
меня никаких контейнеров не запущено, ну и пишу Docker runstgress двоеточие 18 bookm. До этого мне нужно задать пароль для запускаемой базы данных Postgress password равно passросить порт P 5432 5432. То есть 5432 порт изнутри докер конконтейнера я пробрасываю на 5432 порт себе на
Speaker A
локальную машину. Нажимаю Enter. И, как я вижу, база данных запустилась. Но в чём тут ещё неудобство? в том, что запущенная БДшка у меня блокирует мою терминальную сессию. Я бы не хотел этого делать. Я бы хотел дальше пользоваться своим терминалом. Зачем мне вот эта
Speaker A
активная терминальная сессия? Я нажимаю Ctrl C и снова запускаю ту же самую команду, только в конце пишу тире D, чтобы запустить постгрес в detched моде, чтобы не блокировать мою терминальную сессию. Нажимаю Enter, пишу Docker PS, и я вижу, что запущен Docker контейнер с
Speaker A
постгресом. При этом моя терминальная сессия не заблокирована. И теперь в графеs я вижу, что 5432 порт из докер контейнера проброшен мне на локальную машину. И теперь уже я могу попробовать подключиться к этому контейнеру из pадмина. Открываю PGDIN. На этот наш
Speaker A
сервер я нажму правой кнопкой мыши Disconnect from Server. И для чистоты эксперимента я теперь зарегистрирую новый PGR SQL-сервер. Нажимаю правой кнопкой мыши, регистр, сервер. Назову New Docker Postgress, Connection, local Host, порт 5432, база данных, имя пользователя по умолчанию, пароль тот,
Speaker A
который мы с вами передавали в переменное окружение через тире e, погрес Password и так далее. Нажимаю save. И, как я вижу, я теперь подключился к базе данных. Только тут, если открыть схему, пабликсхему, я увижу, что никаких таблиц нету. Почему?
Speaker A
Потому что я подключился к базе данных, которая находится внутри докер контейнера. Давайте сейчас внутри этой базы данных создадим какую-нибудь самую простенькую табличку. Для этого я нажму вот здесь вот в PGM Query Tool. И здесь прямо я могу писать SQL-запросы, которые
Speaker A
полетят мне в мою базу данных. Ну, я напишу просто create table test table. И что у меня будет в этой таблице? В этой таблице у меня будет просто поле ID, serial, primary ke, ну и какой-нибудь name просто абстрактный warchr на 100 символов. В
Speaker A
целом всё. Особого смысла эта таблица не имеет. Нам нужен сам факт создания новой таблицы. Значит, я запускаю этот описанный скрипт. У меня таблица создалась. Теперь я могу здесь нажать правой кнопкой мыши рефреш. И я увижу, что у меня создалась таблица test table.
Speaker A
Эта таблица создана в постгресе, который запущен внутри докерконтейнера. О чём это нам говорит? Это нам говорит о том, что если я сейчас этот самый запущенный докер контейнер остановлю, перезапущу, то у меня пропадёт вот эта моя таблица table. При том, что я буду подключаться
Speaker A
к тому же самому Passgrql серверу. Ну давайте это и проверим. То есть я сейчас беру, останавливаю здесь этот dokerкеer контейнер, копирую его имя и пишу Docker kill. Вставляю имя контейнера, нажимаю Enter. Docker PS. Вижу, что у меня сейчас этого контейнера нету
Speaker A
запущенного. Перехожу в PG adдмин. Нажимаю правой кнопкой мыши по нашему серверу рефреш. И я вижу ошибку, что подключение к серверу потеряно. Ну, понятное дело, потому что мы его остановили. И сколько бы раз я тут не нажимал рефреш, я буду видеть ошибку,
Speaker A
что не получается подключиться к базе данных. Ну, потому что мы её остановили. Хорошо. Теперь давайте обратно её запустим той же самой командой, что мы и запускали до этого. Docker run. Передаю пароль, пробрасываю порт. В detched моде запускаю наш Docker Image. Postgress 18
Speaker A
bookorm. Нажимаю Enter. Вроде снова у нас наш постгрес запущен. Опять проброшен порт. Опять база данных работает в докерконтейнере. Ну давайте теперь я опять нажму рефреш в PGне.
Speaker A
Вроде как я подключился. Иду в tables и вижу, что нету тут никаких таблиц у нас.
Speaker A
Почему? Да, потому что все данные, которые принадлежали постгресу из предыдущего контейнера, они исчезли вместе с этим предыдущим контейнером.
Speaker A
Получается, наша схема данных, наши данные, хранимые внутри постгреса, не переживают перезагрузку докерконтейнера с этим самым постгресом. А как сделать, чтобы данные сохранялись между различными запусками этого самого докер конконтейнера с постгресом? Ну, я думаю, кто-то уже смог догадаться. Нам
Speaker A
необходимо использовать механизм докер волюма. Ну давайте мы это и сделаем. Перехожу в V вкод. Опять прибью тот Docker контейнер с постгресом, который у меня сейчас запущен. Docker PS. Вижу, что сейчас нету запущенных контейнеров.
Speaker A
Ну, правда, если я Docker PS - Aпишу, я тут увижу остановленные Docker контейнеры с постгресом. Мне они не нужны, поэтому я сейчас напишу Docker контейнер prн. Yes. И таким образом я удалю все Docker контейнеры, которые даже были остановлены. Ну, просто, чтобы
Speaker A
они память у меня на компьютере не занимали. Теперь, что Docker PS, что Docker PS - A всё, никаких контейнеров сейчас у меня нету запущенных. Пишу опять эту нашу команду для запуска Docker контейнера. Только теперь дополнительно я ещё и буду пробрасывать
Speaker A
Docker Volume. Для этого я пишу эту уже знакомую нам команду. Только здесь, перед объявлением detected мода, можно и после, я буду пробрасывать волю, пишу тире V. После этого я должен написать директорию, в которую будет проброшен Docker Volum изнутри докерконтейнера. Я
Speaker A
проброшу это всё в директорию out. Вот она. И в ней я создам директорию PG Data. Сейчас у нас в директории out нет никакой под директории PGATA, но это не страшно, потому что докеer, чтобы пробросить в эту директорию какой-то
Speaker A
волюм, создаст эту директорию за нас. То есть в папке появится папка PG дата, которая была создана именно докером.
Speaker A
После двоеточия и я должен описать директорию, которую я изнутри докерконтейнера буду на локальную машину пробрасывать. В случае с постгресом это директория/war/liip/pstg.
Speaker A
SQL В этой директории Postgess хранит все необходимые данные. И если мы эту директорию изнутри докерконтейнера сохраним у себя локально в докер волюме, то все данные, схемы данных в постгресе будут переживать перезапуск контейнера.
Speaker A
Ну что, давайте запустим. Нажимаю Enter. Оп. И я вижу, у меня в директории out появилась под директория PG дата. И тут куча всяких файлов, куча всяких папок.
Speaker A
Эти файлы и папки пришли к нам на локальную машину прямиком из Docker контейнера. Хорошо. Теперь у нас есть Docker Volume. Возвращаемся в PG adдмин.
Speaker A
Я сейчас нажму рефреш на весь наш PGR SQL-сервер. Смотрю таблицы. Нет никаких таблиц. Хорошо. Выполню вот этот вот наш SQL-запрос. Пока мы контейнер с постгрессом перезапускали, у нас подключение стало невалидное. Нам нужно пересоздать новое. Ну, давайте нажмём continue. Оп, всё, подключение
Speaker A
пересоздалось. SQL запрос отправился. Таблица создалась. Нажимаю рефреш и я вижу, создалась новая таблица test table. Хорошо. Теперь возвращаюсь в V в VS-код. Пишу docker PS. Останавливаю работающий контейнер с постгрысом.
Speaker A
Docker kill. Всё, контейнер с постгресом остановлен. Возвращаюсь в PG adдмин. Пытаюсь нажать рефреш. Опять ошибка подключения, потому что у нас остановился постгрес внутри докер контейнера, и у нас больше некуда подключаться. Всё, некуда подключаться.
Speaker A
Постгресс остановлен. Теперь заново запускаю Docker контейнер с постгресом. Нажимаю Enter. Docker PS. Вот он, запущенный Docker контейнер. Возвращаюсь в PG adдмин. Опять переподключаюсь к нашему PSG SQL-серверу. Вниз мотаю. И я вижу, что таблица наша, она осталась на своём законном месте. Почему? Потому что
Speaker A
мы пробросили docker Volume изнутри пог SQL-контейнера себе на локальную машину, благодаря чему мы сохранили все данные постгреса, которые ему нужны для того, чтобы схему данных сохранять и для того, чтобы данные внутри этой схемы данных сохранять. Вот этот наш вом PG дата. И
Speaker A
мы, когда перезапускали наш Docker контейнер, благодаря тому, что мы пробросили этот самый Docker Volume, то запущенный Docker контейнер с постгресом в своей директории war/lip/pgressql увидел наш Docker Volume и увидел все данные, которые ему нужны для запуска.
Speaker A
Соответственно, мы сохранили данные изнутри докерконтейнера с постгресом при перезапуске этого самого докерконтейнера благодаря механизму Docker Volume. Таким образом, наглядно мы можем видеть, для чего нам эти самые Docker волю нужны и как они используются. Таким образом, мы с вами максимально поверхностно
Speaker A
поговорили и посмотрели на Docker. Ещё более поверхностно поговорили, посмотрели на Docker Compos. поняли, что докеer - это инструмент контейнеризации приложений, поняли, что докер - это некий инструмент для запуска приложений в изолированной среде, поговорили, зачем нам в принципе нужна эта самая
Speaker A
изолированная среда. Ну и также поговорили, что есть некий докеркомпос, который, по сути, является лишь удобным менеджером для управления докерконтейнерами. Docker compмпо значительно нам упрощает работу с самим докером. И теперь всё, что нам остаётся - это очень хорошо попрактиковаться с
Speaker A
докером и попрактиковаться с докеркомпозом, потому что это довольно обширные технологии, и основной навык работы с ними приобретается именно во время практики. То есть можно бесконечно учить теорию по докеру, как он там работает, можно бесконечно учить теорию по докеркомпозу, как же он там
Speaker A
взаимодействует с докером. можно бесконечно это всё изучать, а можно просто сесть и начать этим пользоваться.
Speaker A
И вы гораздо быстрее начнёте понимать, как это работает, как это использовать и как сделать так, чтобы эти инструменты работали вам на пользу. Ну и теперь всё, что нам осталось сделать - это добавить изменения в комит, сделать, собственно, этот самый комит, отправить комит на
Speaker A
удалённый репозиторий, сделать пулреквест, провести самому себе кодрев. И если написанный код удовлетворяет вашим требованиям, если вы считаете, что это хороший код, тогда заливайте этот пулреквест себе в мастерку. Ну а я это в десятый раз уже показывать не буду,
Speaker A
чтобы не растягивать хронометраж. [музыка] Настала пора кратенько поговорить про сигналы операционной системы. Небольшая, но довольно важная тема. Вообще операционная система, предположим, что этот квадратик - это операционная система. Операционная система смотрит на всё, что в ней происходит, как на
Speaker A
большое множество различных процессов. К примеру, ваше работающее гошное приложение с точки зрения операционной системы - это просто какой-то процесс.
Speaker A
Запущенная база данных пог с точки зрения операционной системы - это всего лишь какой-то работающий процесс. Ваш браузер, игра запущена или проводник, да что угодно. С точки зрения операционной системы, любое приложение, любая программа - это просто всего лишь-навсего какой-то процесс. Этот
Speaker A
процесс может создавать какие-то файлы. Этот процесс может пользоваться файловой системой операционной системы. Этот процесс может использовать процессорное время или мощности видеокарты. Этот процесс может быть запущен внутри докерконтейнера. Этот процесс может использовать сетевые интерфейсы для отправки или принятия сетевых запросов.
Speaker A
Но тем не менее, несмотря на всё это, с точки зрения операционной системы, что гошное приложение, что база данных, что браузер и так далее, это всё процессы.
Speaker A
Операционная система нам позволяет эти процессы запускать, но после запуска иногда наступает момент, когда мы хотим, чтобы процесс завершился, потому что было бы глупо уметь просто запускать процессы, но не уметь их останавливать.
Speaker A
Мы хотим уметь эти самые процессы завершать. И тут мы встречаемся с некоторой проблемой. Представим, что у нас есть наше гошное приложение. Это гошное приложение представляет нашу какую-то высоконагруженную социальную сеть. И в это гоштоное приложение в данный момент летит огромное количество
Speaker A
сетевых запросов. И множество пользователей ждут, пока эта наша программка, это наше написанное приложение обработает все запросы пользователей. И в данную секунду это приложение занято тем, что пользовательские запросы, собственно, обрабатывает. Но по каким-то причинам мы хотим завершить работу этого приложения.
Speaker A
Ну, к примеру, чтобы выкатить новую версию. Или наше приложение уходит на техническое обслуживание. Нам нужно завершить это приложение. Какие у нас есть для этого варианты? Ну, вообще, мы с вами можем зайти в диспетчер задач и просто прибить это приложение. Просто
Speaker A
взять его и вот так вот одним движением выгрузить из оперативной памяти. Но чем это чревато? Это чревато тем, что приложение в данную секунду могло обрабатывать какие-то пользовательские запросы. А если мы это приложение просто придём и вот так вот варварским способом
Speaker A
из диспетчера задач выгрузим из оперативной памяти, завершим его, прибьём, тогда пользовательские запросы, которые в этот момент обрабатывались, просто будут прерваны, и пользователь увидят у себя на экране ошибку. То есть да, у нас есть такой инструмент завершения процессов, но он нам далеко
Speaker A
не всегда подходит. Так вот, операционные системы насчёт винды не знаю, но Unix операционные системы уж точно поддерживают такой механизм, как отправку сигналов. То есть мы внутрь какого-то работающего процесса со стороны операционной системы можем отправить [откашливается] какой-нибудь сигнал, который будет содержать какую-то
Speaker A
информацию. Этот самый процесс на своё усмотрение может принять данный сигнал и сделать из этого какие-то выводы. В Unix подобных операционных системах различных видов этих самых сигналов может быть большое множество. Мы сейчас с вами рассмотрим три самых популярных сигнала
Speaker A
в Unix операционных системах, которые отправляются от операционной системы к каким-то процессам. Первый сигнал - это SK in. Вот он называется SK in. То есть это сокращение от сигнал итерапt, то есть это какой-то сигнал на прерывание процесса, когда этот сигнал
Speaker A
отправляется. Обычно этот сигнал отправляется, когда пользователь запустил в терминале какую-то программу, эта программа работает, и потом пользователь нажимает Ctrl C. Тогда программе отправляется сигнал си, то есть signal interapt. Хорошо, мы с вами дальше посмотрим конкретно, как это выглядит. Пока идём дальше. Следующий
Speaker A
сигнал - это, то есть signal terminate. Часто путают сик int и сик, потому что си int - это сигнал на прерывание, актер - это сиг. По сути тоже сигнал на прерывание, но между ними есть разница.
Speaker A
Sc отправляется программе, когда пользователь нажимает в терминале Ctrl C, а Сиктер уже отправляется средствами операционной системы и означает скорее не то, что пользователь хочет прервать программу нажатием Ctrl C, а скорее сектерм означает, что операционная система просит процесс аккуратно
Speaker A
завершиться. Ну вот для чего-то основоположниками Unix операционных систем было выбрано такое разделение. По сути, два этих сигнала по смысловой нагрузке крайне похожи. Оба этих сигнала мы можем отправить работающему процессу.
Speaker A
И оба этих сигнала означают примерно одно и то же, а именно, пожалуйста, процесс аккуратно завершись. Я хочу, чтобы ты в ближайшее время завершился, но я тебе даю доделать какие-то там свои важные операции. Но просто сигнт отправляется, когда мы в терминале
Speaker A
нажимаем Ctrl C при какой-то запущенной программе. Актерм отправляется в остальных случаях, когда у нас, к примеру, какая-то программа долгое время работает, у нас нету никакой активной терминальной сессии, связанной с этой программой, мы просто хотим зайти и отправить сигнал на завершение этому
Speaker A
процессу. В таких случаях отсылается сектерм. Ещё раз повторюсь, смысловая нагрузка этих двух сигналов очень похожа. Просто син - это когда мы нажимаем Ctrl C в терминале, сиктерм, всё остальное. Далее у нас есть третий один из самых распространённых сигналов
Speaker A
- это seк killill. Этот сигнал означает, что операционная система хочет немедленно, независимо ни от чего прибить какой-то процесс, без возможности дать этому процессу как-то корректно завершиться, без какого-то ожидания. Операционная система хочет просто прийти и прибить процесс моментально, так сказать, без лишних
Speaker A
слов. Вот такие вот три сигнала самые популярные вообще в Unix операционных системах. С этими тремя сигналами наиболее часто сталкиваются программисты. И глобально отличие у этих сигналов в том, что SIG intг изнутри приложения можем перехватить и обработать так, как мы хотим. А сикиill
Speaker A
мы уже не можем перехватить вообще никак. Если нам в процесс прилетел сигнал сиккиill, всё, наш процесс тут же завершается. То есть у нас, как у программистов, нет никаких возможностей перехватить вот этот вот сигнал.
Speaker A
Возможно, можно как-то пропачить ядро Линукса, но это, конечно, уже совсем другой разговор. Мы сейчас говорим про общий случай. Вот в общем случае сигналы Sкint intктер мы изнутри приложения можем поймать и сделать на основе них какие-то действия. А вот сиккиill мы уже
Speaker A
никак не можем поймать. Прилетает сикки, значит, всё, конец нашему приложению. наше приложение просто удаляется из оперативной памяти немедленно. Каждый из этих сигналов может как отправляться автоматически операционной системой, так и мы можем руками инициировать отправку этих сигналов в какой-нибудь работающий
Speaker A
процесс. Но для того, чтобы нам отправить сигнал в какой-то конкретный процесс, нам нужно знать айдишник этого процесса. То есть в Unix подобных операционных системах для каждого процесса, будь то приложение, база данных или браузер или что-нибудь ещё, для каждого процесса присваивается
Speaker A
определённый айдишник. Вот как в базе данных у нас было, что для каждой записи генерируется какой-то целочисленный айдишник. Вот то же самое в Unix подобных операционных системах. Для каждого процесса генерируется какой-то свой уникальный целочисленный идентификатор. Нам нужно этот идентификатор знать, и тогда мы сможем
Speaker A
по этому идентификатору в этот процесс отправить какой-нибудь сигнал. То есть ещё раз собираем всё вместе. В операционной системе всё является процессом. Гошное приложение, база данных, вообще что угодно, программа любая, написанная вами или же какая-нибудь системная служба. По сути,
Speaker A
это всё процесс. То есть вот такая абстракция операционной системы над какими-то выполняющимися программами. И мы в эти самые процессы в Unix подобных операционных системах можем отправлять извне какие-то сигналы. Это может делать либо операционная система в автоматическом режиме, когда она
Speaker A
посчитает нужным, либо мы, как пользователи этой операционной системы, можем также инициировать отправку этих сигналов в какой-нибудь процесс. Сигнал sigнт принято считать, что обозначает желание пользователя, который как-то интерактивно взаимодействует с запущенным процессом, этот самый процесс остановить. Вот это вот сигнтерм уже
Speaker A
скорее связывают с тем, что у нас есть какой-то фоновый процесс, он у нас работает. Это может быть опять же наша гошная программа, которая в фоне запущена, и мы хотим попросить эту программу аккуратненько завершиться, высвободить все свои ресурсы до конца,
Speaker A
доработать все свои сетевые запросы, которые пришли от пользователей. Вот в таком случае мы можем отправить сиктерм, что означает, пожалуйста, программа, аккуратненько завершись и остановись. Ну и также у нас есть seкиill. Seкиill может быть отправлен операционной системой. Seкиill мы можем своими руками
Speaker A
тоже взять и в какой-нибудь произвольный процесс отправить, но уже изнутри программы мы не можем перехватить сиккиill. То есть операционная система нам не даёт возможности изнутри программы перехватить вот этот вот сигнал. А сигналы сиг и сигтер мы изнутри программы можем перехватить. И
Speaker A
исходя из того, какой сигнал мы получили, мы можем совершить какое-нибудь действие. Ну теперь давайте от этой теории перейдём к небольшой практике, посмотрим, как это всё выглядит. Ну, я создал себе отдельную пустую папочку, чтобы быстренько накидать пример. То есть это не какой-то
Speaker A
гиit репозиторий, тут нет никаких веток. Чтобы на это не тратить время, я просто покажу пример конкретно работы с системными сигналами изнутри гошного приложения. Значит, как это выглядит?
Speaker A
Допустим, я вот из нашего работающего гошного приложения хочу получить какой-то сигнал. Давайте начнём конкретно с сигнала S in. То есть это сигнал, который пользователь может отправить в какой-то работающий процесс в интерактивном режиме. Ну хорошо, давайте сначала посмотрим вообще, как он
Speaker A
выглядит. К примеру, у меня всё, что будет делать гошное приложение - это засыпать на 1 час. Тайм сleп один на тайм hу. Всё. То есть мы будем засыпать на 1 час и ничего больше не делать. К чему это приведёт? К тому, что мы, когда
Speaker A
запустим наше приложение, оно просто будет вот бесконечно работать. Никакой полезной нагрузки не будет выполнять, просто будет вот запущено. Как мы его можем остановить? Мы можем в терминале нажать Ctrl C. И как раз-таки, когда я нажал Ctrl C, видите, программа
Speaker A
остановилась. Когда я нажал Ctrl C, я отправил в это работающее приложение сигнал interupt, вот этот вот сик int я отправил, хотя мог об этом даже не подозревать. И у гошного приложения есть своё поведение по умолчанию, когда оно отлавливает вот этот вот сигнал интера,
Speaker A
то есть оно завершается. Хорошо, но мы, как программисты, можем отлавливать этот сигнал и переопределять поведение программы при получении этого самого сигнал interapt int. Как это выглядит?
Speaker A
Мы с вами должны создать канал сигналов. Ну, я назову это сикhan. Выглядит следующим образом. Я создаю канал типа OS сигнал. И нам ещё необходимо сделать этот канал буферизированным. Хотя бы буфер один ему сделать для того, чтобы мы могли обрабатывать эти системные
Speaker A
сигналы и у нас приложение не зависало. Такое вот требование стандартных инструментов Гошки по отлавливанию и обработке системных сигналов. Хорошо, создали канал для сигналов и теперь пишем следующее. Signal у нас должен импортироваться пакет OS signal то.Nnatify. И в эту функцию мы передаём
Speaker A
первым аргументом канал, в который будут прилетать пойманные сигналы. Это наш сикчан. А вторым аргументом мы должны перечислить сигналы, которые мы хотим отлавливать изнутри нашего гошного приложения. Ну, я хочу отлавливать сейчас сигнал interupt, соответственно, я должен вызвать пакет Cisc, и в нём
Speaker A
есть se int. Таким образом, я благодаря вот этим вот двум строчкам буду изнутри вот этого гошного приложения отлавливать сигнал sigнт, и он уже не будет завершать мою программу. Почему? Потому что я его отлавливаю и кладу его в этот
Speaker A
канал Сикчан. То есть я переопределяю стандартное поведение гошной программы при получении сигнала Sick in. Хорошо, этот сигнал будет лететь мне вот этот вот канал. Ну давайте, для того, чтобы канал читать, я запущу отдельную рутину.
Speaker A
И в этой гарутине, в бесконечном цикле я буду вычитывать полученные значения из этого сикчан и писать их на экран. Буду писать god system signal двоеточие. И вот он, этот самый сигнал буду выводить.
Speaker A
Ну и для того, чтобы у меня main функция не завершилась раньше времени, я тут поставлю сп часок. Пускай вот поспим один часок, так сказать. Всё. Давайте я теперь запущу нашу программу и посмотрю, как она работает. Пишу gounmain.
Speaker A
Вижу, пока ничего не происходит. Почему? Потому что тут мы сказали, что будем получать системный сигнал Сигн и класть его в канал Сикчан. Запустили рутину, которая в бесконечном цикле читает из этого канала. Ну и потом main функция у нас просто заснула на 1 час. То есть по
Speaker A
сути мы сейчас просто наблюдаем, как йфункция спит. Хорошо, давайте попробуем отправить Сигн в это наше приложение.
Speaker A
Что мы можем для этого сделать? Да, точно так же нажать Ctrl C. Смотрим. Оп.
Speaker A
Во-первых, приложение не завершилось. Во-вторых, я вижу system signal двоеточие interapt. Как раз-таки вот этот вот наш вывод. О чём это говорит? О том, что мы с вами благодаря написанному коду смогли изнутри гошного приложения поймать вот этот вот сегнт,
Speaker A
переопределить поведение гошной программы при получении этого системного сигнала. Ну и вывели его на экран. Вот он interupt, тот самый signal interupt.
Speaker A
И сколько бы раз мы не нажимали Ctrl C, каждый раз мы будем просто отправлять этот самый sigнт нам в программу, и эта программа будет получать данный сигнал вот из этого канала. И потом мы просто будем его выводить на экран. Таким
Speaker A
образом, мы с вами смогли поймать этот самый сегнт. Плюс мы увидели, что се отправляется именно тогда, когда пользователь нажимает в терминале Ctrl C. Хорошо. А как теперь программу завершить? Ну вот тут вот мы вот этот значок можем нажать для того, чтобы
Speaker A
терминал просто убить. Это, наверное, один из самых простых способов сейчас завершить вот эту вот программу.
Speaker A
Нажимаем сюда, всё, у нас программа завершена. Открываем новый терминал. И давайте теперь посмотрим, что будет, если мы тут будем отлавливать не SIG in, а сиг. Давайте SIGM. Казалось бы, то же самое, просто тут другой сигнал. Что же тогда будет? Опять запущу нашу программу
Speaker A
и нажму Ctrl C. И, как мы видим, у нас просто программа завершилась. и не было вот этого нашего вывода God System signal. Почему? Потому что тут мы уже сказали, что будем отлавливать сигнал сигтерм. А сигнал sig in в это время мы
Speaker A
не отлавливаем, а значит, он сохраняет своё стандартное поведение и стандартное влияние на гошные приложения, а это просто обычное завершение гошного приложения. Мы сигнал S in никак не перехватываем, поэтому он действует на наше приложение именно так, как и задумывалось разработчиками Гошки.
Speaker A
Хорошо, а как же нам тогда отправить в это наше приложение сигнал сектер? То есть, допустим, оно там работает где-то в фоне, и мы хотим в это самое приложение отправить сектерм сигнал. Ну, для того, чтобы нам в какое-то приложение отправить сигнал, если у нас
Speaker A
нету интерактивной сессии с этим приложением, нам нужно знать айдишник процесса, который соответствует запущенному приложению. Мы можем как-то в диспетчере задач попробовать найти этот процесс. Это наше запущенное гошное приложение. Но самое простое - это прямо в самом начале main функции вывести на
Speaker A
экран айдишник запущенного процесса, чтобы нам потом его как-то специальным образом не отыскивать. То есть я просто первой строчкой вывожу FMT, Printl ID. Обычно ID пишется вот так: PID, то есть это ID, такое достаточно популярное сокращение. Двоеточие. И тут я буду
Speaker A
выводить os.pid. Всё. То есть я буду получать текущий айдишник запущенного процесса, то есть айдишник процесса, который соответствует вот этой вот нашей запущенной программе.
Speaker A
И благодаря тому, что я этот айдишник буду знать, я смогу в эту программу отправить какой-нибудь сигнал, например, сиктеterm. Хорошо, сохраняю и запускаю эту нашу программу gounmain.
Speaker A
Вот он наш процес ID. В моём случае это 38.221. Для наглядности я нажму на разделение терминалов, создам ещё один терминал на правой стороне.
Speaker A
и отсюда из этого терминала я сейчас вот в эту нашу гошную программу отправлю сиктермсигнал. Как это выглядит? Я должен написать команду kill, потом тире и сразу же сиг терм. После этого через пробел айдишник этого самого процесса, в который я буду отправлять сиктер. Окей.
Speaker A
Нажимаю Enter. И слева я вижу god system signal. Вот этот вот наш вывод, о чём он говорит? о том, что нам в сикчан пришло какое-то значение. Что за значение? А конкретно нам сюда пришёл сиктем, то есть signal terminate. Вот я этот самый
Speaker A
сигнал отправил из другого из соседнего терминала, то есть средствами операционной системы. Благодаря тому, что UniКСо операционные системы поддерживают отправку системных сигналов, я этим самым инструментом операционной системы взял и отправил сигнал прямо внутрь самописного гошного приложения. И внутри этого гостного
Speaker A
приложения я этот сигнал перехватил и вывел на экран. Ну, мы, в принципе, точно так же можем отправить SG in в наше приложение. Давайте Sig in. Нажимаю Enter. Оп. И я вижу, что у меня приложение завершилось. А почему оно
Speaker A
именно завершилось? Потому что мы никак не переопределяли поведение сигнала сиг в нашем приложении. Мы переопределили только сигнал сиктерм, а вот Сигн отработал так, как задумывалось изначально разработчиками Гошки, то есть просто завершить нашу программу. Но нам никто не мешает отлавливать сразу два
Speaker A
сигнала. Давайте я создам ещё один канал SECчан 2. Такой же канал у меня будет для передачи сигналов системных. И я его уже повешу Signal Natify, Sigчан 2, запятая Sisc in. И, соответственно, у меня будет одна рутина, которая принимает сиктерм сигнал
Speaker A
год сигтерм сигнал. И вторая такая жерутина, которая уже будет у меня обрабатывать sigчан 2. А сичан 2 принимает sig, то есть sig in system signal. Да, в принципе, и всё. Давайте запустим всё. Теперь, если я вот в этот вот
Speaker A
процесс ID буду отправлять seтерm, тогда у меня будет выводиться год sectте system signal, потому что я вот здесь вот поймал сектерм в сикчан вот в этой грутине обработался данный сигнал. Если я буду отправлять сик in, то у меня
Speaker A
будет вывод, что я получил сик in. Вот таким вот образом я могу перехватывать изнутри гошного приложения системные сигналы, которые отправляются в это самое приложение. Ну, давайте теперь попробуем внутри гошного приложения перехватить сигнал seк killill. Ну, я вот удалю обработку двух сигналов
Speaker A
одновременно буду обрабатывать только один сигнал God si killill system signal. Ну и тут я буду перехватывать ciscill.
Speaker A
Казалось бы, всё должно быть точно так же, как мы с вами уже видели. То есть мы будем перехватывать этот самый сигнал и выводить его на экран. Ну что ж, убиваю эти терминалы, запускаю новые и пишу go runmain. Запустилось наше приложение,
Speaker A
дало нам свой process ID, и теперь я в соседнем терминале пишу kill тире kill и передаю процес ID того процесса, который мы только что запустили. Казалось бы, мы сейчас должны перехватить этот сигнал и вывести его на экран, но наше приложение
Speaker A
просто убивается. Почему? Потому что я вам напомню, сик kill - это такой особенный сигнал операционной системы.
Speaker A
который мы, как программисты изнутри приложений перехватить не можем. - это такой строгий сигнал, который в любом случае просто придёт и прибьёт запущенный процесс. Для чего вообще в Гошке используется вот эта поимка сигналов? Ну, обычно она используется для того, чтобы завести в приложении
Speaker A
какой-то родительский контекст и отменять этот родительский контекст, если пришёл сигнал сиктерм. Для чего? как раз-таки для того, что мы с вами обсуждали, мы в таком случае сможем в наше гошное приложение средствами операционной системы отправить вот этот вот сиктерм. Гошное приложение примет
Speaker A
этот сиктерм и поймёт, что мы средствами операционной системы просим это гошное приложение аккуратненько завершиться.
Speaker A
Отменяется родительский контекст в гошном приложении, соответственно, отменяются все дочерние контексты до конца. аккуратненько всетинки, все запросики дорабатываются. И только после этого наше гошное приложение спокойно завершается. И это вместо того, чтобы просто через диспетчер задач вот так вот в наглую грубо завершать гошное
Speaker A
приложение, которое в этот момент могло ещё дорабатывать какие-то запросы. Нет, мы вместо грубого завершения мягенько отправляемся к терм и просим, пожалуйста, гош на приложение аккуратненько завершись. И как это выглядит? Мы тут создаём в самом начале main функции какой-нибудь родительский
Speaker A
контекст, унаследованный от контекстбэкграунда. То есть это контекст viscel унаследованный от контекст бэкграунд. После этого мы начинаем отлавливать сигнал сиктер, потом запускаем отдельную рутину. И в этой самой рутине мы будем ждать, когда же нам что-нибудь придёт, в Сикчан. И как
Speaker A
только в сикчан нам что-нибудь придёт, это значит, что в наше приложение был отправлен сигнал сектерам. В таком случае мы просто берём и отменяем этот самый родительский контекст. Да, по сути, и всё. После отмены этого родительского контекста все дочерние
Speaker A
контексты будут также отменяться. А если мы отменили контекст, напоминаю вам из предыдущих уроков, у нас начинают завершаться всерутинки, запущенные в нашей программе. И наша программа начинает делать так называемый graceful shutdown. Это когда мы не жёстко прибиваем приложение, а аккуратненько
Speaker A
просим его завершиться, давая доработать всю начатую работу. Конкретно, если в этот момент обрабатывались какие-то пользовательские запросы, то они будут аккуратненько доработаны, и потом рутинки, которые за это отвечают, будут завершены, и наше приложение аккуратненько в соответствии с принципами Grceful Showdown завершится.
Speaker A
Всё благодаря тому, что мы воспользовались функциональностью Unix операционных систем, а конкретно смогли поймать системный сигнал и аккуратненько завершиться. Но в Гошке есть более интересная конструкция для вот этого всего. Обычно делают следующим образом: signal тоnotify context. Сюда мы просто
Speaker A
можем передать контек и отлавливать мы с вами будем сигналы ciscol.sm функция notify context нам возвращает context и cancel функцию context cancel.
Speaker A
Что это значит? Вот мы одной вот этой вот строчкой создали себе контекст, ну, с возможностью его руками ещё отменить.
Speaker A
Вот мы создали себе контекст, унаследованный от контекстбэкграунда, который автоматически отменится, если нам придёт сигнал сектерм. Ну, как это проверить? Давайте просто опять же я запущу гарутину и в этой гаррутине сделаю следующее. Я буду писать до отмены контекста. Вот тут вот буду
Speaker A
ждать контекст дан и буду писать здесь после отмены контекста восклицательный знак. Ну и функцию кал я сейчас никак не буду использовать, поэтому сделаю следующим образом. Ну хорошо, в принципе, тут нету большого смысла в отдельной рутине, можно и без
Speaker A
неё обойтись. Убираем сп. Вот так вот тоже всё будет работать. То есть наша программа запускается, показывает нам свой process ID. И после этого мы завязываем контекст, вот этот вот CTX на системный сигнал сектер. После этого выведем до отмены контекста и будем
Speaker A
ждать, когда же контекст отменится. А когда он отменится, отменится этот контекст тогда, когда наше приложение примет сигнал сектер. Ну хорошо, давайте запустим нашу программу. Я тут пишу runmain.go. Вот он нашess ID. И мы видим вывод до отмены контекста. И всё, наша
Speaker A
программа зависла. Она ждёт чего-то. Она будет так бесконечно ждать до тех пор, пока мы в неё не отправим сигнал сиктем.
Speaker A
Ну давайте это и сделаем. Пишук term и передаю ID. Нажимаю Enter. Оп. После отмены контекста, то есть главный контекст нашей программы отменился, как только мы в нашу программу отправили сектем. Ну а соответственно отмена контекста. Если правильно организовать гошное приложение, это самая отмена
Speaker A
контекста за собой ведёт Graful Shutdown. Все гарутинки аккуратненько завершаются. Мы дорабатываем все пользовательские запросы, до конца доводим всю необходимую работу для корректного завершения приложения и, собственно, завершаем наше приложение.
Speaker A
Вот таким вот образом используются системные сигналы и особенно в связке с Гошкой и с её контекстами. более конкретно на таком хорошем практическом примере, мы это с вами рассмотрим совсем скоро, когда будем писать эээнд-приложение, собирающее всё то, что мы прошли в рамках этой части. Есть ещё
Speaker A
одна вещь, о которой нам стоит поговорить в рамках темы системные сигналы, а именно как системные сигналы работают в связке с докером и что мы, как программисты, должны учитывать, чтобы наши приложения, запущенные внутри докерконтейнера, корректно обрабатывали эти самые системные сигналы. Ну давайте
Speaker A
для начала напишем довольно простую гошную приложуху. Она будет сначала по классике выводить свой process ID.
Speaker A
process ID = OS Get process ID. После этого я буду вызывать пакет Signal и в нём функцию Notify Context для того, чтобы получить контекст, который будет отменяться при сигнале сиктем, напоминаю, сиктер - это такой сигнал, который говорит нашему приложению:
Speaker A
"Пожалуйста, доработай до конца всю начатую работу и после этого корректно завершись". Получим из этой функции контекст. И функцию cancel я получать оттуда не буду. То есть у нас функция nattify context, она возвращает, собственно, контекст, который будет отменён при получении приложением
Speaker A
перечисленных сигналов, в нашем случае сектер. И вторым возвращаемым аргументом из функции Notify context нам приходит cancel функция, при помощи которой мы с вами вручную можем отменить контекст, пришедший нам первым возвращаемым значением. Но мы этого сейчас делать не будем, поэтому на месте cancel функции я
Speaker A
просто нижнее подчёркивание ставлю, как бы пропуская это самое возвращаемое значение. О'кей. Теперь давайте опишем некоторую работу нашего приложения в отдельной функции job. Эта функция будет принимать контекст для своей работы. И в бесконечном цикле при помощи конструкции Select, это наша функция Job, будет
Speaker A
смотреть либо, что у нас контекст отменён. И в таком случае нам нужно завершить нашу работу. Выведу на экран job, то есть работа выполнена. И после этого завершу функцию. Иначе раз в секунду мы с вами будем выполнять какую-то работу.
Speaker A
Ну, просто буду выводить на экран application job. Ну и давайте ещё целочисленную переменную заведу, при помощи которой мы будем как бы эмулировать прогрессию нашей работы. То есть у нас каждый раз раз в секунду в нашей функции j будет выводиться
Speaker A
application job 1, потом через секунду Application Job 2, ну и так далее. Просто эмуляция какой-то работы. Если же контекст, который был передан в нашу функцию job, отменится, в таком случае мы выведем, что наша работа завершена, и завершим эту самую работу. Хорошо,
Speaker A
давайте в мейне будем эту нашу самую функцию job вызывать и передавать в неё контекст, который у нас завязан на сектерм системный сигнал. После того, как работа нашего приложения завершится, я в конце ещё дополнительно выведу application stop correctly. О чём будет
Speaker A
нам говорить вот этот вот финальный вывод? Application stopped correctly. Этот вывод нам будет говорить о том, что наше приложение во время исполнения своей работы было не каким-то варварским способом прибито, а было остановлено аккуратно через отмену контекста, благодаря чему у нас вся работа до конца
Speaker A
завершается, и мы в конце будем видеть этот вывод. А иначе, если наше приложение будет, так сказать, грубо прибито посреди своей работы, то мы не увидим в конце этот вывод. Почему? Да, потому что приложение где-то посреди выполнения функции j резко становится и
Speaker A
всё, и до конца свои инструкции оно не доработает. Ну давайте сейчас проверим, как это просто у нас на локальной машине работает. Запускаю нашу приложуху.
Speaker A
Gorunmain. Запустилась приложуха. Вот её process ID. И пошла какая-то работа. Всё, наше приложение выполняет какую-то свою работу. И в соседнем терминале я отправлю в это наше самое приложение, у которого процес ID 70.418.
Speaker A
Сигнал сиктер. Ну давайте я это сделаю. Отправляю сикterm в это наше про ID и смотрим, что происходит.
Speaker A
Job done. Application stopped correctly. Почему это произошло? Да потому что мы аккуратно попросили наше приложение, приложение "Пожалуйста" завершись через отправку в него сигнала сиктер и что самое главное, мы были готовы этот самый сигнал сиктер внутри нашего приложения корректно обработать. Мы на этот сигнал
Speaker A
повесили отмену контекста и на этот контекст повесили выполнение нашей Джобы. О'кей. Давайте теперь посмотрим, как будет выглядеть вывод нашего приложения, если мы его варварским способом через сик killill прибьём.
Speaker A
Запускаем приложуху, получаем её process ID. И теперь пишу kill. Явно передаю kill и ID. Нажимаю Enter, сигнал killed.
Speaker A
И мы не видим никакого вывода application stopped correctly. Почему? Да, потому что мы отправив в наше приложение сигнал сики просто не дали ему корректно доработать до конца.
Speaker A
О'кей. То есть мы увидели, что наше приложение в принципе действительно может останавливаться корректно, совершать так называемый гсful shutdown, то есть дорабатывать всю свою работу до конца после просьбы завершиться, если мы в это приложение отправим сигнал seктерм. Хорошо. Теперь нам нужно
Speaker A
немножечко поговорить про докер. Когда наша гошная приложуха работает просто у нас локально на компьютере, мы можем спокойно в соседнем терминале отправить в неё какой-нибудь системный сигнал сик int или же сиктер или же Kill, ну, вообще какой захотим. Но а если мы
Speaker A
запускаем нашу приложуху внутри докерконтейнера, как нам тогда внутрь этой приложухи отправить какой-то системный сигнал? Ведь мы помним, что докерконтейнер - это изолированная среда без 5 минут, условно говоря, отдельная операционная система. И как нам со своего локального компьютера внутрь
Speaker A
этого самого докерконтейнера, да и не просто внутрь докерконтейнера, а конкретно в нашу гошную приложуху отправить какой-нибудь системный сигнал?
Speaker A
Для этого в Докере предусмотрены следующие команды. Причём одну из этих команд мы с вами постоянно использовали.
Speaker A
Первая команда - это Docker Kill. С ней мы уже знакомы. Каждый раз, когда мы с вами писали doкеer kill и после этого передавали айдишник или же название какого-то контейнера, который мы хотим прибить, то внутрь этого самого контейнера отправлялся сигнал seки
Speaker A
killки kill. Но может быть такое, что у нас внутри докерконтейнера работает не только наша гошная программа, а ещё какие-то другие процессы. И вот сейчас я вас попрошу быть повнимательнее. Какие же ещё процессы могли работать внутри нашего докер конконтейнера, когда мы
Speaker A
запускали в нём гошное приложение? Если мы с вами при старте нашего докерконтейнера запускали нашу гошную приложуху через командуmain.
Speaker A
То мы помним, что эта самая команда gounmain. Первая - это скомпилировать исполняемый файл, а вторая - это запустить скомпилированный исполняемый файл.
Speaker A
процесс компиляции исполняемого файла и процесс запуска исполняемого файла - это два разных процесса в докере по умолчанию первый запущенный процесс имеет айдишник один то есть процесс id пид у процесса который компилировал исполняемый файл равен один а процесс id
Speaker A
у исполняемого файла уже не один уже там может быть какой-нибудь пять или ещё что-нибудь такое казалось бы и ладно То есть мы с вами собрали наш исполняемый файл, и процесс сборки этого файла имел айдишник один. Но когда мы сам
Speaker A
исполняемый файл запустили, у него уже процесс ID не один. Ну казалось бы, и что такого? А на самом деле тут кроется вторая проблема, которую я обещал, что мы с вами обсудим, когда мы обсуждали, какие проблемы запуска гошного приложения внутри doкеer контейнера
Speaker A
через командуmain. Значит, пишем gounmail. Внутри dokerкерконтейнера. Первым делом запускается процесс по сборке нашей гошной приложухи. Этому процессу присваивается айдишник один. Потом запускается дочерний процесс, а именно исполнение скомпилированного исполняемого файла. процесс, который компилировал нам наш исполняемый файл, имеет айдишник один, а непосредственно
Speaker A
запуск исполняемого файла, процесс, который соответствует этому исполняемому файлу, имеет там айдишник пть, ну или какой-нибудь рандомный айдишник, там 2, 3, 17, например. Главное, что не один.
Speaker A
То есть процесс ID у нашей итоговой запущенной гошной программы уже не один. Ну, обозначу, что запуск исполняемого файла - это дочерний процесс относительно процесса, который этот исполняемый файл собирал. Так вот, к чему нам все эти тонкости. Когда мы с
Speaker A
вами в терминале на нашем локальном компьютере писали do kill и там передавали айдишник или название контейнера, мы внутрь этого самого контейнера отсылали сигнал сиккиill. Но мы не просто как-то абстрактно в контейнер отсылали сиккиill, а конкретно отсылается сигнал внутри докерконтейнера
Speaker A
тому процессу, который имеет айдишник один. Соответственно, когда мы с вами писали docker kill, мы отправляли сик kill системный сигнал внутрь докер контейнера процессу с айдишником один, потому что докер считает процессы с айдишником один основными главными процессами, запущенными внутри
Speaker A
докерконтейнера. И тем самым мы прибивали процесс компиляции исполняемого файла с нашей программой. Но казалось бы, какое отношение процесс компиляции исполняемого файла имеет к самому исполняемому файлу? А такое, что команда gounmain.
Speaker A
Процесс запуска исполняемого файла дочернем к процессу сборки этого самого файла. А в UКБ системах, когда мы убиваем родительский процесс, тем самым убивается автоматически и дочерний.
Speaker A
Именно поэтому у нас с вами работала команда Docker Kill. Несмотря на то, что сигнал сикill на самом-то деле в нашу итоговую исполняемую программу не попадал. Когда мы с вами писали Docker kill, то у нас летел сигнал сикиill внутрь докер контейнера в процесс с
Speaker A
айдишником один. И так как этот процесс с айдишником один был родительским для процесса с исполняемым файлом, то нам удавалось при помощи этого завершить все процессы внутри докер конконтейнера. И тем самым мы останавливали этот докер-контейнер. Ну, казалось бы, опять
Speaker A
же, ну, хорошо, для чего нам все эти тонкости? А эти тонкости начинают работать, когда мы вдруг в нашу работающую программу хотим отправить сигнал не сик killill, а сиктерм для того, чтобы не прибивать нашу программу жёстко, аккуратненько попросить её
Speaker A
завершиться, доделав всю начатую работу. Как при помощи докера отправить внутрь докер контейнера сигнал сиктер? Делается это при помощи команды docker stop. И после этого точно также передаём название либо айдишник запущенного контейнера. Но что будет происходить, когда мы будем писать Docker Stop?
Speaker A
Команда Docker Stop, написанная у нас на локальном компьютере в терминале, отправит в переданный контейнер сигнал сиктем. Этот сигнал сиктер придёт к нам в контейнер. Но в какое конкретно приложение пойдёт сигнал сиктер? Сигнал сиктер, точно так же, как и сигнал сик
Speaker A
killill, пойдёт в приложение с айдишником один. Соответственно, сигнал сиктер придёт в процесс, который нам компилировал исполняемый файл. Но вот в случае с сигналом сиктерм уже так не будет работать, что если мы в родительский процесс отправим сигнал сектерм, то этот самый сигнал сектерм
Speaker A
сразу пойдёт во все дочерние процессы. Нет, так не произойдёт. А что это значит? Это значит, что сигнал сектер так и останется в этом самом процессе, который нам компилировал нашу итоговую запускаемую программу. Но до нашей программы сигнал сектер не дойдёт. То
Speaker A
есть наша программа не узнает о том, что мы вообще-то хотим, чтобы она уже аккуратненько завершилась. И программа продолжит свою работу. Но Doer работает таким образом, что если после отправки команды Docker Stop внутрь Docker контейнера, в итоге Docker контейнер не
Speaker A
остановился, а у нас это произойдёт, потому что до нашей исполняемой программы сигнал сиктерм не дошёл. Она продолжила работу, а значит и Docker контейнер продолжит свою работу. Так вот, если после отправки команды Docker стоп в Dockerконтейнер через 10 секунд
Speaker A
Docker контейнер не остановился, то Docker считает, что те процессы, которые внутри контейнера работают, за 10 секунд не успели совершить так называемый shutдаун. И это приводит к тому, что Докер устаёт просто ждать, когда же контейнер завершится, и отправляет в
Speaker A
него сиккиill. В итоге сиккиill приходит в родительский процесс. У нас все процессы завершаются, и, соответственно, докер контейнер тоже завершается. В конечном счёте происходит такой неприятный момент. Мы вроде как внутри нашего гошного приложения научились обрабатывать приходящий сигнал сиктерм.
Speaker A
Мы готовы его принять, мы готовы его корректно обработать и совершить graceful shdown. Мы с этим знанием запускаем эту нашу приложуху внутри докерконтейнера. Docker контейнер какое-то время работает. И после этого мы хотим его аккуратненько завершить.
Speaker A
Пишем docker стоп, передаём айдишник контейнера с нашим работающим приложением. Доке отправляет сектерм внутрь контейнера. Сектерм попадает в process и ничего не происходит. Докер ждёт, ждёт, ждёт. 10 секунд ждёт, устаёт ждать и отправляет, короче, сиккиill вообще в наш контейнер. И сразу наш
Speaker A
контейнер варварским способом завершается. И казалось бы, зачем мы тогда внутри нашей приложухи учились обрабатывать системные сигналы, если в итоге системный сигнал сектер, благодаря которому мы реализуем Showdown, не доходит до нашей приложухи. Что мы вообще с этой проблемой можем сделать?
Speaker A
Ну, давайте сначала посмотрим, чем эта проблема была вызвана. Эта проблема была вызвана тем, что мы внутри докер контейнера нашу приложуху запускали через командуun main.go. Gorunmain. По капотом вызывает два процесса. Первый процесс нам компилирует исполняемый файл, а второй процесс - это, собственно, запуск самого
Speaker A
исполняемого файла. Но так как мы при запуске докер конконтейнера первым делом билдили нашу программу, компилировали её в исполняемый файл, то процесс ID1 закрепился за вот этим процессом компиляции. И для самой нашей программы уже процесс ID будет другой, потому что
Speaker A
процесс ID1 занят. И именно из-за этого, когда мы внутрь нашего doкерконтейнера отправляем Сиктер, то Сиктер не доходит до нашей программы, потому что СиктерM пойдёт именно в процесс с айдишником один. Что это значит? Это значит, что если мы хотим, чтобы до нашей приложухи
Speaker A
внутри докерконтейнера доходил корректно сигнал сектер, то мы должны каким-то образом обеспечить, чтобы наша вот эта работающая приложуха имела процесс ID1.
Speaker A
Ведь если у нашей приложухи будет процесс ID1, то тогда, когда в Docker контейнеer будет приходить сигнал сиктер, он будет идти в process.
Speaker A
А process ID1 - это наша работающая программа. Получается, нам каким-то образом нужно обеспечить, чтобы у нашей работающей программы процесс ID был именно один. И мы этого не можем сделать, когда мы запускаем внутри docker контейнера нашу приложуху через командуmain.
Speaker A
Но что будет, если мы внутри dokerкеer файла в директиве cmd, которая запускает основной процесс в Docker контейнере, будем писать не main.go, а мы разнесём сборку нашей программы и её запуск в две отдельные команды. К чему это приведёт?
Speaker A
Это приведёт к тому, что процесс компиляции нашей программы в исполняемый файл будет запущен во время сборки Docker иджа. И потом уже при запуске Docker контейнера всё, что необходимо будет сделать - это запустить уже собранный исполняемый файл. К чему это
Speaker A
приведёт? Это приведёт к тому, что вот этот вот исполняемый файл будет запущен первым внутри нашего докерконтейнера. А это, в свою очередь, приведёт к тому, что наш исполняемый файл будет иметь process ID1, потому что он просто первый был запущен в нашем doкерконтейнере. А
Speaker A
это, в свою очередь, приведёт к тому, что при написании команды Docker Stop внутрь нашего doкеer контейнера будет отправляться SeтерM. Сиктер будет отправляться в process, а Process ID1 - это наша приложуха. И это, наконец-таки, приведёт к тому, что сиктерм, отправляемый через команду
Speaker A
Docker Stop, будет внутри докерконтейнера доходить до нашей работающей программы, которая готова этот самый сиктерм принять, корректно обработать и совершить shdown. И это и есть тот самый второй момент, который я хотел с вами обсудить, когда мы разговаривали про минусы запуска гошного
Speaker A
приложения внутри докер контейнера через команду runmain.goo. Хотя на первый взгляд кажется, что команда-то хорошая, она работает, почему бы при помощи неё не запускать наши приложухи внутри докерконтейнера? А всё на самом деле сложнее, и нам практически всегда гораздо выгодней разносить компиляцию и
Speaker A
запуск нашего приложения на две отдельные команды. Да, не просто на две отдельные команды, а ещё и саму компиляцию выносить на этап сборки Docker Imджа, а на этап запуска Docker контейнера оставлять лишь запуск исполняемого файла. Это, во-первых, ускоряет запуск Doке контейнера, потому
Speaker A
что мы один раз собрали нашу приложуху на этапе сборки Docker иджа, а потом при запуске Docker контейнера из этого самого Docker иджа нам просто остаётся запустить исполняемый файл. Он уже собран, он уже скомпилирован. Это во-первых. А во-вторых, таким образом мы
Speaker A
обеспечиваем корректную доставку системных сигналов до нашего приложения внутри работающего докерконтейнера. Ну а теперь нам остаётся посмотреть, как это всё работает на деле. Но для этого возвращаемся в VS-код. Тут нас встречает наша описанная гошная программа, которая умеет принимать системный сигнал сектер
Speaker A
и после обработки этого сигнала корректно завершать начатую работу и выводить информацию о том, что мы завершились корректно. Давайте теперь опишем два докер файла. Один docker файл будет описывать запуск гошного приложения через командуmain.
Speaker A
Второй же Docker файл будет отдельно на этапе сборки Docker иджа компилировать исполняемый файл и отдельно на этапе запуска Docker контейнера запускать лишь только полученный скомпилированный исполняемый файл. И мы наглядно увидим разницу, что у нас в первом случае просто отсутствует возможность корректно
Speaker A
завершить наше приложение, несмотря на то, что само приложение умеет принимать сигнал сектерм и завершаться корректно.
Speaker A
О'кей, но для начала я объявлю себя модулем go mode инит. Для чего? Потому что команда gobild, она требует того, чтобы собираемая сущность была модулем.
Speaker A
Поэтому, чтобы мы могли наглядно посмотреть на работу команды GoBild, я объявляю себя модулем. Ну вот GМ файл появляется. Хорошо, поехали. Описываем первый наш Docker файл. Назову его Dockerфайл тоbet. Почему BТ? Потому что мы в нём будем использовать одну из
Speaker A
самых неоптимальных стратегий запуска гошного приложения. Ну, поехали. Описываю базовый Docker образ from Goleng 124 Bookworm.
Speaker A
После этого создаю себе рабочую директорию/upp. После этого копирую в эту самую рабочую директорию все файлы нашего проекта. И после этого я могу, конечно, написать гомойди, но у нас никаких сторонних зависимости нету, поэтому нет никакого смысла писать гомойди. Я сразу мог бы
Speaker A
написать cmd go run. Но давайте для ещё пущего интереса я буду запускать нашу гошную приложуху не через gounmain. А вообще создам себе makeфай.
Speaker A
В этом make-файле опишу target севи run. И в этом таргете я напишу runmain. Ну и, соответственно, в doкеer файле я уже буду писать make run. Для чего я хочу ввести makeфайл? Да потому что тут вообще начнётся лютый сюр. У нас docker запускает makeфайл. А
Speaker A
make - это тоже какой-то процесс. После этого makeile запускает goormain. А gounmain. - это ещё два дочерних процесса. В итоге начинается полная веселуха. Ну, сейчас мы посмотрим, в чём дело. Собственно, давайте я из этого docker файла соберу Docker image и
Speaker A
запущу этот самый Docker image. Для этого я в терминале пишу Docker build f в этот раз. То есть я должен конкретно передать, какой файл я буду собирать. То есть я конкретно должен передать из какого файла я буду собирать docker
Speaker A
image. Почему? Да, потому что Doке по умолчанию всегда будет искать в переданной директории файл с названием Dockerфайe. А так как у нас есть вот эта приставка тоbet, то без дополнительных указаний Docker не сможет найти этот файл, поэтому я его явно передаю через
Speaker A
флажок F тире F dockerfile.bet. После этого задам название создаваемому Docker image, ну, назову это Docker Image go bet signal. И в конце нужно написать точку, чтобы докер знал, что вот переданный файл нужно искать в текущей директории. Нажимаю Enter. Идёт
Speaker A
сборка моего Docker иджа. Docker image готов. Теперь я могу его запустить. Docker run gobet signal Enter.
Speaker A
Gounmain.go. Мы видим, мы видим ID какой у нашего приложения. Совсем не один, на целых 707 больше, чем нужно. Но, несмотря на это, вроде как приложуха запущена, работает и выполняет какие-то свои задачи. Ну хорошо, давайте теперь я попробую внутрь этого dokerкер
Speaker A
контейнера отправить сигнал сиктем. Открываю соседний терминал, пишу DockerPS. Вот он мой запущенный Docker контейнер через make run. И здесь я скопирую айдишник этого самого контейнера. После этого напишу docker стоп и передаю айдишник контейнера. Что я ожидаю? Я ожидаю, что в этот наш
Speaker A
работающий Dockerконтейнер Docker отправит сигнал сиктер, а наше приложение, оно уже умеет работать с сигналом Сиктер. Всё должно быть хорошо.
Speaker A
Мы с вами локально, когда проверяли, действительно, наше приложение корректно обрабатывает этот сигнал. Ну давайте я теперь отправлю эту команду Docker Stop.
Speaker A
И внимание, сейчас на левый терминал отправляю. Мы с вами остановили именно процесс make-файла, но процесс компиляции нашего приложения и процесс запуска нашего приложения мы не остановили. Почему?
Speaker A
Потому что процесс ID1 имел процесс make йк-файла, и мы с вами завершили только лишь make-файл. После этого наша приложуха во-первых продолжила работать, а потом через 10 секунд Докер понял, что докер контейнер не завершается, и он отправил туда SEки
Speaker A
Kкиill, тем самым насильственным образом прибив наше приложение. И наше приложение даже не вывело Application Stoped Correctly. Ну потому что оно и не было корректно остановлено. Можем ещё раз проверить. Ещё раз запускаю Docker контейнер из нашего docker image. Пишу
Speaker A
dockerps. Вот он айдишник нашего контейнера. и пишу dock стоп передаю айдишник, нажимаю Enter. И мы видим, наше приложение продолжает свою работу.
Speaker A
То есть ему как бы всё равно на сектеterm. Почему? Потому что сектер пришёл именно в makeфайл, в процесс сй-файлом, но в нашу приложуху никакого сектерма не пришло. И потом докер устал ждать и через 10 секунд просто прибил вообще весь doкеer контейнер. О'кей,
Speaker A
давайте теперь опишем Docker файл, в котором мы будем корректно запускать наше гошное приложение. Я скопирую вот этот файл тоabbet, но назову его то, что будет происходить в нашем Docker file.good. Точно также уже на основе готового базового иджа с гошкой мы
Speaker A
начинаем строить свой doкеer image. Создаю рабочую директорию/up. Копирую туда проект. И после этого я отдельно соберу наше гошное приложение, отдельно его скомпилирую в исполняемый файл. Этот исполняемый файл я положу в директорию app, назову его ex, ну, сокращённо execute. И собирать я буду программу
Speaker A
main. После этого в директиве cmd, которая выполняется уже непосредственно при запуске Docker контейнера, я буду просто запускать уже готовый исполняемый файл. Ну давайте теперь соберём Docker image из этого Docker файла, запустим наш вновь собранный Docker image и посмотрим, как гошное приложение внутри
Speaker A
этого Docker контейнера обрабатывает входящие сигналы. Для того, чтобы из этого docker файла собрать себе docker image, я в терминале пишу docker build файл передаю dockerfile.g собираемого docker image я передам go good signal. Ну и переданный dockerфайл, вот этот dockerfile.g нужно искать в
Speaker A
текущей директории. Нажимаю Enter. Идёт сборка моего docker иджа. Docker image собран. Теперь я могу его запустить.
Speaker A
Docker run go good signal enter. Как мы видим, у нашего приложения process ID один. О чём это говорит? Это говорит о том, что сигналы, отправленные через докер, в наш докерконтейнер, они придут именно в нашу приложуху. О чём это говорит? Это говорит о том, что если
Speaker A
я сейчас возьму вот этот вот наш работающий докерконтейнер, в котором сейчас трудится наша приложуха, и попробую через Docker Stop остановить этот Docker контейнер, то команда Docker Stop отправит в этот Docker контейнер сигнал сектерм. И так как наша приложуха, запущенная внутри этого докер
Speaker A
контейнера, имеет процесс ID1, то эта наша приложуха данный сигнал примет и корректно обработает. Внимание на левый терминал. Нажимаю Enter. Job done, application stopped correctly. Казалось бы, одна и та же гошная приложуха, один и тот же гошный код работает совершенно
Speaker A
по-разному, в зависимости от того, как именно мы запускаем наши докерконтейнеры. Ну и давайте сделаем из этого всего короткий вывод. Когда мы с вами запускаем гошное приложение вне до Doконтейнера, локально у нас на машине, в целях, например, тестирования нет
Speaker A
ничего плохого, чтобы написать gounmain. Это даже хорошо, это даже удобно. Но если мы запускаем наши гошные приложухи внутри докер конконтейнера, и нам важно, чтобы эти гошные приложухи умели принимать и обрабатывать системные сигналы, тогда мы должны отдельно билдить исполняемый файл из нашей гошной
Speaker A
программы. И только после этого сбилный скомпилированный исполняемый файл запускать для того, чтобы этот самый исполняемый файл получил процесс ID1 и корректно принимал и обрабатывал системные сигналы, которые мы направим внутрь докер контейнера. Повторюсь, это и был тот второй момент, который я хотел
Speaker A
с вами обсудить, когда мы с вами обсуждали, почему внутри докер контейнера запускать гошные приложухи через makeunmain.
Speaker A
Это нехорошо, у этого есть последствия. И нам вместо этого нужно отдельно билдить или же компилировать исполняемый файл и в момент запуска докерконтейнера только лишь запускать этот исполняемый файл. Ну а пока я считаю тему системных сигналов и работу с системными сигналами
Speaker A
из внутригошного приложения закрытой. [музыка] Следующее, о чём мы поговорим - это логирование. Быстронько пробежимся по верхам, обсудим основные моменты, что это такое, для чего нужно, зачем и как используются. Но прям супер вглубь я копать не буду и мегаподробно всё
Speaker A
объяснять, потому что, к сожалению, в этой части курса погошки у нас осталось не так уж и много времени. Ну, поехали.
Speaker A
Опять у меня пустой проект. Просто папочка стадии, никакой не Gitрепозиторий, просто обычная папка с main. Файлом. Что же такое логирование?
Speaker A
Логирование - это инструмент, помогающий фиксировать и сохранять происходящее внутри приложение во время работы этого самого приложения. Условно говоря, наша ээнд- приложуха запускается, и в неё начинают лететь какие-то пользовательские запросы. Наша энд-приложуха начинает делать какие-то запросы в базу данных, как-то внутри
Speaker A
себя обрабатывать данные. Во время всех этих операций могут происходить какие-то ошибки, какие-то непредвиденные обстоятельства или, наоборот, всё может идти в штатном режиме. Так вот, иногда у нас, как у разработчиков, может возникнуть потребность узнать, что же происходило с нашим приложением в
Speaker A
какое-то конкретное время. К примеру, к нам пришёл пользователь и говорит: "15 декабря в 16:05 у меня произошла ошибка, и мы, как разработчики этого приложения, в котором пользователь столкнулся с ошибкой, мы хотим уметь узнавать, а что же за ошибка
Speaker A
там была, во сколько она произошла, что ей предшествовало, что в этот момент наше приложение ещё делало. То есть, по сути, мы хотим куда-то записывать всё происходящее с нашим приложением. Это, наверное, можно сравнить с чёрным ящиком в самолётах. То есть вот в самолёте есть
Speaker A
вот этот вот самописец. Иногда его называют чёрным ящиком. То есть это инструмент, который постоянно во время всего полёта самолёта записывает всё происходящее с самолётом: высоту, скорость, угол наклона и так далее, и так далее, и так далее. Так вот,
Speaker A
лагирование - это нечто похожее, только мы с вами разрабатываем не самолёт, а мы с вами разрабатываем эээнд приложение.
Speaker A
То есть мы благодаря логированию можем записывать всё происходящее в нашем приложении, куда-то это сохранять и потом при необходимости этот, так сказать, чёрный ящик мы можем открыть и посмотреть всё, что происходило с нашим приложением в определённый промежуток времени, какие действия производило наше
Speaker A
приложение, какие там происходили ошибки и так далее. Значит, как логирование выглядит именно в программном коде. На самом деле это очень похоже на уже знакомый нам FMT Printen, но, естественно, логирование - это более продвинутый инструмент, чем просто FMT Printaln. Ну давайте я вот напишу FMT
Speaker A
Print Allen Hello World. Ну казалось бы, я вот могу запустить это приложение, и у меня выводится Hello World. Но я не знаю, когда я сделал этот вывод на экран. Я не знаю, в каком месте моей программы я сделал этот вывод на экран.
Speaker A
Я не понимаю этот вывод на экран. Он означает, что-то идёт в штатном режиме или что-то идёт не так или вообще какая-то ошибка произошла. Я ничего не могу понять по вот этому вот простому выводу. Опять же, я даже не могу понять,
Speaker A
в какой функции этот самый вывод у меня был произведён. То есть вот у меня пускай будет функция фу, и в ней будет как раз-таки этот самый FMT Printl Hello World. Я просто из мейна вызову функцию F. И точно так же я не пойму в итоге-то,
Speaker A
где была напечатана эта информация, к чему она вообще относится. И тут нам на помощь приходит логирование. Для того, чтобы пользоваться логированием, существует большое множество различных библиотек, как встроенные вeng, так и сторонние. Но я воспользуюсь одной очень популярной сторонней библиотекой,
Speaker A
которая называется zapб loger. Для того, чтобы нам этот самый zaploger установить, мы должны сами быть сначала модулем go mode init study. И после этого пишем go getgo.org/zap.
Speaker A
Это библиотека, которая предоставляет большое количество инструментов для логирования. Сущность внутри языка программирования которое собственно логирует что-то, называется логером.
Speaker A
Давайте создадим этот самый логер. Для этого я вызываю эту самую библиотеку z то. New. И тут огромное количество различных логеров мы можем создать. Ну давайте сначала я вызову функцию New Development. Она нам возвращает указатель на логер и возможную ошибку.
Speaker A
Ну хорошо, давайте я получу логер, запятая ошибка. И если какая-то у меня ошибка была, тогда просто кину панику.
Speaker A
О'кей, я создал логер. Теперь, что я с помощью него могу делать? Я с помощью логера могу, собственно, логировать происходящее внутри моего приложения.
Speaker A
Давайте я посмотрю, какие у этой структуры есть методы. И тут огромное количество методов есть debug, erorр, фатал, info и так далее, и так далее.
Speaker A
Что это за методы? Это так называемые уровни логирования. Для чего нам нужны уровни логирования? Для того, чтобы разделять, с какой целью та или иная информация была, собственно, залогирована. Ну давайте начнём с самого низкого уровня логирования - это баг.
Speaker A
Вот уровень debug. И у нас есть возможность сюда передать какую-нибудь строчку. К примеру, это будет hello loger.
Speaker A
Хорошо, давайте ещё раз запустим нашу программу. Go runmain.go. Опа. И как я вижу, там, где я сделал вывод, с помощью логера, у меня вывелась как строка, которую я хотел вывести, так и большое количество дополнительной информации. У меня вывелась дата, у меня
Speaker A
вывелось время, когда именно был произведён вот этот вот лог, когда вот эту вот строку я залогировал. После этого у меня идёт уровень логирования.
Speaker A
Повторюсь, уровень логирования - это некоторые условные обозначения, которые нам говорят, для чего именно логируется данная информация. Далее мне логируется место в моей программе, где был вызван этот самый лог. Directoria stud main.gov file девятнадцатая строчка. Вот оно. То есть теперь я даже могу знать, откуда
Speaker A
именно было произведено логирование. Ну и, собственно, сама строчка, которая была залогирована. Хорошо, это был уровень логирования debug. Какие ещё у нас есть уровни? loger.info - это уже более высокий уровень логирования.
Speaker A
Hello infolog. Ещё раз давайте запустим нашу программу. И как мы видим, то, что мы с вами залогировали на уровне debug, у нас пометилось как debug. То, что мы залогировали на уровне info, у нас залогировалось, собственно, как уровень инфо. Соответствующее время,
Speaker A
соответствующее место в программе и соответствующая строка, которую мы передали. влогер. Дальше у нас есть уровень warning. Ну, сокращённо warn.
Speaker A
Hello, warn logel. Запускаю программу. И теперь я вижу, что всё то же самое, как и было. Только теперь у меня добавился лок уровня warрning плюс ещё какие-то непонятные строки. Что это за строки? Это стек вызовов, который появляется при
Speaker A
логировании, начиная с уровня warрning. Для чего это надо? Давайте мы сейчас к этому вернёмся и рассмотрим ещё один уровень лога. Loger error. Hello, error logel. Запускаю программу. И теперь я вижу, что у меня помимо предыдущих трёх уровней добавился ещё лог уровня Eror.
Speaker A
Вот уровень Erроor. Вот соответствующая ему строка. Что это за стеки вызовов у уровней Warрning и уровней Eror? Эти стеки вызовов призваны нам, как программистам, помочь понять, откуда взялась ошибка или же предупреждение, почему эти самые стеки вызовов у нас
Speaker A
печатаются только для уровня warрning и error. Да потому что уровень debug вообще нужен для того, чтобы логировать какую-то отладочную информацию. Ну, например, мы у нас в backндend приложении хотим понимать, сколько миллисекунд выполнялась та или иная операция. То есть эта информация нам в
Speaker A
общем случае не сильно нужна, но вот иногда мы хотим это выяснить в целях какой-то отладки. Это уровень логирования debug. Дальше у нас уровень логирования inf - это такой самый популярный уровень логирования, то есть просто какая-то информация о том, что
Speaker A
происходит в сервисе, которую довольно важно видеть, когда мы открываем, собственно, логи нашего приложения. Далее уровень warрning. Ворning - это уже какое-то предупреждение, то есть это ещё не ошибка, но уже что-то идёт не лучшим образом. Вот это уже уровень
Speaker A
warрning. И уровень error говорит, что у нас в программе случилась какая-то ошибка. Ну и мы должны это как-то иметь в виду. То, что у нас во время выполнения нашей программы где-то там проскочил ер ну и нам, собственно, покажут, в какой строчке у нас это
Speaker A
произошло, что именно случилось, и сразу же напечатается дата и время случившегося инцидента. Так вот, возвращаемся к этим стекам вызовов.
Speaker A
Стеки вызовов нам помогут проследить, откуда же к нам пришло вот это вот предупреждение, либо откуда же к нам пришла ошибка. Ну давайте вот как раз-таки в этой функции фу я буду принимать указательно zap loger.
Speaker A
И при помощи этого самого заплогера я буду логировать какую-то ошибку. Ну я просто выведу текст сам error happens.
Speaker A
Всё. Ну и при вызове этой функции фу буду передавать вот этот вот наш логер.
Speaker A
Вот эти вот все вызовы я пока сотру, оставлю только один дебаг. Хорошо, давайте запустим программу.
Speaker A
Оп. И мы видим, что у нас произошёл log уровня error. Произошёл он в main. Файле в восьмой строчке. Вот он. А внизу вот этот вот стек вызовов нам поможет проследить, какие были вызовы, весь путь вызовов тех или иных функций, который
Speaker A
привёл к логированию на уровне Eror. В нашем случае это main.19. Из main. На девятнадцатой строчке мы вызвали функцию F.
Speaker A
Вот она. Вызываем. И тут на восьмой строчке произошёл самый лок. То есть этот стек вызова функций просто нам помогает отследить путь возникновения ошибки. Ну или если у нас логирование на уровне ворнинг, на уровне предупреждения, то этот стек вызовов нам
Speaker A
помогает отследить путь возникновения вот этой вот ситуации, которая требует какого-то предупреждения. И таким образом, благодаря логеру, мы можем довольно просто писать какую-то информацию о том, что происходит внутри нашего эээнд- приложение. И этот самый логер уже сам позаботится о том, чтобы
Speaker A
каждой строке, которую мы записываем, добавлять ещё дату и время случившегося, добавлять уровень логирования и добавлять то, в каком месте произошёл сам вызов этого логера. Ну и таким образом мы с вами рассмотрели логер, который пишет просто в консоль. Но в чём
Speaker A
может быть проблема? Мы запустили наше приложение, оно там отработало, вывело какие-то логи. А после этого мы, например, перезагрузили наш сервер. Вот мы сервер перезагрузили, заново зашли в терминал и всё, логи пропали. Всё, мы уже никогда не узнаем, что же там
Speaker A
происходило в нашем приложении. Если к нам после этого придёт пользователь и скажет: "А вот у меня вчера была какая-то ошибка". Мы уже никогда не узнаем, что же там случилось, потому что логи, которые отвечали за то, что с приложением происходило вчера, мы
Speaker A
успешно потеряли. Они просто стёрлись вместе с закрытием терминала. Почему? Потому что мы логировали только лишь в этот самый терминал. Мы логировали только в консоль. Для того, чтобы этого не происходило, обычно помимо логирования в консоль логируют ещё и дополнительно в файл. В таком случае
Speaker A
инициализация логера становится посложнее и выносится уже в какую-то отдельную функцию. Я понимаю, что сейчас это всё может выглядеть довольно сложно, но мы просто, чтобы сейчас на этих мелочах не останавливаться, разберём происходящее в этой функции довольно поверхностно. Значит, что происходит в
Speaker A
этой функции? Это конструктор для логера. То есть это какой-то конструктор, который нам создаст и проинициализирует вот этот вот самый заплогер, вернёт его, и мы сможем этим самым заплогером пользоваться. О'кей.
Speaker A
Эта функция принимает какую-то строку log. Что это такое? Это возможность задать минимальный уровень логов, который будет в итоге логироваться. То есть у нас логер на самом-то деле может логировать очень много всяких логов. Баг логи, infog, erorрги, он может
Speaker A
логировать и так далее. Но зачастую deb логи нам практически никогда не нужны, но их может быть огромное множество. А если мы пишем эти логи в файл, у нас эти самые лог-файлы начинают разрастаться, и иногда они могут настолько сильно
Speaker A
разрастись, что начнут занимать несколько гигабайт, а потом и несколько сотен гигабайт. Поэтому обычно хоть в программе могут и присутствовать логи уровня debug, но минимальный уровень логов, который всё-таки действительно будет залогирован и записан в консоль и в файл, выставляют на info. Что это
Speaker A
значит? Это значит, что там, где происходили вызовы логирования на уровне debug, просто ничего не будет происходить. А все логи уровня info и выше, ну, тут ещё уровень warрнинг, вот эти вот все уровни уже логируются. Но при необходимости этот самый уровень
Speaker A
логирования минимальный можно понизить, и тогда уже будет логироваться. Всё это нужно, когда, к примеру, мы хотим отладить какой-то баг в нашей программе.
Speaker A
Ну и тогда ставится уровень логирования debug, чтобы видеть прямо всё, что происходит в нашем приложении. Хорошо.
Speaker A
Возвращаемся к нашей функции. Значит, тут можно задать некоторую строку, которая обозначает минимальный уровень логирования. Хорошо, что происходит дальше? Создаётся какая-то структура New Atomic Level. Это структура, которая предоставляется библиотекой ZP. И у неё есть довольно полезная функциональность.
Speaker A
Это преобразование вот этой вот строки, которая обозначает минимальный уровень логирования в структуру, которую потом можно будет передать в логер при инициализации, чтобы мы могли в эту функцию передать обычную строчку. И эта строчка действительно повлияла на минимальный уровень логирования нашего
Speaker A
заплогера. Ну хорошо. Вот тут вот мы, собственно, преобразуем эту строку в уровень логирования. Проверяем, что не было никаких ошибок во время этого преобразования. После этого у нас создаётся отдельная директория, отдельная папка, куда мы с вами будем складывать наши файлы с логами. Просто
Speaker A
потому, что принято файлы с логами класть в отдельную директорию, чтобы их было потом удобнее находить. Ну, то есть создаётся обычно или в корне проекта, или где-нибудь в другом месте папочка.
Speaker A
Называется она что-то типа logs. И в этой папочке создаются logйлы. Ну, и чтобы гарантировать, что эта папка у нас будет создана, мы её создаём изнутри гошного приложения. Значит, давайте посмотрим тут поподробнее. Функция MK Dear All из пакета OS. Она первым
Speaker A
аргументом принимает путь до директории, которую мы хотим создать, но у нас это относительный путь, поэтому одного лишь названия директории достаточно. И вторым аргументом эта функция принимает число, которое обозначает права доступа к создаваемой директории. На самом деле тут всё не очевидно. Это тонкости работы
Speaker A
Unix операционных систем. Сейчас я быстренько попытаюсь объяснить, что тут вообще произошло, что это за число. Так как в начале у этого числа стоит нолик, это значит, что данное число у нас представлено в восьмеричной системе с числения. Как это число воспринимается
Speaker A
операционной системой? Операционной системой данное число воспринимается как три отдельные цифры. Отдельная цифра семь, отдельная цифра пять и ещё одна отдельная цифра пять. Что это за цифры?
Speaker A
Первая цифра задаёт права доступа к создаваемой директории для юзера, который эту директорию, собственно, и создаёт. То есть это права доступа для нас самих. Вторая циферка задаёт права доступа уже для тех пользователей, которые находятся в той же самой группе,
Speaker A
что и мы. Ну, в Unix подобных системах есть возможность создавать группы пользователей. И вот если в этой группе пользователей вместе с нами есть какие-то ещё другие пользователи, то вот эта вторая циферка - это как раз-таки права доступа для них. Ну, я назову это
Speaker A
user group. И третья циферка - это права доступа для всех остальных. То есть это права доступа для всех остальных пользователей, которые не являются тем пользователем, который создал директорию и не входят в группу пользователей, где находится юзер, создавший директорию. То
Speaker A
есть все остальные. Я это назову АЗР. Хорошо. Что же значат сами цифры? Вообще в Unix подобных операционных системах права доступа к директориям делятся на три вида. Первое право обозначается английской буковкой R и означает read.
Speaker A
То есть это право читать содержимое директории, просматривать, какие там есть файлы. Второе право именуется буковкой W и означает write, то есть каким-либо образом менять содержимое директории, создавать там какие-то новые файлы, ну и так далее. И третье. Право доступа обозначается английской буковкой
Speaker A
X, то есть execute. И конкретно применительно к директориям вот это вот третье право доступа позволяет заходить в директории и работать с файлами внутри этой самой директории. И вот этот вот набор прав, он может отдельно задаваться для каждого круга пользователей. То есть
Speaker A
для конкретного юзера, который создаёт директорию, мы можем задать эти три права отдельно. Для группы пользователей мы можем задать эти три права отдельно.
Speaker A
И для всех остальных мы тоже можем эти три права задать отдельно. Каким образом эти самые права задаются? Если в соответствии с каким-то правом стоит единичка- это значит право выдано. Если нолик, значит, права нету. Так вот, тут мы постепенно уже подходим вот к этой
Speaker A
вот цифре. Значит, смотрим. Для пользователя, который создал эту директорию, мы хотим дать следующие права. Право на чтение содержимого этой директории. Да, мы хотим дать это право.
Speaker A
Право на создание новых файлов в этой директории. Да, мы хотим дать это право и право на то, чтобы в директорию заходить и просматривать и работать каким-то образом с уже созданными файлами. Да, мы хотим дать это право тоже. Хорошо. Значит, дальше для группы
Speaker A
пользователей. Мы хотим другим пользователям предоставить возможность читать содержимой директории. Да. Хотим ли мы другим пользователям предоставить возможность создавать, удалять какие-то файлы в нашей директории логов? Нет. То есть зачем? Как бы я пишу логи от имени своего пользователя. Я не хочу, чтобы
Speaker A
другие пользователи могли что-то там добавлять или создавать в этой папке, поэтому я этого права не даю. Но при этом я хочу другим пользователям дать возможность заходить в папку с логами, навигироваться по этой папке, открывать файлы, находящиеся в этой папке. Поэтому
Speaker A
userгруппе вот это право X я выдаю. И то же самое я хочу сделать с всеми остальными пользователями. Выдаю право на чтение содержимого директории.
Speaker A
Забираю право создавать, удалять файлы в этой директории и даю право заходить в эту директорию и просматривать содержимое файлов. Да. О'кей, мы с вами получили вот такой вот набор битов. А теперь смотрим вот это вот число. 111 1 в двоичной системе исчисления - это
Speaker A
семёрка. 101 в двоичной системе исчисления - это пятёрка. И ещё раз 1 01 - пятёрка. Вот мы и получаем 755. И сюда это число записывается. Да, я понимаю, звучит довольно дико, но, к счастью или, к сожалению, вот такие вот тонкости
Speaker A
работы Unix подобных операционных систем, в частности, выдача прав на те или иные действия различным кругам пользователей относительно каких-то директорий. Ну, короче говоря, русским языком вот это вот 0755 означает, что я пользователь, который создал эту директорию, имею полные права на неё, а
Speaker A
всем остальным я даю возможность заходить в эту директорию и просматривать её содержимое, просматривать содержимое логфайлов, но не удалять ничего и ничего там не создавать, ничего как бы не менять. Вот создали, значит, директорию с этими правами. Далее мы получаем какой-то там
Speaker A
таймстмп. Что это такое? Я получаю текущее время, то есть время конкретно, когда была вызвана вот эта вот функция new loger. Перевожу это в нулевой часовой пояс и потом форматирую текущее время в следующий формат, где у меня идёт сначала дата, потом большая буковка
Speaker A
Т, после этого время с точностью до миллисекунд. И после этого полученный таймстмп я добавляю в название создаваемого файла с логами. Для чего?
Speaker A
для того, чтобы создаваемые файлы с логами имели в названии время своего создания. Это просто удобно, чтобы я конкретно понимал, во сколько тот или иной файл с логами был создан, и я смог примерно понимать, когда вообще то или иное приложение было запущено. Вся эта
Speaker A
информация будет содержаться в названии файла. Собственно, получаю название logйла и после этого его создаю. Создаю через функцию Openфайл. передаю путь до файла, а именно он лежит в директории logs, и передаю название вот estamp.log, и передаю какие-то флаги. Что это за
Speaker A
флаги? Эти флаги означают две вещи. Первый флаг означает, что если файла такого нету, его нужно создать. Вот этот вот флаг create. Второй флаг означает, что тот файл, который я создал или же открыл, он у меня только для записи. То
Speaker A
есть читать я из этого файла ничего не могу. Ну потому что мы, когда лог файлы пишем, мы их, собственно, только пишем.
Speaker A
Читать нам ни для чего их не нужно. Именно изнутри приложения. Изнутри приложения мы в лог файлы только записываем. Вот эти два флага я передаю при открытии сшсоздании файла. Далее тут опять права доступа 644. Точно такая же телега, как и здесь. Только почему тут
Speaker A
другие цифры? Ну давайте посмотрим. Я вот это вот скопирую, сюда вот вставлю. Только тут у нас уже цифры 6 4четыре.
Speaker A
Значит, в случае с файлами у нас несколько другие вещи обозначаются вот этими вот самыми буковками. Буковка Р означает возможность читать содержимое файла. Буковка W означает возможность писать в этот файл. А буковка X означает возможность делать файл исполняемым, превращать его в какой-то там скрипт и
Speaker A
выполнять его как скрипт. Ну хорошо, хотим ли мы пользователю, который создаёт этот файл, дать возможность читать содержимое файла? Да. Хотим ли мы дать возможность писать в этот файл? Ну конечно, мы же пишем logфайл. Нам нужны эти права. Хотим ли возможность давать
Speaker A
запускать этот файл как скрипт? Ну нет, это просто logфайл. Это никакой не скрипт. Этих прав мы давать не хотим.
Speaker A
Хорошо.Па. Хотим ли мы пользователям в нашей группе давать возможность читать содержимое файла? Да. Писать в этот файл? Нет, потому что я создатель этого файла и я хочу в него записывать. Кто-то другой не может прийти и что-то в мой
Speaker A
lкфайл записать? Нет, в этот logфайл пишу только я. И хочу ли я другим пользователям давать возможность запускать этот файл как скрипт? Нет, не хочу. То же самое для всех остальных пользователей. 1 значит это 1 - это в двоичной системе 6. 1 в двоичной системе
Speaker A
4тыре и ещё одна четвёрочка. Вот и получаем 644. Ну и тут уже говоря русским языком при создании файла вот это вот 644 означает, я как создатель этого файла могу и читать его содержимое, и писать в него. Все остальные могут только читать. И никто
Speaker A
из нас не может запускать этот файл как какой-то скрипт. Всё, вот разобрались с этой циферкой. Хорошо, создали директорию для создания локфайлов.
Speaker A
Создали, собственно, самфайл. О'кей, теперь идём дальше. Теперь тут создаётся config для логера. Библиотека Zap довольно большая. У неё есть гибкие возможности по кастомизации того, как именно будут логироваться те или иные строки на ваше усмотрение. Кому интересно, можете почитать документацию
Speaker A
или поискать какие-нибудь статейки в интернете. Мы сейчас на этом долго останавливаться не будем. В сорок первой строчке я чуть-чуть меняю то, как выглядит таймстampмп при логировании.
Speaker A
Для чего? Потому что я хочу видеть миллисекунды. По умолчанию самая точная единица, которую логирует за блогер - это секунда. Но за одну секунду в приложении может очень много чего произойти, поэтому мне нужна точность до миллисекунд. Я выбираю такой вот формат.
Speaker A
Далее я создаю два подъядра для моего логера. Первое под ядро у меня будет писать в консоль. Вот std out - это обычный консольный вывод. Второе под ядро у меня будет писать в logфай. И обоим этим так называемым под ядром я
Speaker A
выставляю уровень логирования, тот, что был передан у меня в аргументах функции. Всё. После этого я инициализирую сам логер и добавляю в него ещё пару параметров. Вот этот вот параметр нужен для того, чтобы логер дополнительно логировал место, из которого будет,
Speaker A
собственно, произведён сам лог. А вот этот параметр говорит, что стек вызовов, которые мы с вами рассматривали, нужно показывать только для уровней eror и выше. То есть для дебак уровня, для info уровня и для уровня warning у нас вот
Speaker A
эти вот стеки вызовов показываться не будут. Будут показываться только для уровня erorр и выше. И что возвращает эта функция? Функция возвращает loger.
Speaker A
Функция возвращает другую функцию. И вот эта другая функция нужна для того, чтобы закрывать logфайл. То есть это хорошая практика, когда мы файлы не только создаём и открываем, но ещё и закрываем, чтобы вовремя давать операционной системе понять, что работа с файлом
Speaker A
прекращается. Ну и, собственно, это три возвращаемых значения функции. Сам логер, другая функция, чтобы закрыть файл, в который мы будем логировать, ну и возможная ошибка. Хорошо, давайте воспользуемся этой функцией new loger.
Speaker A
Посмотрим, что она вообще делает. Вот в мейне я пишу loger log file clos = new loger. Ну и минимальный уровень логирования я передам бак. Хорошо, если у меня ошибка не равна, тогда кину панику. После этого задефёрю закрытие файла, чтобы он у меня точно закрылся.
Speaker A
Ну и после этого я могу, наконец-таки, пользоваться этим самым логером. Давайте я залогирую уровень debug. Напишу тут сам debug log. Залогирую infуровень сам infol log и залогирую. что-нибудь на уровне erorр сам erorр log. Хорошо, давайте теперь запустим нашу программу,
Speaker A
посмотрим, что же вообще этот логер у нас теперь делает. Пишуun main. Так, что мы видим? Мы видим, во-первых, у нас залогировались все три лога в консоль.
Speaker A
Вот эти вот все три лога, которые мы логировали, попали в консоль. И на уровне Erро у нас ещё и стег вызовов залогировался. Хорошо. Но помимо этого у нас создалась папка logs, и в ней создался файл. И все логи, которые мы
Speaker A
писали в консоль, они у нас оказались не только в консоли, но ещё и в файле. А что это нам даёт? А это нам даёт то, что мы даже если компьютер перезагрузим, например, или ещё что-нибудь произойдёт с нашим приложением, мы закроем
Speaker A
терминал, в котором у нас вот эти вот все логи, у нас эти логи не пропадут, они у нас останутся в файле. Значит, давайте посмотрим, что в названии файла вообще за такое. На самом деле файл называется по таймстемпу. Вот у нас есть
Speaker A
дата, вот у нас время с миллисекундами тоlog. Для чего так называть logфайл? Нужно это для того, чтобы мы могли заранее увидеть, восколько этот logфайл был создан. И на каждый отдельный запуск приложения у нас будет создаваться новый log. Что это нам даёт? Это нам даёт то,
Speaker A
что каждый новый запуск приложения будет генерировать новый logйл, и у нас будет чёткое разделение для каждого конкретного запуска приложения. Это очень удобно, чтобы не гадать, например, где предыдущая версия приложения закончила логировать и где начала логировать новая версия приложения. Нет,
Speaker A
мы просто можем зайти посмотреть по таймстемпам, где начался новый лок-файл. Ну, при необходимости мы могли бы в название этого логфайла добавлять ещё и версию приложения, например, версия 1 тире. Вот так вот. Ну, это уже зависит от того, как вашей команде принято.
Speaker A
Обычно просто вот так вот таймстемпами называют логфайлы. они сразу же автоматически будут отсортированы от самого старого к самому новому. И это довольно удобно просматривать. Ну а на этом, в принципе, всё. Мы с вами поговорили, что такое логер, для чего он
Speaker A
нужен, посмотрели примерно, как вообще в настоящих ээнд- приложениях инициализируются логеры, посмотрели, что логер нам может позволить писать одновременно и в консоль, и в файл. Ну и что самое главное, мы получили такое общее определение того, что такое логирование, что такое логер. Теперь мы
Speaker A
с вами можем идти дальше. [музыка] Теперь нам стоит рассмотреть такую важную тему, как архитектура приложения.
Speaker A
Поговорим о том, что вообще такое архитектура приложения, какие архитектуры бывают и как нам стоит вообще эту тему воспринимать, так сказать, прольём на это некоторый свет познания. Вообще существует большое множество различных архитектур. У нас есть чистая архитектура, такая самая
Speaker A
популярная на слуху. У нас есть гексагональная архитектура. И у нас есть огромное множество других архитектур, возможных. Вообще, что такое архитектура приложения? Архитектура приложения - это то, каким образом в нашем проекте устроена структура файлов, структура папок, то, какие именно сущности у нас
Speaker A
описаны в наших файлах с кодом, то, как эти сущности между собой взаимодействуют. Вот это вот всё есть архитектура приложения. То есть мы можем всё наше приложение написать в одном огромном файле, нам как бы никто не запрещает, но мы можем придумать
Speaker A
довольно хитрую структуру папок в нашем приложении. структуру файлов. В каждом файле будут какие-то определённые сущности описаны, которые каким-то образом будут между собой взаимодействовать. Вот это всё архитектура приложения. Но в чём тут огроменный просто мем? Мем в том, что
Speaker A
чистая архитектура, гексагональная архитектура или любая другая архитектура не даёт нам чёткий ответ на вопрос: как именно мы должны создавать свои папки?
Speaker A
Как именно мы должны называть файлы в этих папках? Как именно должны называться создаваемые внутри этих файлов сущности? Ответов на эти вопросы зачастую нету. К чему это приводит? К тому, что интерпретация той же чистой архитектуры у двух разных разработчиков
Speaker A
может быть разное. А к чему это приводит? к тому, что вы можете прийти на одно рабочее место и там вроде как пытались написать проект согласно чистой архитектуре, и получился какой-то проект определённого вида. Потом вы придёте на другое рабочее место, там тоже люди
Speaker A
пытались написать проект согласно чистой архитектуре, но в итоге он выглядит совершенно по-другому. Совершенно другая структура файлов, совершенно по-другому называются сущности. Хотя, казалось бы, и там, и там люди пытались реализовать чистую архитектуру. Именно поэтому мы с вами сейчас не будем рассматривать
Speaker A
конкретно чистую архитектуру или мы не будем конкретно рассматривать гексагональную архитектуру или какую-то ещё конкретно описанную где-то в какой-то книжке архитектуру. Мы с вами посмотрим, как обычно в настоящих коммерческих проектах строится архитектура приложения, так сказать, некоторый усреднённый средств, исходя из
Speaker A
которого вы уже как-то на своё усмотрение сможете дополнять архитектуру своего приложения, которое вы пишите. Но так или иначе, обычно в основе всех архитектур лежит трёхслойная архитектура. Что такое трёхслойная архитектура? Трёхслойная архитектура - это архитектура, согласно которой наше приложение делится на три слоя:
Speaker A
транспортный слой, слой сервиса и слой репозитория. Что это за слои? Транспортный слой - это слой, который отвечает за связь нашего приложения с внешним миром. Из того, что мы с вами уже прошли - это взаимодействие по HTTP.
Speaker A
То есть взаимодействие по HTTP - это есть транспортный слой. Хорошо. слой репозитория. Что такое слой репозитория - это такой слой, который ответственен за то, чтобы сохранять или же получать данные, с которыми наше приложение работает. Ну, опять же, из того, что мы
Speaker A
прошли, это взаимодействие с постгресом через библиотеку PGX. Вот это слой репозитория. Что же такое слой сервиса?
Speaker A
Слой сервиса - это всё остальное. То есть слой сервиса - это тот код, который находится между обработкой HTTP запроса и взаимодействием с репозиторием. То есть слой сервиса - это обработка основной бизнес-логики нашего приложения. Ну, давайте на конкретном примере. Например, у нас приложение для
Speaker A
обработки банковских операций. Значит, транспортный слой - это принять HTTP запрос о том, что кто-то хочет произвести какую-то банковскую операцию.
Speaker A
Слой репозитория - это, собственно, сохранить информацию о том, что у кого-то новый баланс. А слой сервиса - это непосредственно получить из репозитория какие-то данные о банковских картах, о банковских счетах, после этого произвести расчёт операции, произвести комиссию, понять, в какой платёжный
Speaker A
сервис нужно передать эту информацию, понять, как сохранить чек о произведённой операции, куда его сохранить. Это всё уровень сервиса, то есть самая настоящая бизнес-логика, то есть прикладной код, который непосредственно решает бизнес-задачу, конкретно бизнес-задачу по переводу денег, сохранению банковских чеков,
Speaker A
выбора нужного метода оплаты. Это всё уровень сервиса. Ну вот, если взять более конкретно пример с составлением чека, то сам чек о банковской операции составляет слой сервиса. А вот, чтобы сохранить этот чек в базу данных, тут уже сервис обращается к репозиторию, и
Speaker A
репозиторий сохраняет этот чек. Ну а транспортный слой, повторюсь, это просто HTTP запросы, например. Но помимо HTTP могут быть ещё другие протоколы для взаимодействия с нашим приложением.
Speaker A
Может быть, кто-то знает, есть такой протокол JRPC или есть ещё такие вещи, как брокеры сообщений. Message брокеer.
Speaker A
Это всё тоже транспортный слой. То есть это то, как с нашим приложением можно взаимодействовать извне. Дальше слой сервиса - это непосредственно бизнес-логика, непосредственно код, который решает бизнес-задачи. Ну и дальше слой репозитория просто для того, чтобы как-то взаимодействовать с
Speaker A
репозиторием, что-то там сохранять, какие-то данные, либо же какие-то данные получать. Вот это трёхслойная архитектура, и она лежит в основе практически всех архитектур. Практически все архитектуры так или иначе можно декомпозировать до трёхслойной архитектуры. Что у нас есть? Отдельный слой для взаимодействия с транспортом, у
Speaker A
нас есть отдельный слой для написания бизнес-логики и у нас есть отдельный слой для работы с репозиторием. Какие сущности использует каждый из уровней?
Speaker A
Ну, в случае с транспортом, если кто помнит, в предыдущей части мы, когда писали Rest AP, мы использовали дтшки.
Speaker A
Так вот, ДТОшка - это сущность, которая нужна для работы с данными на транспортном уровне. Хорошо, на уровне репозитория мы тоже с вами помним, какая сущность используется. На уровне репозитория используется некоторый аналог этой ДТОшки, но называется он уже модель. просто для того, чтобы различать
Speaker A
сущности, которые нужны, чтобы принять входящий запрос, и сущности, которые мы в итоге сохраняем в базе данных. Так вот, сущности, которые в итоге сохраняются в базе данных - это модели.
Speaker A
Что же тогда на уровне сервиса? На уровне сервиса - это домен. То есть домен - это основная сущность в нашем приложении, с которой происходит взаимодействие бизнес-логики. Конкретно для чего это нужно? для того, чтобы при необходимости мы могли поменять
Speaker A
транспортный слой на какой-нибудь другой. Либо же мы могли перейти с одного репозитория на другой. Но именно доменная сущность, с которой взаимодействует основная часть нашего приложения, именно бизнес-логика, чтобы эта доменная сущность была изолированной от других слоёв приложения. Таким образом, поток данных в приложении
Speaker A
обычно выглядит следующим образом. нам приходит HTTP запрос и в нём какие-то jнба байты. Эти JSON байты у нас преобразуются в дтошку. То есть дшка по сути на уровне нашего приложения - это обычная структура. После этого мы полученные данные передаём в уровень
Speaker A
сервиса. И когда мы передаём эти данные в уровень сервиса, то ДТОшка у нас конвертируется в доменную сущность. На уровне сервиса происходит какая-то работа с этой доменной сущностью. И потом, например, нам нужно что-то сохранить в базу данных. мы эту доменную
Speaker A
сущность преобразуем в модель. И потом уже эта модель у нас сохраняется в репозитории. Ну, например, сохраняется в базу данных Postgress. Тут ещё стоит добавить довольно важное уточнение, что конкретно, когда идёт речь про трёхслойную архитектуру в Гошке, то эти
Speaker A
самые слои между собой связаны не напрямую, а через специальные интерфейсы. То есть у нас слой транспорта, структура, которая отвечает за слой транспорта, она не зависит напрямую от структуры сервиса. Вместо этого структура транспорта зависит от интерфейса сервиса, а уже сам сервис
Speaker A
потом реализует этот самый интерфейс. То же самое можно сказать про взаимоотношения уровней сервиса и репозитория. Сервис не зависит обычно напрямую от репозитория. Сервис обычно зависит от интерфейса репозитория. А репозиторий потом уже имплементирует или же реализует этот самый интерфейс. Для
Speaker A
чего так делают? Ну вот давайте посмотрим на примере с репозиторием. Допустим, у нас есть сервис, он выполняет какую-то бизнес-задачу, например, сохраняет чеки о банковских операциях. Сервис зависит от интерфейса репозитория. И под этим интерфейсом у нас лежал репозиторий, который сохранял
Speaker A
наши чеки в базу данных Постгрес. Но потом, спустя какое-то время, мы поняли, что сохранять чеки в постгрыз - это не лучшая идея, потому что чек из себя на самом-то деле представляет документ, и мы хотели бы хранить документы в базе
Speaker A
данных, которые на это заточена. Например, Mono DB. Важно понимать, что я сейчас не говорю, что Mongo DB - это наилучшее место для сохранения банковских чеков. Мы лишь сейчас рассматриваем некоторый теоретический пример. Mono DB - это документоориентированная база данных. И
Speaker A
в некоторых сценариях мы могли бы предпочесть сохранять чеки в Монo DB. И благодаря тому, что у нас сервис напрямую не зависел от репозитория, мы можем просто описать новый репозиторий, который основывается на Mongo DB, и сделать так, чтобы этот репозиторий
Speaker A
удовлетворял вот этому интерфейсу. И тогда мы сможем спокойно перевести наш уровень сервиса со старого репозитория на новый репозиторий. Просто потому, что у нас уровень сервиса напрямую не зависел от конкретной реализации конкретного репозитория, а вся зависимость у нас была исключительно
Speaker A
через интерфейс. Благодаря этому мы получили то, что два разных слоя нашего приложения не зависят друг от друга напрямую. И это, как мы с вами только что рассмотрели, как минимум даёт большую гибкость при разработке. Это во-первых. А, во-вторых, очень часто
Speaker A
такой приём используется для тестирования. Мы с вами тестирование будем обсуждать в следующей части курса погошки, но сейчас могу удочку такую закинуть, что если у нас сервис зависит от репозитория напрямую, то нам, чтобы протестировать уровень сервиса, нам необходимо туда подсовывать настоящий
Speaker A
репозиторий. А настоящий репозиторий будет требовать, например, подключения к базе данных Постгress. И это всё приводит к тому, что нам, чтобы написать простенький тест на уровень сервиса, нам для этого необходимо инициализировать репозиторий и инициализировать внешнюю базу данных. А это слишком большие
Speaker A
накладные расходы, и зачастую нам это просто не нужно. Но если у нас вдруг сервис зависит от репозитория не напрямую, а через интерфейс, мы в целях тестирования можем инициализировать этот сервис, а под интерфейс подпихнуть некоторую заглушку репозитория. В таком
Speaker A
случае нам не придётся инициализировать настоящий уровень репозитория и, как следствие, не придётся инициализировать настоящую внешнюю базу данных, что очень сильно упрощает тестирование.
Speaker A
Соответственно, вот два таких больших плюса того, что различные слои нашего приложения не зависят друг от друга напрямую, а делают это через интерфейсы.
Speaker A
Плюс первый. В случае необходимости мы можем какой-нибудь из слоёв приложения взять и поменять. И нам не придётся залезать в исходные коды других слоёв, что сильно упрощает и ускоряет разработку. Второй плюс - это более простое тестирование. Если у нас один
Speaker A
слой зависит от другого через интерфейс, то мы в целях тестирования можем этот самый другой слой заменить на какую-нибудь заглушку для того, чтобы, к примеру, не инициализировать слой репозитория в то время, когда я хочу отдельно изолированно протестировать лишь слой сервиса. Вот это основа
Speaker A
трёхслойной архитектуры. И уже поверх этого строятся остальные архитектуры. Сейчас мы с вами рассмотрим такую усреднённую архитектуру приложения, которая вот в среднем используется в коммерческих проектах. Повторюсь, почему мы не рассматриваем конкретно чистую архитектуру или конкретно гексагональную архитектуру или ещё какую-то другую?
Speaker A
Потому что у каждого в голове своя интерпретация этих архитектур. Поэтому сейчас на этапе обучения нету никакого смысла вгрызаться в какую-то конкретную архитектуру. Я буду вам показывать то, как это обычно реализовано в коммерческих проектах. Ну а теперь, как говорится, приготовьтесь, потому что мы
Speaker A
переходим от трёхслойной архитектуры, которая, если где-то в реальных проектах и применима, то только в очень-очень маленьких сервисах. Мы от этой трёхслойной архитектуры переходим уже к полноценной архитектуре ээнд приложения, такой, как она обычно в среднем выглядит на настоящих коммерческих проектах. Ну,
Speaker A
поехали. У нас обычно в корне проекта есть папочка internal. И папочка содержит внутри себя всё самое интересное. По сути, папкаın internal - это внутренности сервиса, это основная его логика, основные сущности, основной поток данных. Вот всё это описывается именно внутри папочки internal. Далее в
Speaker A
самой папочке internal уже по сути может происходить всё, что угодно, но вот где-то в среднем выделяются две дополнительные подпапки. В папочке internal первая подпапка - это features.
Speaker A
В этой папке содержатся фичило. И в этой папке features на каждую отдельную фичу своя подпапка. Раз подпапка для Фичи1, подпапка для Фичи2 и вот под папка, под директория для Фичи 3. Каждая фича уже в своё время делится на три уровня. То есть для каждой
Speaker A
отдельной фичи мы создаём уровень транспорта, уровень сервиса и уровень репозитория. То есть не просто как было в трёхслойной архитектуре, что у нас тупо на всё приложение три слоя. Нет, у нас теперь на каждую отдельную фичу три слоя: транспорт, сервис и репозиторий.
Speaker A
Ну, хорошо. Что помимо папки Features есть в папке Internal? Помимо папки Features в папке Internal обычно есть поддиректория. Называется она что-то типа Core, что-то типа app, что-то в этом роде. Ну вот я обычно называю это Core. Что тут содержится? Тут обычно
Speaker A
содержится какой-то общий код, который разделяется между несколькими или же между сразу всеми фичами нашего приложения. Какой это может быть код?
Speaker A
Это может быть логер, то есть создание, инициализация логера. Это может быть какой-то конфиг. Например, в конфиге мы с вами можем передавать логины, пароли для базы данных. Это могут быть какие-то сторонние утилиты, которые нам помогают что-то делать. Например, у нас во всех
Speaker A
фичах используются query параметры. И у нас есть какая-то отдельная утилита, которая нам помогает эти queryпараметры из входящего HTTP запроса доставать. Ну и чтобы в каждой фиче отдельно не прописывать эту утилиту, по сути она будет одинаковая. Она вот выносится в
Speaker A
папочку Core как какой-то общий код. Также обычно в эту папку Core выносятся ошибки, но не все ошибки, а только те ошибки, которые как-то влияют на поведение нашего приложения. К примеру, мы с вами хотим понимать, когда пользователь попытался через HTTP запрос
Speaker A
получить какой-то несуществующий ресурс. Ну, к примеру, у нас социальная сеть, и пользователь попытался отправить HTTP запрос на получение странички другого пользователя, которого не существует. То есть, по сути, была попытка получения несуществующей страницы. В таком случае мы хотим не просто из какого-то слоя
Speaker A
нашей фичить ошибку, там типа not found. Мы хотим конкретно из-за этой ошибки not found поменять поведение нашего приложения. А конкретно мы в HTTP ответе хотим отдать статус код 404. Не просто там 500 internal server Eror, а именно статус код мы хотим поменять. И так как
Speaker A
эта ошибка not found, она может разделяться как между разными уровнями одной фичи, так эта самая ошибка not found может вообще разделяться между различными фичами, то есть использоваться в этих различных фичах.
Speaker A
Поэтому мы эту ошибку тоже создаём не в рамках какой-то конкретной фичи, а выносим её в общую папку errors. Плюс это нам позволяет избежать прямой зависимости между различными слоями приложения. То есть, например, у нас репозиторий пошёл, сходил в базу данных
Speaker A
и понял, что странички запрашиваемой не существует. Он хочет отдать вот эту вот ошибку not found, где она должна быть объявлена. Если эта ошибка будет объявлена где-то в глобальной области видимости на уровне репозитория, хорошо, репозиторий сможет отдать эту ошибку
Speaker A
сервису, сервис сможет отдать ошибку транспорту. Транспорт, когда получит какую-то ошибку из сервиса, транспорт заранее не знает, что это за ошибка, транспорт захочет узнать конкретно, что за ошибка ему пришла. И чтобы проверить, что это именно not found, транспорт должен будет импортировать уровень
Speaker A
репозитория. А это нарушение правила, которое гласит, что разные слои приложения не должны импортировать друг друга вообще никак. Вся зависимость между различными уровнями только через интерфейс. Никаких прямых импортов. Ну а транспорту, чтобы понять, что ему именно not found ошибка пришла, именно вот эта,
Speaker A
придётся импортировать уровень репозитория. Это неправильно. Но когда у нас такого рода ошибки выносятся в папочку Core, в подпапочку errors, то есть в какую-то общую область нашего приложения, мы решаем эту проблему, что у нас какой-то там слой должен импортировать другой. Нет. Теперь, когда
Speaker A
репозиторий сходит в базу данных и поймёт, что запрашиваемого ресурса не найдено, репозиторий просто кинет какую-нибудь ошибку из этой подпапочки erorс, а потом транспорт будет проверять, что эта самая ошибка именно из папочки Eorрс какая-то конкретная пришла. Но теперь мы видим, что у нас
Speaker A
как репозиторий, так и транспорт не зависят друг от друга, а они оба зависят от какой-то общей части кода, которая лежит в папке Core. Ну и, соответственно, мы решаем проблему того, что у нас различные уровни приложения начинают друг друга импортировать и, как
Speaker A
следствие, зависеть напрямую. Ну и плюсом, что ещё нам даёт вынесение каких-то общих ошибок в область кода Core - это то, что ошибка Not found - это довольно общая ошибка, которая может использоваться в рамках различных фичей.
Speaker A
И чтобы нам эту ошибку not found везде не дублировать, одну и ту же не писать, просто все фичи, которые нуждаются в этой ошибке, могут брать и импортировать вот эту вот папочку errors из общей области кода. Напоминаю, общая область
Speaker A
кода - это вот всё, что в папке Core. Хорошо, пока всё, что мы поняли, это что у нас есть корень проекта и в корне проекта папка internal. Папкаın internal - это внутренности нашего сервиса. В этой папке internal есть две подпапки:
Speaker A
Features и Core. Папка Features содержит в себе фичи нашего приложения. Каждая фича имеет свою отдельную папку, и у каждой фичи есть три своих слоя. То есть в рамках каждой фичи у нас объявлен слой транспорта, слой сервиса и слой
Speaker A
репозитория. Хорошо. Также у нас в рамках папки internal есть поддиректория Core. В ней содержится какой-то общий код для нашего приложения, включая общий логер, какие-то общие утилиты, общие конфиги и общие ошибки. Общие ошибки - это такие ошибки, в результате которых
Speaker A
наше приложение должно как-то поменять своё поведение. То есть когда нам именно важно, что за ошибка пришла, и когда мы в зависимости от того, что именно эта ошибка пришла, будем менять поведение в нашем коде. Вот такие ошибки выносятся в
Speaker A
общую область, в папочку errors. Ну, опять же, нейминг может быть произвольный, но примерно суть такая.
Speaker A
Хорошо. Идём дальше. Помимо прочего, в папочке Core есть папочка domains, то есть это домены какие-то. Что такое домены? Ну, мы с вами при обсуждении трёхслойной архитектуры уже успели услышать, что домен - это некоторая сущность нашего приложения, с которой
Speaker A
работает уровень сервиса. Вообще, если так более широко подумать, то уровень сервиса - это некий центральный уровень нашего приложения, в котором, по сути, происходит вся основная бизнес-логика приложения. То есть это некоторый такой особый центральный слой нашего приложения. Уровень транспорта нужен
Speaker A
просто для того, чтобы принять и провалидировать ну например HTTP запрос. Уровень репозитория нужен просто, чтобы какие-то данные сохранить, либо уже ранее сохранённые данные получить, либо удалить, ну, и так далее.
Speaker A
А вот уровень сервиса уже определяет бизнес-логику, то есть это именно само решение бизнес-задачи. То есть бизнес перед нами ставит какую-то задачу, и мы её решаем именно в уровне сервиса. Ну, положим, у нас та же самая социальная сеть, и у нас вот эта вот фича 1 - это
Speaker A
регистрация нового пользователя. Значит, за что будет отвечать уровень транспорта? Уровень транспорта просто будет отвечать за то, чтобы принять данные о новом пользователе по сети, ну, и преобразовать их там в какую-то дтшку.
Speaker A
Уровень репозитория будет отвечать за то, чтобы зарегистрированного пользователя положить в базу данных. А вот уровень сервиса уже будет отвечать за то, чтобы провалидировать входящие данные, провалидировать номер телефона, email, который прислал пользователь, отправить на email код с подтверждением, принять этот код с подтверждением,
Speaker A
провалидировать код подтверждения и только в нужный момент уже вызвать слой репозитория, чтобы сохранить зарегистрированного и провалидированного пользователя. То есть все те требования от бизнеса, как мы валидируем пользователя, когда мы регистрируем пользователя, когда пользователь считается с подтверждённым аккаунтом и
Speaker A
так далее, и так далее. За всё это ответственен именно слой сервиса. Повторяю, слой транспорта просто принять запросик и преобразовать его там в ДТошку. Может быть какая-то минимальная валидация входящего запроса. Слой репозитория - это именно, чтобы что-то в базу данных положить, получить, поменять
Speaker A
что-то в базе данных. А вот конкретно решение бизнес-задачи, конкретно основной код фичи, который эту самую фичу определяет, который эту самую фичу реализует, находится в слое сервиса.
Speaker A
О'кей, тут поняли. Но что самое смешное, обычно бывает так, что если у нас какая-то простая фича, которая по сути является крудом. Круд - это вот от английского, английская аббревиатура crude, то есть это create, read, update, delete, что означает простые,
Speaker A
примитивные операции над данными. Создать какой-то ресурс, получить какой-то ресурс, обновить какой-то ресурс или удалить какой-то ресурс. Это называется Крут. Так вот, если у нас фича по сути из себя представляет какой-то крут, ну, например, тот же To-дулиist, у нас может быть сохранение,
Speaker A
получение, удаление, изменения задач. Тогда как будет выглядеть? У нас по сути весь основной код, он будет содержаться в репозитории, потому что у нас будет самая сложная задача - это положить данные в базу, получить данные с базы и так далее. В уровне транспорта мы просто
Speaker A
будем принимать HTTP запрос, который будет содержать данные о создаваемой задаче. А уровень сервиса почти ничего не будет делать. Уровень сервиса будет просто принимать запрос из уровня транспорта и сразу же без каких-то дополнительных бизнес-требований, без какой-то бизнес-логики просто вызывать
Speaker A
уровень репозитория. Да, такое бывает, но это не значит, что мы можем не писать уровень сервиса. Нет, сегодня у нас простой тудулист, который при создании задачи просто принимает HTTP запрос и кладёт полученные данные в базу данных.
Speaker A
А завтра мы начнём отсылать какие-нибудь уведомления на почту, начнём как-то дополнительно ещё присылать пушведомления пользователю на телефон, типа ты красавчик, там создал за сегодня три задачи, твоя продуктивность составляет там 50% и так далее, и так далее. И уже тогда, когда у нас вдруг
Speaker A
внезапно какая-то более сложная бизнес-логика появится, чем просто передать HTTP запрос, преобразовать данные и положить их в базу данных, когда у нас появится что-то посложнее, у нас уже будет отдельный уровень сервиса, в рамках которого мы сможем начать писать код, и нам не придётся
Speaker A
производить какой-то жёсткий рефакторинг. О'кей, про это мы с вами тоже поговорили. Возвращаемся к нашим доменам. Что же такое домены? Домены - это сущности, с которыми работает уровень сервиса. Так как уровень сервиса - это основной центральный слой нашего приложения, то эти самые домены - это
Speaker A
такие сущности, которые описывают какие-то ресурсы, какие-то структуры, какие-то данные, которые продиктованы бизнесом. Довольно расплывчатое понятие, но о чём я говорю? Например, ДТОшка, которая принимает HTTP запрос, она нам нужна только лишь для того, чтобы обслужить этот самый HTTP запрос.
Speaker A
моделька, благодаря которой мы какие-то данные в базу кладём либо получаем, она нужна только для того, чтобы обслужить работу с каким-то хранилищем. А вот домен, доменная сущность уже нужна для того, чтобы обслужить бизнес-требования.
Speaker A
Именно поэтому слой транспорта работает с ДТОшкой, слой репозитория работает с моделькой, а вот слой сервиса работает с доменом. То есть домен - это некоторая такая ключевая сущность в нашем приложении, на основе которой реализуется бизнес-логика. К примеру, у нас та же самая социальная сеть, Фича по
Speaker A
регистрации пользователя. ДТОшка у нас нужна просто, чтобы принять входящий HTTP запрос. Моделька нужна просто для того, чтобы какого-то там провалидированного зарегистрированного пользователя положить в базу данных. А вот домен уже будет представлять пользователя в нашей системе, который пытается зарегистрироваться. И на уровне
Speaker A
сервиса пользователь, который пытается совершить регистрацию, будет представлен именно в виде домена. Я понимаю, что довольно сложно это всё так абстрактно понять, но мы должны это обсудить перед тем, как перейдём к практике. И что самое весёлое, иногда бывает такое, что
Speaker A
ДТОшка, домен и модель практически ничем друг от друга не отличаются или вообще не отличаются. К примеру, у наслиist, у нас ДТОшка принимает входящий HTTP запрос на создание задачи, и там содержатся все поля задачи. Домен представляет нашу задачу с точки зрения
Speaker A
бизнеса, то есть это наша задача в ту-дулисте, над которой мы производим работу. И модель тоже содержит все поля нашей задачи, чтобы её сохранить в базу данных. В итоге у нас ДТОшка, домен и модель выглядят практически одинаковым образом, либо вообще полностью
Speaker A
совпадают. Да, такое бывает. Значит ли это, что мы должны абсолютно все операции совершать в домене и забыть про дшки и модели? Нет. Почему? Потому что зачастую те же самые ДТшки, они очень специфичны для какой-то фичины для какого-то конкретного эндпоинта.
Speaker A
Например, у нас ДТошка для того, чтобы создать задачу, действительно может быть похожа на доменную сущность. Ну, потому что там будут перечисления всех полей, задачи. А вот ДТошка, чтобы обновить какое-то поле в задаче, уже может быть совершенно не похожа на доменную
Speaker A
сущность. И благодаря тому, что у нас дшка и доменная сущность - это разные вещи, благодаря этому мы можем делать наш код более гибким и принимать в http запросах только те данные, только в таком виде, которые нам нужны, чтобы
Speaker A
именно обслужить слой транспорта в рамках нашей фичи, в рамках какого-то эндпоинта. И эта самая ДТошка может быть совершенно не похожа на доменную сущность. То же самое с моделью. У нас может быть такая ситуация, что для того, чтобы сохранить какие-то данные в
Speaker A
репозитории, нам нужен определённый набор полей. И этот набор полей может быть отличным от того набора полей, что есть в домене. Особенно это начинает раскрываться, когда у нас в базе данных таблицы связаны внешними ключами, и у нас модель получается, это некоторая
Speaker A
такая составная сущность из нескольких таблиц. Такое получается LEGO из нескольких сущностей. А вот домен, он представляет только какую-то одну конкретную сущность. Поэтому в этом примере мы не смогли бы использовать домен в качестве модели. Вот поэтому идёт такое разделение. Хорошо, кто-то
Speaker A
может спросить, почему ДТОшка, которая относится к транспортному уровню, она объявляется внутри какой-то фичи. То есть внутри Фичи есть транспорт, и в рамках этого транспорта объявляется ДТОшка. То же самое с моделью. В рамках Фичи есть репозиторий, и в рамках
Speaker A
репозитория объявляется модель. Но вот домен не объявляется в рамках сервиса, в рамках фечи. То есть у нас нет такого, что феча, в ней сервис и в ней там доменчик тут затисался. Как у нас с другими слоями? Почему мы так не делаем?
Speaker A
Почему домен у нас именно в какой-то общей части нашего приложения, в папке Core, в подпапке домены? Почему мы доменную сущность не объявляем на уровне сервиса? Вот здесь вот. Почему? Да потому что так как домен - это некоторая бизнес-определяющая
Speaker A
сущность нашего приложения, у нас может быть такое, что один и тот же домен будет использоваться из разных фичей.
Speaker A
Например, у нас социальная сеть. Одна фича представляет собой фичу по регистрации пользователя. И эта фича будет использовать вот этот домен пользователя. Потом у нас в социальной сети другая фича, возможность переписываться. То есть у нас один пользователь связывается с другим
Speaker A
пользователем. Так вот, эта самая фича тоже может использовать тот же самый домен пользователя. То есть домен - это такая сущность, в которой могут одновременно нуждаться несколько фичей.
Speaker A
И именно поэтому мы выносим домен в отдельную общую часть нашего приложения в папочку Core. Ну, вроде как такие основополагающие моменты касательно папочки мы с вами обсудили. Я понимаю, что, наверное, вообще ничего непонятно, но, к сожалению, тема архитектура приложения - это не что-то такое, что
Speaker A
можно вот сесть, видео на Ютубе посмотреть и сразу впитать все тонкости построения архитектуры приложений. К сожалению, нет. Навык построения архитектуры приложения может прийти к вам лишь с опытом, лишь когда вы напишите несколько откровенно плохих приложений с очень плохой архитектурой.
Speaker A
Только тогда вы начнёте по чуть-чуть понимать, почему же в итоге та архитектура, которую я использовал раньше, была плохой, и как сделать так, чтобы моих предыдущих ошибок можно было избежать. Ну вот, например, вы в целой ФЧ захотите использовать только лишь
Speaker A
домен. У вас не будет ни дтошек, ни моделей. Вы потратите какое-то время на написание своего приложения, а потом вдруг поймёте, что у вас нужно обслужить такой HTTP запрос. он будет принимать такие данные, которые вообще не похожи на доменную сущность, а у вас транспорт
Speaker A
уже использует доменную сущность по полной. И тут вы поймёте, что, ага, получается, где-то всё-таки нужно создать отдельную дтошку. Потом вы поймёте, что таких эндпоинтов у вас очень много. И вот эта доменная сущность, она вообще практически нигде в транспорте напрямую не подходит. всегда
Speaker A
будут какие-то лишние поля, которые усложняют ваш IP интерфейс, и тогда вы поймёте блин всё-таки наверное лучше для транспортного уровня отдельно создавать дтшки и не использовать домены в транспортном уровне. Ага, на будущее я это запомню. Ну и так далее, и так
Speaker A
далее, и так далее. Поэтому то, что мы сейчас обсуждаем, оно даёт какое-то общее понимание структуры приложения, какие-то минимальные аргументы за чтобы делать именно так, как мы сейчас с вами рассматриваем. Но, повторюсь, понимание какое-то более глубокое придёт только после практики. Это во-первых. А
Speaker A
во-вторых, важно упомянуть, что не существует какой-то супер-мега универсальной архитектуры для всех эээнд приложений. Поэтому даже то, что мы сейчас с вами рассматриваем, может подойти не везде. И каждый раз, когда вы строите архитектуру своего приложения, важно понимать, что вы делаете, зачем вы
Speaker A
делаете, зачем вы создаёте ту или иную папку, зачем вы создаёте тот или иной слой, почему ваши данные имеют именно такой поток от транспорта до репозитория, а не какой-то другой. Нужен ли отдельный домен для вашей фичи? Не нужен. Вот на все эти вопросы появляется
Speaker A
возможность ответить только при наличии достаточного опыта и при наличии, конечно же, понимание, зачем нам нужны те или иные слои, те или иные сущности, те иные папки и так далее, и так далее.
Speaker A
Но, несмотря на всё то, что я только что сказал, я считаю, что представленная архитектура, которую мы сейчас с вами рассматриваем на схеме и совсем скоро будем воплощать в жизнь на практике, эта архитектура является наиболее универсальной, наиболее подходящей под
Speaker A
широкий пласт бизнес-задач и бизнес-требований. Но, конечно, даже это архитектура, это не панацея, и всегда важно понимать, что вы делаете и зачем.
Speaker A
Ну, хорошо. Вроде как папочку internal мы с вами обсудили, примерно поняли тут, что, да, как. Какие ещё помимо папки у нас с вами могут быть папки в проекте?
Speaker A
Помимо папки обычно у нас в проекте есть ещё папка cmd. И в этой папке cmd находятся так называемые точки входа в наше приложение. Это main. Файлы. У нас main. Файл может сразу лежать в папке cmd, а может в какой-то дополнительной
Speaker A
подпапке. Например, у нас там может быть под папка To-Do app, то есть, да, какое-то наше To-Do приложение, и только тут уже main.go. Для чего так делается, да? Потому что у нас в рамках одного проекта может быть как основной сервис,
Speaker A
который представляет этот проект, так и всякие дополнительные воркеры, кронбы, какие-то прогреватели кэшей и так далее, и так далее, и так далее. Какие-то фоновые обработчики наших данных. И у них у всех будет свой main. Файл. Ну, а, соответственно, если приложение довольно
Speaker A
простое и в нём ничего этого нету, ну тогда можно просто такую структуру сделать. cmd название сервиса и main.
Speaker A
Файл точка входа в этот самый сервис. О'кей. И ещё есть такая магическая папка ПКГ. Что такое папка ПКГ? Вот папка ПКГ - это точка генерации бесконечного количества холеваров. Вот что же должно лежать в папке ПКГ? Есть такое вот
Speaker A
популярное мнение, что папка ПКГ нужна для того, чтобы сохранять в ней код, который может позволить использовать ваш проект как какую-то стороннюю библиотеку. Ничего непонятно, что было сказано. Ну давайте попробуем на каком-то примере рассмотреть.
Speaker A
Предположим, вы разрабатываете приложение, которое является каким-то высоконагруженным хранилищем, в которое каждую секунду летит огромное количество несколько сотен тысяч. HTTP запросов.
Speaker A
Несколько сотен тысяч раз в секунду вашекэнд приложение какие-то данные может сохранить в какой-то внешнее хранилище, какие-то данные при этом может внутри себя закэшировать, делает какую-то умную сериализацию и десериализацию этих данных в байты, как-то там круто пользуются кышами и так
Speaker A
далее, и так далее. И тут к вам приходят коллеги и говорят: "Слушай, очень классное приложение, очень крутое, классно держит нагрузку, классно работает с данными, вообще круто. Я бы хотел иметь возможность пользоваться функциональностью твоего приложения внутри своего приложения. В таком случае
Speaker A
код вот этого вот экэнд приложения, который инициализирует его основную функциональность, выносится в эту самую папку PKG, и потом этот код из main.
Speaker A
Файла импортируется. Таким образом это самое Backend приложение запускается. Но теперь благодаря тому, что вот этот вот инициализирующий основную функциональность ээнд- приложения код вынесен в папку ПКГ, теперь наши коллеги из совершенно другого проекта могут вот этот вот весь наш проект импортировать к
Speaker A
себе, скачать и взять вот этот вот код из папочки ПКГ, тем самым заюзав вот эту вот основную ключевую функциональность вашего классного высоконагруженного хранилища уже в рамках какого-то своего проекта. Ну вот тут пускай какой-то вот другой проект будет. Соответственно,
Speaker A
вашим коллегам, чтобы заюзать в рамках своего проекта вот эту вот вашу крутую функциональность, им уже не нужно запускать ваш проект, им не нужно запускать отдельное приложение, им не нужно создавать новый процесс и так далее, и так далее. Нет, они просто
Speaker A
импортируют вот этот вот инициализирующий код и потом получают уже в рамках своего приложения вот эту вот классную функциональность. Ну, примерно в таких целях иногда используется вот эта вот папка ПКГ. Но, повторюсь, это момент крайне халиварный.
Speaker A
Вам миллиард мнений на этот счёт скажут. И что самое интересное, вот даже в том примере, что я привёл, может быть, лучше не выносить этот код в папку ПКГ, а просто создать какой-то общий репозиторий, в котором будет вот эта вот
Speaker A
классная функциональность по быстрой, высоконагруженной обработке данных. И потом уже тот, кто нуждается в этой функциональности, будет импортировать этот общий репозиторий и использовать необходимые пакеты. Ну и зачем нам вообще тогда папка ПКГ? Да, я скорее придерживаюсь этого мнения, потому что
Speaker A
папка ПКГ требует дополнительной сложности в организации архитектуры нашего проекта. И зачастую, повторюсь, проще вынести вот этот классный код, который хочется использовать в рамках нескольких репозиториев, в какой-то общий отдельный репозиторий. Ну, в общем, кому как нравится, тот так эту
Speaker A
папку ПКГ и интерпретирует. Я лишь вам могу посоветовать, что если вы прямо точно не знаете, зачем вы выносите какой-то код в папку ПКГ, если конкретно ваши коллеги не придерживаются такого подхода и у вас нет какой-то задачи, которая бы требовала что-то выносить в
Speaker A
папку ПКГ, просто не создавайте её. Всё, всем будет спокойней. Единственное, что вам гарантируется - это то, что папку internal сможет импортировать только тот модуль, в рамках которого эта папка объявлена. То есть какой-то сторонний проект не сможет скачать вот этот вот
Speaker A
весь ваш модуль и что-то там импортировать из папки. Нет, этого сделать не получится, а из папки pк получится. Поэтому, если вам прямо нужно, чтобы какой-то сторонний гошный модуль мог импортировать какой-то код из вашего гошного модуля, тогда выносите этот код в папку ПКГ, предварительно
Speaker A
тысячу раз подумав, что же именно вы делаете. Ну и по сути, что касается именно гошных файлов, гошных папок, то это всё. То есть это вот основа папка cmd для точек входа в программу или в какие-то там воркеры, дополнительные
Speaker A
кронobбы и так далее, папка internal со всеми внутренностями нашего сервиса и опциональная папка PKG, назначение которой - это спорный момент и зачастую будет так. Вот в какую компанию команду вы придёте, как там принято, так вы и будете делать. Но повторюсь, папочка
Speaker A
довольно опциональная. далеко не во всех проектах она есть. Ну и тут уже на рассмотрение разработчика, как говорится. Поэтому я тут вот эту вот связь main. Файла и папки PКG уберу, чтобы показать, что это довольно опциональный и спорный момент. Ну а что
Speaker A
касается негошных файлов, то зачастую у нас в корне проекта есть ещё makeфайл, что по сути является файлом, в котором содержатся таргеты для управления нашим проектом. Это Docker файл, который нужен для того, чтобы создать Docker Image для нашего основного сервиса. Ещё бывает,
Speaker A
что этот docker файл кладут сюда. То есть, когда у нас там несколько различных воркеров в этой самой папке cmd, и на каждый воркер нужен свой doкеer файл, тогда docker файл кладётся именно в папочку cmd рядом с тем сервисом воркером или Кроjбой, к которым
Speaker A
этот Docker файл относится. Но если такого нету, тогда Docker файл просто в корне проекта лежит и нужен для сборки основного сервиса в рамках проекта.
Speaker A
Далее для более удобной работы с контейнерами Docker Compos файл создаётся, и уже через него, через вот эту вот абстракцию в виде докеркомпоза идёт работа с контейнерами. В этих самых контейнерах запускаются все сторонние зависимости, например, базы данных.
Speaker A
Далее у нас есть папочка migrations. В корне проекта тоже она лежит. Ну и там описаны всякие файлы миграций.
Speaker A
Соответственно, также всякие go файлы, goam файлы, тонфайлы, gitnore файлы. Также обычно создаётся такая папочка, что-то типа out она называется, всегда по-разному. И в этой самой папке содержатся логи приложения, то есть out, тут logs, и в этой папке logs уже логи
Speaker A
приложения. Также в папке обычно содержится докер волюмы, которые пробрасываются изнутри докер контейнеров. То есть, например, у нас запущен Docker контейнер с базой данных Postgress, и мы хотим пробросить docker Volume этой самой базы данных. Ну, для того, чтобы между запусками этого
Speaker A
контейнера с постгресом у нас данные сохранялись. Ну, также в папочке создаётся папочка там что-то типа PG дата, да, например, PG дата. И это является докер волюмом для данных внутри докерконтейнера с постгресом. Ну, понятно, что названия могут везде отличаться. Кто-то может папку features
Speaker A
назвать как-нибудь use cases или actions или как вообще душе угодно. Папочку Core могут назвать папочкой App, папочку PGATA как угодно могут назвать, расположить могут как угодно. Нужно быть просто к этому готовым. Но если вы поймёте, прочувствуете и попрактикуете
Speaker A
всё то, о чём я сейчас вам говорю, то потом, когда вы увидите какую-то другую структуру гошного приложения, гошного проекта, вам уже сильно проще будет разобраться. Ну вот будет называться уровень репозитория, не репозиторий, а, например, сторe. То есть как хранилище,
Speaker A
типа вот так вот сто. Ну вы всё равно разберётесь, что это именно слой репозитория в рамках приложения. Ну и так далее. О'кей. Кажется, мы обсудили с вами некоторые ключевые моменты в рамках темы архитектура ээнд приложения.
Speaker A
Повторюсь, я понимаю, что, возможно, это всё воспринимается довольно сложно, особенно когда видишь это впервые, кажется это каким-то огромным монстром.
Speaker A
Но, как говорится, ребята, глаза боятся, а руки делают. Чем больше вы получите практики, чем больше вы нарешаете домашних заданий, чем больше кода вы напишите, чем больше каких-то фичей попробуете реализовать, тем больше вы получите опыта, понимания и навыка. И
Speaker A
тогда уже вся вот эта вот архитектура эээнд приложения Нашке вас не будет пугать. Вы будете на неё смотреть и чувствовать себя, ну, просто как дома.
Speaker A
Вы заранее будете знать, какую папку создать, что в неё положить. Примерно, понятное дело, на месте будут какие-то тупнички локальные, но когда понимаешь общую структуру приложения, жить, конечно, становится гораздо проще. Ну и в конце кажется логичным провести закрепляющую практику по всему тому, что
Speaker A
мы с вами изучили. Но, к сожалению, на YouTube практически невозможно загрузить видео длиною более 12 часов, а это значит, что мы не успеваем провести закрепляющую практику. Но это не беда, ведь мы можем провести эту закрепляющую практику просто в следующем видеоролике.
Speaker A
Поэтому не забывайте подписываться на мои социальные сети. Кому понравилось, ставьте лайки, пишите, пожалуйста, комментарии, ведь они помогают продвигать данное видео. Всех жду в нашем приватном сообществе. Не забывайте ознакамливаться с дополнительными материалами. Не забывайте решать домашние задания и учебные проекты для
Speaker A
того, чтобы быть готовым сполна впитать в себя всю пользу закрепляющей практики, которую мы с вами проведём в рамках следующего видео. Ну а так всем огромное спасибо за просмотр. Если вам понравилось это видео, я вас очень попрошу поставить лайк, написать
Speaker A
объёмный комментарий. Ну и не прощаемся. Увидимся с вами в следующем ролике. M.
Topics:
Go
Golang
Git
Backend
Контроль версий
GitHub
Программирование
IT-сообщество
Docker
Базы данных