GOLANG ПОЛНЫЙ КУРС ДЛЯ НАЧИНАЮЩИХ | ЧАСТЬ 2 | ПРОДВИНУТЫЙ GO и REST API — Transcript
Full transcript
- 0:00Всем привет. Добро пожаловать на
- 0:01долгожданную вторую часть полного курса
- 0:04пон для начинающих. Хотел бы напомнить,
- 0:07что цель этой серии уроков - это с
- 0:09абсолютного нуля в программировании
- 0:11обучить человека всем необходимым
- 0:13техническим навыкам для работы над
- 0:15настоящими коммерческими проектами.
- 0:17Буквально после освоения всех частей из
- 0:20этой серии уроков можно смело идти
- 0:21делать резюме и после подготовки к
- 0:24собеседованиям искать работу. Поэтому
- 0:26советую прямо сейчас подписаться на мои
- 0:28YouTube и Telegram-каналы, чтобы не
- 0:30пропустить новые дропы качественного
- 0:32контента. В рамках текущей части мы с
- 0:34вами до конца рассмотрим основные
- 0:36аспекты языка программирования Goang, а
- 0:38также познакомимся с протоколом HTTP и,
- 0:41как следствие, напишем своё первое APP
- 0:44приложение. Для закрепления пройденного
- 0:46материала я настоятельно рекомендую
- 0:48решать домашние задания и писать учебные
- 0:51проекты. Как домашние задания, так и
- 0:53учебные проекты найдёшь в нашем
- 0:56приватном сообществе по ссылке в
- 0:57описании. Помимо домашних заданий и
- 1:00учебных проектов, в нашем приватном
- 1:01сообществе ты обнаружишь огромное
- 1:03количество инсайдов с настоящих
- 1:05собеседований и немалое количество
- 1:07гайдов о том, как же на эти самые
- 1:10собеседования попасть. Поэтому после
- 1:12решения домашних заданий и написания
- 1:14учебных проектов ты, не выходя из
- 1:16сообщества, сможешь подготовиться к
- 1:18собеседованию и найти свою первую
- 1:20работу. А теперь всем приятного
- 1:22просмотра.
- 1:24[музыка]
- 1:29Следующая тема - это интерфейсы. Сначала
- 1:32разберём, в принципе, значение слова
- 1:34интерфейс в обычной речи, в обычной
- 1:36жизни. Потом посмотрим, как это выглядит
- 1:38с точки зрения языка программирования.
- 1:40Ну и, конечно же, обсудим, зачем вообще
- 1:41нам нужна такая концепция, как
- 1:42интерфейсы. Скажу сразу, что, в
- 1:45принципе, в теории можно было бы жить
- 1:47без интерфейсов, но уж настолько они
- 1:49удобны. настолько много пользы приносят,
- 1:52что используется просто повсеместно. Для
- 1:54начала давайте попробуем разобраться
- 1:55вообще со значением слова интерфейс, не
- 1:57думая о языке программирования. Просто
- 1:59интерфейс вот в обычной жизни что такое?
- 2:02Ну, мы знаем, что у нас могут быть
- 2:03какие-то сайты, какие-то приложения,
- 2:05какие-то соцсети, и у них есть интерфейс
- 2:08для взаимодействия между пользователем,
- 2:09ну, и сайтом, например. То есть какой-то
- 2:11сайтик у нас есть, и у нас есть
- 2:12кнопочки, которые мы можем нажимать.
- 2:14Допустим, у нас вот есть сайт авто.ru, и
- 2:17у него куча, куча куча кнопочек. Вот мы
- 2:19можем на главной странице посмотреть
- 2:21какие-то рекомендации, а можем нажимать
- 2:23на разные кнопки. Ну вот, например,
- 2:24Moto. Нажимаю MO и у меня сразу же
- 2:27мотоциклы появляются. Нажимаю на
- 2:28какой-то отдельный мотоцикл, сразу
- 2:30фотки, его дополнительное описание, его
- 2:32характеристики. Но ведь это всё где-то
- 2:34хранится. Есть какая-то сущность,
- 2:36которая вот хранит все эти фотки, всю
- 2:37эту информацию, и я просто могу нажать
- 2:40на любую кнопочку, и мне вся эта
- 2:41информация будет выдана просто на
- 2:43блюдечке. То есть я могу не задумываться
- 2:45о том, как конкретно это работает, как
- 2:47конкретно хранятся все эти фотки. Ну,
- 2:50это, на самом деле, довольно сложно, как
- 2:51вот это всё описание хранить, как вот
- 2:53фильтровать мне выдачу по разным типам
- 2:55техники. Я об этом не думаю. У меня есть
- 2:57просто обычный интерфейсик сайтика, куда
- 2:59я нажимаю, и оно как-то за меня
- 3:01работает. Мне всё, что нужно - это
- 3:03понимать, какая кнопка за что отвечает.
- 3:05А как это работает под капотом, меня,
- 3:07если честно, ну, не интересует. То есть
- 3:09у нас есть интерфейсик, и он под собой
- 3:11скрывает какую-то сложную реализацию. То
- 3:13есть вот эта сложная реализация какая-то
- 3:15условная, абстрактная. Сайта ато.ru. Там
- 3:17есть различные компоненты, есть какие-то
- 3:19базы данных. Они хранят все эти фотки,
- 3:21они хранят все эти объявления, потом
- 3:23какие-то отдельные сервисы получают эту
- 3:25информацию с базы данных, её как-то
- 3:27фильтруют в зависимости от того, какую
- 3:29кнопку я нажал. Типа нажал MOТО, мне вот
- 3:31выдают сразу мототехнику. Нажал там
- 3:33электро, мне выдают вон
- 3:34электроавтомобиль, вон сразу какое-то
- 3:36отдельное оформление под
- 3:37электроавтомобили. Нажимаю там
- 3:38коммерчески, могу грузовики, мне сразу
- 3:40отфильтровывают грузовики. для каждого
- 3:42грузовика каким-то образом хранятся его
- 3:44фотографии, описания. Это всё на самом
- 3:45деле довольно сложно. Но я не думаю об
- 3:47этом. У меня есть интерфейс сайта. То
- 3:50есть в данном случае интерфейс - это
- 3:52какой-то набор кнопочек, какая-то
- 3:54сущность, которая, по сути, абстрагирует
- 3:55меня от вот этой вот сложной реализации
- 3:57конкретного сайта. Мне нужно лишь думать
- 4:00об интерфейсе этого сайта, о том, какие
- 4:02кнопочки за что отвечают, что будет,
- 4:04если я нажму ту или иную кнопку. Вот эта
- 4:05конкретная реализация меня, если честно,
- 4:07ну, не интересует. То есть один из
- 4:09вариантов интерфейса вот в принципе в
- 4:10нашей обычной жизни - это интерфейс
- 4:12сайта. То есть какие-то кнопочки,
- 4:13которые меня абстрагируют от конкретной
- 4:15реализации. Давайте рассмотрим ещё один
- 4:17пример интерфейсов в нашей жизни. Ещё
- 4:20один пример - это интерфейс для
- 4:22взаимодействия между водителем и
- 4:23автомобилем. Что я имею в виду на самом
- 4:25деле? Руль, педали, ручка переключения
- 4:28передач. Это всё тоже интерфейс, как мы
- 4:31с вами до этого смотрели. У нас был
- 4:33интерфейс сайтика, что мы можем нажимать
- 4:34какие-то кнопочки, получать какой-то
- 4:36результат и не задумываться о том, как
- 4:38это всё устроено под капотом. То же
- 4:40самое для нас предоставляет руль, педали
- 4:42и ручка переключения передач. Мы просто
- 4:44знаем, что можем крутить руль, нажимать
- 4:46педали и переключать передачи. И
- 4:48автомобиль будет ехать, будет газовать,
- 4:50тормозить, будет выжиматься сцепление,
- 4:52будет переключаться передача. Если мы
- 4:54крутим руль влево, то колёса будут
- 4:55крутиться влево. Если мы крутим руль
- 4:57вправо, то колёса будут крутиться
- 4:58вправо. И нам даже не нужно
- 5:01задумываться, особенно если какой-то
- 5:02современный автомобиль, о том, каким
- 5:04именно образом там это всё происходит,
- 5:06каким образом вообще мы тормозим, если я
- 5:08нажимаю тормоз. Я просто нажимаю тормоз,
- 5:10машина сбавляет скорость. Я нажимаю газ,
- 5:12машина набирает скорость. Я там включил
- 5:14заднюю передачу, машина назад поедет. Я
- 5:16повернул руль налево, она налево
- 5:17повернёт. Я благодаря рулю, педалям и
- 5:20ручке переключения передач могу не
- 5:22думать о том, как в автомобиле все его
- 5:25сложные системы устроены. На самом деле
- 5:26автомобиль - это очень сложная система,
- 5:28особенно для тех, кто впервые на неё
- 5:29смотрит. То есть у нас есть какая-то
- 5:31топливная система, да, там есть
- 5:32топливный бак, топливный насос, он всё
- 5:34качает топливо в наши форсунки. Форсунки
- 5:37как-то впрыскивают топливо в наш
- 5:38двигатель. Двигатель благодаря этой
- 5:39топливно-воздушной смеси создаёт
- 5:41какой-то крутящий момент. Этот крутящий
- 5:43момент передаётся на коробку передач.
- 5:44Коробка передач, особенно если она
- 5:46автоматическая, она может быть очень
- 5:47сложно устроена. Она выбирает, каким
- 5:49именно образом, с каким соотношением
- 5:51передать крутящий момент на колёса в
- 5:53зависимости от скорости оборотов движка.
- 5:55колёса тоже крутятся. У колёс есть своя
- 5:57тормозная система, там есть всякая
- 5:58тормозная жидкость, которая тоже
- 6:00хранится у нас под капотом. Есть всякие
- 6:02вакуумные усилители тормозов, которые
- 6:03помогают нам тормозить легко и плавно. И
- 6:06это всё довольно сложная система. Без
- 6:09шуток, если изучать какие-нибудь
- 6:10современные автомобили, можно года
- 6:11потратить на то, чтобы понять, как это
- 6:13всё устроено. Но человек, который просто
- 6:15купил себе новую машину, он не хочет вот
- 6:16этого всего изучать, что там в движке,
- 6:18как устроены, поршни, цилиндры, камеры
- 6:20сгорания, там коленвалы и так далее, и
- 6:23так далее, и так далее. Ну это вообще
- 6:24его не интересует. Человек, который
- 6:26купил автомобиль, он просто хочет
- 6:27крутить руль, жмакать педальки и
- 6:29передачки переключать и кайфовать от
- 6:30жизни. И этот самый руль педали и ручка
- 6:33переключения передач предоставляет эту
- 6:34возможность. То есть это является для
- 6:37нас интерфейсом для взаимодействия с
- 6:39автомобилем. Мы просто крутим руль,
- 6:41просто нажимаем педали, просто
- 6:42переключаем передачи и всё. И мы не
- 6:44думаем о конкретной реализации того или
- 6:46иного автомобиля. То есть доходит до
- 6:48того, что у нас в принципе может быть
- 6:49один и тот же автомобиль, но с разной
- 6:51комплектацией. в какой-то комплектации
- 6:53может быть вообще установлен другой
- 6:55тормоз, другая топливная система, другой
- 6:56двигатель, другая коробка передач. Но
- 6:58интерфейс он для нас останется просто
- 7:01руль, ручка переключения передач и
- 7:02педаль. Получается, для нас руль, ручка
- 7:04переключения передач и педали - это
- 7:06просто интерфейс, который нас
- 7:08абстрагирует от конкретной реализации
- 7:10автомобиля. Точно так же, как у нас было
- 7:13с интерфейсом сайтика. У нас есть
- 7:15какие-то кнопочки, мы их нажимаем и
- 7:17вообще не думаем о том, как оно там под
- 7:18капотом всё устроено. Поэтому это и
- 7:20называется интерфейсом. То есть что-то,
- 7:22что дано пользователю в руки.
- 7:25Пользователь может нажимать это, может
- 7:26это использовать. И это всё, о чём
- 7:28пользователю нужно знать. Как и в случае
- 7:30с автомобилем. Всё, о чём пользователю
- 7:32нужно знать - это то, какие кнопки он
- 7:34нажмёт и к какому результату это
- 7:35приведёт. И подкапотная реализация уже
- 7:37не сильно интересует. Вот это значение
- 7:39слова интерфейс, как бы, в нашей обычной
- 7:41жизни. То есть что-то, какие-то кнопки,
- 7:42какие-то рычаги управления, которые
- 7:44вынесены пользователю, и пользователь
- 7:46должен думать только о них. это рычаги
- 7:48управления какой-то техникой или
- 7:50каким-то сайтом. Это интерфейс.
- 7:51Подкапотная реализация уже пользователя
- 7:54в идеале не должна как-то интересовать,
- 7:55потому что мы ему дали интерфейс для
- 7:57взаимодействия, сказали: "Будешь педаль
- 7:58нажимать, будет всё круто ехать". А то,
- 8:00что уже именно будет происходить под
- 8:02капотом, об этом я сам позабочусь. Ты
- 8:04просто вот педаль нажимай, короче,
- 8:05будешь ехать. Вот это значение слова
- 8:07интерфейс в обычной жизни. То есть
- 8:09сейчас я говорил без относительно
- 8:10каких-то языков программирования, просто
- 8:12чтобы на обычном человеческом понимании
- 8:14немножко начать чувствовать этот момент,
- 8:15что такое интерфейс вообще. Ну, про
- 8:17значение слова интерфейс в обычной жизни
- 8:19поговорили. Теперь давайте посмотрим,
- 8:21как интерфейсы работают в коде. Сейчас
- 8:23мы рассмотрим чисто технически, как
- 8:25работать с интерфейсами. Потом мы
- 8:26обсудим, для чего они нужны, и уже
- 8:28рассмотрим пример, где используются
- 8:30интерфейсы как-то осмысленно. Сейчас у
- 8:32нас будут бессмысленные примеры,
- 8:34единственная цель которых показать, как
- 8:36именно с технической точки зрения
- 8:37работают интерфейсы. Ну, к примеру,
- 8:39смотрим, у нас есть структура
- 8:41какая-нибудь,
- 8:43ну, к примеру, вот BMW, то есть машина
- 8:45BMW, она как бы у нас не будет иметь
- 8:48каких-то полей, но когда мы о этой BMW
- 8:51будем говорить: "Жми газ", типа step onз
- 8:56то она будет выводить на экран.
- 8:58Я BMW 550
- 9:02лошадиных сил.
- 9:05Жмём на газ.
- 9:08Вот просто структурка BMW, у неё никаких
- 9:10полей нету. Есть один метод вот Step on,
- 9:12который вводит: "Я BMW, я там жму на
- 9:14газ". Ну давайте проверим, как это
- 9:15работает. Создам вот экземпляр этой
- 9:17структуры и вызову метод Step onнгаз.
- 9:20Запускаю программу.
- 9:23Ну и как мы видим, вот наша обычная
- 9:24структура, да, мы её вот здесь вот
- 9:26описали, она пустая, в ней нет никаких
- 9:28полей. Мы создали экземпляр этой
- 9:30структуры, но у неё есть метод stнгаз.
- 9:32Мы его вызвали, и он просто вводит на
- 9:33экран. Вот я BMW, там 550 лошадиных сил.
- 9:35Жмём на газ. Теперь давайте создадим ещё
- 9:37одну похожую структуру, только уже не
- 9:38BMW, а у нас будет жига. Ну,
- 9:40какая-нибудь там 2107, я не знаю. У неё
- 9:42будет такой же метод step onнгаз.
- 9:46И будет он выводить Я жига 2107.
- 9:53Пробую не развалиться.
- 9:55Ну, надеюсь, владельцы Жигулей не
- 9:57ущемятся, потому что я сам владелец,
- 9:58только 2114. Так что эту боль я с вами
- 10:01разделяю. Ну или это счастье, там уж кто
- 10:03как смотрит на жизнь, в принципе. Ну
- 10:05давайте, вот мы описали нашу пустую
- 10:07структуру Жига. В принципе, то же самое,
- 10:08что и BMW, только вот метод Step on
- 10:10будет выводить не я BMW, а я Жига. Ну,
- 10:12потому что он относится к Жиг. Ну,
- 10:13давайте создадим экземпляр.
- 10:15Жига. Создаём экземпляр. И у этой Жиги
- 10:19мы тоже вызываем stepпнгаз. Ну давайте
- 10:21запустим, проверим, что у нас
- 10:22происходит. Ну хорошо, типа, первый раз
- 10:24мы создаём BMW, вызываем метод
- 10:26степонгаз, вводится я BMW. Второй раз
- 10:27создаём Жигу, вызываем у неё Степангаз,
- 10:30выводится я, Жига 217. Пробую не
- 10:31развалиться. Хорошо. Теперь представим,
- 10:32что у нас есть функция, которая у нас
- 10:35будет описывать взаимодействие с
- 10:36каким-то отдельно взятым автомобилем.
- 10:38Ну, назову эту функцию write. Она будет
- 10:40принимать что-то, пока мы ещё не выбрали
- 10:42что. И всё, что она будет делать эта
- 10:44функция - это выводить: "Я водитель.
- 10:49Я сажусь в свою машину
- 10:55и нажимаю на газ.
- 10:58После этого мы в зависимости от того,
- 11:00какая у нас машина, будем нажимать на её
- 11:02газ. Ну, к примеру, вот у нас функция
- 11:04right. И сначала опишем её для BMW. Ну,
- 11:06она будет принимать как бы у нас копию
- 11:08нашей структуры BMW. Тут как бы
- 11:10указатели нам не нужны, потому что мы
- 11:11никак не планируем исходную структуру,
- 11:13созданную в мейне, менять. Ну и всё,
- 11:14просто можем после этого вызвать
- 11:16stepнгаз у BMW. Соответственно, давайте
- 11:18пока жигу у нас сотрём. У нас вот есть
- 11:20BMW, и мы хотим сделать наш, то есть
- 11:23вызвать функцию и передать туда BMW.
- 11:26Ну что будет? У нас как бы тут просто
- 11:28создаётся наш экземпляр BMW. Передаём
- 11:30функцию right. Right принимает копию
- 11:32этой BMW, выводит: "Я водитель, сажусь в
- 11:33машину, нажимаю на газ". Потом у нас
- 11:35будет вызов метода Step on у BMW. Ну,
- 11:37соответственно, выдится я BMW. Ну,
- 11:38давайте проверим это всё. Я водитель,
- 11:41сажусь свою машину, нажимаю на газ. И
- 11:42когда он нажал на газ, так как мы
- 11:44передали BMW, у нас я BMW 550 сил.
- 11:46Хорошо, теперь представим, что мы хотим
- 11:47сделать такую же функцию, только с
- 11:49возможностью передавать туда Жигу. Ну,
- 11:51хорошо, нам, получается, нужно это всё
- 11:52просто скопировать. И тут у нас вместо
- 11:54BMW принимать Жигу.
- 11:57И тут уже у Жиги вызывает Степангаз. Но
- 12:00мы начинаем сталкиваться с двумя
- 12:01проблемами. Во-первых, это дублирование
- 12:03кода. То есть, если бы у нас тут код был
- 12:05какой-то более сложный, более сложное
- 12:06взаимодействие с автомобилем, то у нас
- 12:08было бы вообще страшное дублирование там
- 12:09на несколько десятков, может быть, и
- 12:11сотен строк кода. И это поддерживать,
- 12:12ну, в принципе, было бы очень сложно. Мы
- 12:14уже говорили, почему. Потому что мы бы
- 12:15захотели, например, выводить не я
- 12:16водитель, а я счастливый обладатель. Нам
- 12:18бы пришлось тут менять счастливый
- 12:21водитель, ну или обладатель.
- 12:24и тут менять. А теперь представим, что у
- 12:26нас не только Жига, BMW, ещё Mazda,
- 12:27Toyota там и вообще что угодно. Ну,
- 12:29блин, нам в десяти разных местах менять
- 12:31это всё очень удобно. Ну, хорошо, это
- 12:33первая проблема. То есть обычное
- 12:34дублирование кода. Какая вторая
- 12:35проблема? Вторая проблема то, что у нас
- 12:37функции называются по-одинаковому, а
- 12:39такого не может быть, потому что язык
- 12:40программирования не может понять, когда
- 12:41мы будем писать right. Какую же в итоге
- 12:44из Wite вызывать, которая BMW принимает
- 12:46или Жигу? Нам бы пришлось как-то
- 12:48изворачиваться типа write BMW и тут
- 12:50right Жига. Ну и хорошо. Так бы мы
- 12:52постоянно писали right Mazda, write
- 12:54Газель, write Camas, write Lamborghini,
- 12:56там что угодно. Это неудобно, потому
- 12:59что, во-первых, нужно каждый раз
- 13:00выдумать какое-то название. Во-вторых,
- 13:01самая вот основная проблема - это
- 13:02дублирование кода. Нам бы было бы круто
- 13:04передавать сюда не конкретно BMW или
- 13:06Жигу, а просто передавать какой-то
- 13:08автомобиль, которому будут нажимать на
- 13:10газ. И уже в зависимости от того, что
- 13:12там у этого автомобиля выводится при
- 13:14нажатии на газ, то и будет выводиться.
- 13:16То есть нам бы хорошо какую-нибудь такую
- 13:18вот функцию, которая бы принимала просто
- 13:20авто, да, и делала вот всё то же самое,
- 13:25только в конце бы не жигали BMW, а авто,
- 13:28автопongas. И в итоге мы бы могли вот в
- 13:30эту однуединственный раз написанную без
- 13:32всякого дублирования кода функцию
- 13:34передавать любое авто, и уже в
- 13:36зависимости от того, что там именно за
- 13:38авто лежит, уже бы тот вывод у нас
- 13:39выводился. Так, есть такая возможность в
- 13:41Голнге, она как раз-таки называется
- 13:43интерфейсы. То есть мы теперь немножко
- 13:45вот то, о чём мы говорили, что такое
- 13:46интерфейсы в реальной жизни, пробуем
- 13:47переносить на язык программирования. То
- 13:49есть в Голенге интерфейсы - это
- 13:50возможность объявить методы, которые мы
- 13:53хотим видеть у автомобиля в данном
- 13:55случае, но никак мы не завязываемся на
- 13:57конкретную реализацию этого самого
- 13:59автомобиля. Ну давайте я напишу код,
- 14:00потом посмотрим на него. Ну для того,
- 14:02чтобы в Голнге объявить интерфейс, нужно
- 14:03написать следующим образом.
- 14:07Объявление интерфейса очень сильно
- 14:08похоже на объявление структуры. Только
- 14:10вместо слова strctctт мы пишем слово
- 14:12интерфейс. И в теле интерфейса мы должны
- 14:15написать, объявить методы, которые мы
- 14:17хотим видеть у всех структур, которые бы
- 14:21подходили под этот интерфейс. Я понимаю,
- 14:22сейчас ничего непонятно. Сейчас
- 14:24потихонечку картина будет перед вами
- 14:25открываться. Тут я пишу Step on,
- 14:28[аплодисменты]
- 14:29и я могу создать вот эту самую функцию
- 14:32WR, которая у меня будет
- 14:35принимать не BMW, не жигу, а просто
- 14:38авто.
- 14:39Ну и будет выводить вот это вот всё то
- 14:41же самое, только в конце не жига, а
- 14:42авто. Хорошо. Что тут вообще произошло?
- 14:45Я сказал, что хочу сделать интерфейс и
- 14:49назвал его авто и говорю, что я буду
- 14:51считать автомобилем всё. Я буду считать
- 14:53автомобилем каждую структуру, у которой
- 14:56есть метод stpнгаз. Вот если есть у тебя
- 14:58метод stp onнгаз, то я буду
- 14:59автоматически считать тебя автомобилем,
- 15:01и я могу тебя использовать как
- 15:03автомобиль в разных местах. Всё, теперь
- 15:06у меня есть одна единственная функция
- 15:07right. Она будет принимать автомобиль. И
- 15:10уже мы можем туда передавать любую
- 15:12структуру, у которой есть метод Step
- 15:14onнгаз. Почему? Да потому что мы это
- 15:15сказали в интерфейсе. По большей части
- 15:17всё как с нашими примерами интерфейсов
- 15:18из реальной жизни. Нам неважно, что там
- 15:20за движок, что там за коробка, что там
- 15:21за топливная система, что там за
- 15:22форсунки, всё это неважно. Нам важно,
- 15:24чтобы были педали, руль, ну и рычаг
- 15:26переключения передач. Всё это наш
- 15:28интерфейс. Точно так же нам тут важно
- 15:30лишь, чтобы был метод степнгаз и всё. А
- 15:32что там конкретно будет происходить, нас
- 15:34уже не интересует. Нас интересует, что
- 15:35просто если есть метод step on, если
- 15:37можно нажать на газ, тогда это
- 15:39автомобиль. Ну и давайте посмотрим, как
- 15:41это использовать. Мы вот можем создать
- 15:43BMW, можем создать Жигу.
- 15:48Ну и давайте пока закомментирую Жигу и
- 15:49вот вызову нашу функцию WR, которая
- 15:52принимает авто. Я туда теперь могу
- 15:55передать что угодно, у чего есть метод
- 15:57stпоpга. Вот у меня есть у бэхи метод
- 16:00stepнгаз, да? Я беру туда передаю бэху.
- 16:03И несмотря на то, что функция R
- 16:04принимает какое-то авто, я туда могу
- 16:07передать BMW. Видите, тут я передаю туда
- 16:09BMW, хотя принимает она авто. Но почему
- 16:12это работает? Потому что авто - это
- 16:14интерфейс. Что значит интерфейс? Это
- 16:16значит, что я могу под собой хранить
- 16:19всё, что угодно, что подходит под моё
- 16:21описание. Ну а у нас описание интерфейса
- 16:23- это просто, что нужно, чтобы был метод
- 16:24stнгаз. Всё то есть. Это значит, что
- 16:27функцию WR мы можем передать любую
- 16:30структуру, у которой есть метод step on
- 16:34объявили в интерфейсе метод Step on, мы
- 16:36объявили функции R, что мы принимаем
- 16:38этот интерфейс. Это значит, что мы можем
- 16:40принимать любую структуру, у которой
- 16:42есть метод step onнгаз. У BMW есть метод
- 16:44step onнгаз, да? Значит, я могу передать
- 16:47туда беху. Давайте запустим, посмотрим,
- 16:49что происходит.
- 16:51Я водитель, сажусь свою машину, нажимаю
- 16:53на газ. Я BMW. Всё, мы туда передали
- 16:55БХУ. Я BMW. Теперь давайте я
- 16:56закомментирую BMW, раскомментирую Жигу и
- 17:00передам нашу функцию Wражигу. Сохраняю,
- 17:02запускаю и всё. То же самое. Мы вызываем
- 17:06ту же самую функцию. Ничего не
- 17:07поменялось. Поменялось лишь то, что
- 17:09именно мы передаём в эту функцию. Именно
- 17:12структура, которую мы передаём в этот
- 17:14интерфейс. Теперь у нас интерфейс авто
- 17:16принимает не BMW, он принимает Жигу. И у
- 17:18нас вводится: "Я жига пробую не
- 17:20развалиться". То есть мы, объявив
- 17:22интерфейс, мы абстрагировались от
- 17:24какой-то конкретной реализации в нашей
- 17:26функции. И всё, что мы сказали, нам
- 17:28важно, чтобы у этого чего-то был метод
- 17:31степнгаз. И нам не важно, что он там
- 17:33этот метод будет делать под капотом, как
- 17:35именно будет этот степнгаз нажиматься.
- 17:37Нам лишь важно, чтобы принимаемая
- 17:39структура имела метод степнгаз. И всё. И
- 17:41нам уже не нужно создавать разные
- 17:42функции под каждую конкретную структуру.
- 17:44Мы можем увидеть какую-то общую картину
- 17:47разных структур. В данном случае это что
- 17:49Жига метод газ имеет, что BMW метод газ
- 17:51имеет. И мы просто можем сказать: "Вот
- 17:53мне вот реально пофигу, что я буду
- 17:54принимать в качестве аргумента. Мне
- 17:56главное, чтобы у этого был метод
- 17:57Степнгаз". И всё. Соответственно, мы
- 17:59избавлены от необходимости писать
- 18:01однотипные функции для разных типов
- 18:03автомобилей. У нас есть одна функция
- 18:06write, которая будет подходить под всё,
- 18:08что угодно, у чего есть метод step on то
- 18:10есть теперь я могу спокойно создавать,
- 18:12например, ламба. У меня вот будет у
- 18:15ламбы метод stпнгаз и будет вводиться я
- 18:18ламба. Там 1.500 лошадиных сил. Еду
- 18:21быстро. И теперь тут просто создаю ламбу
- 18:26и могу вызывать метод, передавая в него
- 18:28ламбу. Запускаем. Я водитель, сажусь. Я
- 18:31ламба 1.500 лошадиных сил. Всё, функция
- 18:34осталась та же. Она просто принимает
- 18:36какое-то авто. А мы сказали, что авто -
- 18:38это интерфейс. Что значит интерфейс? Это
- 18:40значит, что мы не завязываемся на
- 18:43конкретную структуру, мы завязываемся на
- 18:45то, как эта структура должна выглядеть.
- 18:47То есть мы, если пишем, что мы принимаем
- 18:49какой-то интерфейс, а в данном случае
- 18:51авто, это у нас интерфейс, мы говорим,
- 18:53что мы сможем работать со всем чем
- 18:55угодно, что подходит под этот интерфейс.
- 18:57Ну а чтобы у нас какая-то структура
- 18:59подходила под интерфейс, нужно, чтобы
- 19:00все методы этой структуры
- 19:02соответствовали описанному интерфейсу. А
- 19:04у нас описанный интерфейс говорит, что
- 19:05должен быть метод Step onгаз. Всё.
- 19:06Теперь давайте посмотрим, что будет,
- 19:07если мы в этот интерфейс добавим
- 19:09какой-то новый метод. Например, тормоз.
- 19:11Ну вот, типа step on break. То есть
- 19:13брейк - это тормоз английского. Теперь
- 19:14давайте посмотрим, что же случилось.
- 19:16Теперь мы говорим, что мы будем считать
- 19:18автомобилем всё, у чего есть метод
- 19:20нажать на газ и нажать на тормоз. И
- 19:22теперь какая-то ошибка появляется.
- 19:23Наводим, и нам говорят: "Мы не можем
- 19:25использовать ламбу как автомобиль,
- 19:28потому что у ламбы нету метода Step on
- 19:31break. Теперь у нас по сути всё, что мы
- 19:33написали, все структуры, они уже не
- 19:35являются автомобилем, потому что мы
- 19:37сказали, что автомобилем будет являться
- 19:39только то, что нажмёт на газ и у чего
- 19:41есть метод нажать на тормоз. А у нас
- 19:43нигде нету этого метода нажать на
- 19:44тормоз. Соответственно, уже мы не можем
- 19:46ничего из этого передать в right.
- 19:49Почему? Казалось бы, а потому что если
- 19:52мы принимаем интерфейс авто, это
- 19:54подразумевает, что мы должны быть
- 19:56уверены, что у того, что мы принимаем,
- 19:58есть метод Step on break, потому что мы
- 19:59этот метод можем вызвать. Например,
- 20:01сразу после того, как мы нажали на газ,
- 20:02мы хотим нажать на тормоз. А если мы
- 20:04передадим туда структуру, у которой нету
- 20:05нажатия на тормоз, и что делать тогда,
- 20:07непонятно. Поэтому у каждой структуры,
- 20:10которую мы будем передавать в этот
- 20:11интерфейс, должен быть объявленный
- 20:13метод, потому что мы этот метод будем
- 20:15вызывать в нашей функции или в теории
- 20:17можем в любой момент вызвать. А если под
- 20:19капотом у машины не окажется тормозов,
- 20:21тогда мы и нажать не сможем. Хотя мы в
- 20:23функции сказали, что мы это сделать
- 20:25должны. Поэтому для того, чтобы вот что
- 20:27BMW, что Жига, что ламба смогли быть
- 20:30переданы функцию, они должны
- 20:31удовлетворять этому интерфейсу. Они
- 20:33удовлетворяют ему только наполовину.
- 20:35Только step on gas есть у всех, а step
- 20:36on break нету. Ну давайте исправим эту
- 20:38проблему. И вот, допустим, добавим
- 20:40тормоза BMW,
- 20:43объявим ей метод Step on break и будем
- 20:45выводить.
- 20:47Я BMW. Тормоз в пол. Хорошее торможение.
- 20:52Хорошо. Ламбу мы всё ещё не можем
- 20:54передать, потому что у нашей ламбы
- 20:55тормозов нет. Зато тормоза появились у
- 20:57BMW. Давайте вот раскомментирую BMW,
- 20:59закомментирую лампу и попробую передать
- 21:01BMW. Всё, вроде как ошибок никаких нету.
- 21:04Давайте запустим программу. Всё, я BMW
- 21:06550 сил. Я BMW тормоз пол, хорошее
- 21:07торможение. Всё работает. И как только
- 21:10мы вот этот метод удалим, опять будет
- 21:13ошибка, потому что у BMW опять пропадут
- 21:14тормоза, хотя мы их используем. Ну,
- 21:16возвращаем. Таким образом, мы можем
- 21:18понять, что интерфейсы - это возможность
- 21:21абстрагироваться от конкретной
- 21:22реализации для того, чтобы писать
- 21:25какие-то унифицированные функции, для
- 21:26того, чтобы избавляться от дублирования
- 21:28кода. То есть один раз написали функцию,
- 21:30и она подходит под всё, что может
- 21:33прикинуться интерфейсом авто. И уже тот
- 21:35человек, который нашу функцию будет
- 21:36использовать, если он захочет туда
- 21:38передать какое-то авто, он уже сам
- 21:40должен посмотреть, что у его автомобиля
- 21:42должен быть метод нажать на газ и нажать
- 21:43на тормоз. Если этих методов у него не
- 21:45будет, то мы не можем сказать, что он
- 21:47автомобиль и он уже должен будет сам
- 21:49дорабатывать свою машину, чтобы она
- 21:51могла удовлетворять этому интерфейсу.
- 21:53Соответственно, основное вообще
- 21:54преимущество интерфейсов - это
- 21:55возможность избавиться от дублирования
- 21:57кода. Повторюсь, если бы интерфейса не
- 21:59было, то нам бы пришлось писать функцию
- 22:01WR, которая принимает сначала BMW, потом
- 22:03которая принимает жиду, потом которая
- 22:04принимает ламбу. А если у нас
- 22:05какое-нибудь приложение типа Ато.ru, где
- 22:07у нас там просто куча различных
- 22:09автомобилей может быть и марок, и
- 22:11брендов, там и китайские, и корейские,
- 22:12японские, вообще что угодно,
- 22:13отечественные. Но вы представляете,
- 22:15сколько однотипных функций бы пришлось
- 22:16написать и какие бы большие проблемы с
- 22:18дублированием кода мы бы начали
- 22:19испытывать. А так интерфейсик написали,
- 22:21сказали, что всё, у чего есть нажать на
- 22:23газ, нажать на тормоз - это авто, и
- 22:25написали функцию, которая принимает
- 22:26интерфейсик. И всё. И она один раз
- 22:28написана. И потом мы туда уже можем
- 22:30передавать разные структуры и
- 22:32использовать их в одной функции. Мы
- 22:34избавляемся от дублирования кода.
- 22:35Главное, чтобы у этих передаваемых
- 22:37структур были все методы, которые
- 22:39описаны в интерфейсе. У структур может
- 22:40быть гораздо больше методов, чем описано
- 22:42в интерфейсе. Например, в BMW у нас ещё
- 22:44может быть бип-бип. Что это значит? Это
- 22:46значит, что я BMW, я там я нажимаю
- 22:50колаксом. Ну то есть вот эту бибиколку.
- 22:52У нас как бы теперь BMW умеет и на газ
- 22:54нажимать, и на тормоз, и бибикать, но
- 22:56всё равно мы можем её использовать в
- 22:57интерфейсе авто. Почему? Потому что
- 22:59интерфейс авто не говорит, что у
- 23:01структуры должны быть только именно
- 23:04такие методы. Он говорит, что эти методы
- 23:06должны быть. Но если у используемой
- 23:08структуры методов больше, ну и пускай.
- 23:11Ну и хорошо. Главное, чтобы у неё были
- 23:13step on и step on break. Ну а у BMW они
- 23:16есть. А если у неё какие-то другие ещё
- 23:18методы? Да, хорошо, вообще. Ну типа нас
- 23:20это не беспокоит, ведь мы в интерфейсе
- 23:22авто сказали, что мы считаем автомобилем
- 23:23всё, у чего есть нажать на газ, нажать
- 23:25на тормоз. Если у этого чего-то есть
- 23:27больше возможностей, ну и славно, нам
- 23:29это не помешает. Ведь мы же умеем
- 23:30нажимать на газ и на тормоз, а значит,
- 23:32это автомобиль. Всё. Поэтому мы даже,
- 23:34если какие-то методы будем добавлять
- 23:35новые, то мы сможем использовать беху
- 23:37как авто. Ну и, соответственно, любая
- 23:38другая машина, которой мы добавим Step
- 23:40on break, она тоже сразу же станет
- 23:42автомобилем. Ну вот давайте Жиги добавим
- 23:43Step on breakк. Я Жига.
- 23:47Вот, блин, тормоза чуть не отвалились.
- 23:50Теперь у нас у Жиги тоже естьдстepрей. И
- 23:53если мы будем передавать Жигу в нашу
- 23:55функцию,
- 23:57то никаких ошибок это вызывать не будет,
- 23:59потому что Жига теперь у нас будет
- 24:01имплементировать интерфейс авто, потому
- 24:03что она содержит все методы, которые в
- 24:05этом интерфейсе объявлены. И,
- 24:06соответственно, мы эту Жигу можем
- 24:08использовать как авто. Чуть только у
- 24:10Жиги не будет тормозов, всё сразу нам
- 24:12скажут, что типа Жига не может быть
- 24:14использована как авто, потому что нет
- 24:16метода Step on break. Возвращаем этот
- 24:17Step onbreak, всё отлично работает.
- 24:19Давайте запустим и увидим, что мы
- 24:21вызываем ту же самую функцию write, но у
- 24:23нас уже она работает не с BMW. Видите,
- 24:25целых два вызова BMW было, а с жигой.
- 24:27Вот они, эти наши два вызова. Но функция
- 24:29одна, и мы избавлены от дублирования
- 24:31кода. Просто можем имплементировать этот
- 24:33интерфейс авто в разных структурах. Он в
- 24:35BMW, в Жиге, Ламбе можем тормоза
- 24:37добавить. Тогда ламба тоже будет
- 24:39считаться автомобилем. И всё. И
- 24:40передавать их и использовать. Это второй
- 24:43принцип, который мы с вами рассмотрели.
- 24:44Один из самых основополагающих
- 24:46программирований. Первый у нас был, если
- 24:47помните, инкапсуляция. Это когда мы в
- 24:49каких-то других пакетах с вами прятали
- 24:51работу с какими-то чувствительными
- 24:52полями за конструкторами и методами. А
- 24:55сейчас у нас принцип полиморфизма. Что
- 24:57это значит? Это значит, что мы один раз
- 24:59написали какую-то функцию и можем
- 25:02использовать её с разными формами, с
- 25:03разными структурами. То есть полиморфизм
- 25:05- это как бы переводится множественность
- 25:07форм. Это значит, что мы вот в одном
- 25:08месте можем использовать различные
- 25:10формы, ну, в данном случае различные
- 25:11структуры. Просто если структура
- 25:13подходит под интерфейс авто, она может
- 25:15быть сюда передана. Это полиморфизм.
- 25:17Таким образом, мы разобрались с
- 25:18технической точки зрения, как работают
- 25:19интерфейсы. Давайте же теперь перейдём к
- 25:21какому-то более осмысленному примеру и
- 25:23рассмотрим использование интерфейсов в
- 25:24этом самом примере. Теперь давайте
- 25:26разберём интерфейс на более таком
- 25:29реальном примере, на примере, который
- 25:30очень приближен к реальной разработке.
- 25:32Ну вот смотрим. Допустим, мы работаем с
- 25:34вами в какой-нибудь компании, которая
- 25:36разрабатывает всякие банковские
- 25:38приложения. Ну и перед нами, как перед
- 25:40разработчиком, стоит задача. Необходимо
- 25:42создать модуль проведения оплат. То есть
- 25:44модуль проведения оплаты, он должен
- 25:46уметь проводить оплату на какую-то
- 25:47определённую сумму, он должен уметь
- 25:49отменять эту самую оплату, ну и он
- 25:51должен уметь отдавать информацию о
- 25:53произошедших операциях. В чём основная
- 25:55загвоздка вообще всего этого модуля? В
- 25:58том, что он должен уметь работать с
- 26:00разными способами проведения оплаты. То
- 26:02есть он должен уметь работать с
- 26:03биткоином, ну, то есть с криптовалютой.
- 26:05Он должен уметь работать с Пайпалом,
- 26:08проводить оплаты и отменять эти оплаты
- 26:09через PayPal. И он должен уметь работать
- 26:11с обычными банковскими картами.
- 26:14Ну как бы мы могли с вами это
- 26:15реализовать? Ну то есть модуль
- 26:18проведения оплаты, это у нас была бы
- 26:19какая-то структура. И у этой структуры
- 26:21были бы вот методы, три метода: провести
- 26:24оплату, отменить оплату и получить
- 26:26информацию о всех произошедших оплатах,
- 26:28ну или операциях там, о всех
- 26:29произошедших оплатах и отменах. Вот
- 26:31хорошо, но как нам быть с тем фактом,
- 26:33что нам нужно уметь работать с тремя
- 26:35разными способами проведения плат? Ну,
- 26:38наверное, самое вот такое глупое, самое
- 26:40первое, что приходит в голову, что мы бы
- 26:42могли сделать - это мы бы могли создать
- 26:44три разных модуля проведения оплат.
- 26:46Первый модуль был бы у нас с криптой,
- 26:48второй модуль бы использовал PayPal,
- 26:50третий модуль использовал бы банковскую
- 26:51карту. Но тут бы мы столкнулись с этой
- 26:54самой неприятной проблемой дублирования
- 26:56кода, что у нас бы было три одинаковых
- 26:58модуля оплаты, просто у них бы отличался
- 27:00вот этот вот компонент, отвечающий за
- 27:02способ проведения оплаты. Ну и,
- 27:04соответственно, все из этого вытекающие
- 27:05проблемы, что если к нам пришла бы
- 27:07какая-нибудь новая фича, например, к нам
- 27:09бы пришёл наш начальник и сказал: "А
- 27:10теперь сделай ещё возможность платить
- 27:12часть заказа бонусными баллами". И нам
- 27:14бы везде пришлось бы добавлять в каждый
- 27:17из трёх модулей оплаты эти бонусные
- 27:19баллы. В первом модуле оплаты писать
- 27:21этот код, во втором модуле оплаты писать
- 27:23этот код, в третьем модуле оплаты писать
- 27:24этот код. Плюс из-за дублирования кода,
- 27:27если у нас где-то возникла бы ошибка,
- 27:29нам бы пришлось эту ошибку исправлять в
- 27:30трёх разных местах сразу. Это
- 27:32максимально неудобно, и мы бы хотели
- 27:34как-нибудь избавиться от этого
- 27:35дублирования кода. И вот бы была
- 27:37возможность описать один модуль
- 27:39проведения оплаты, но чтобы он мог
- 27:41зависеть от разных способов оплаты. И мы
- 27:43при создании этого модуля проведения
- 27:45оплат просто передавали бы туда один из
- 27:47способов проведения оплаты, и он бы
- 27:49вставал на месте вот этого пунктирного
- 27:50квадратика, и мы бы работали с этим
- 27:52способом проведения оплаты. Нам бы
- 27:53хорошо бы как-то абстрагироваться от
- 27:55конкретного способа, но при этом уметь
- 27:57работать с разными способами. Как нам
- 27:59это можно сделать? Ну, вообще, очень
- 28:01хорошо сюда подошёл бы интерфейс. То
- 28:03есть мы бы могли в нашем модуле
- 28:04проведения оплат зависеть не от
- 28:06конкретного способа проведения оплаты, а
- 28:08от интерфейса. Определили бы
- 28:10какой-нибудь интерфейсик, работали бы с
- 28:12этим интерфейсиком, а потом при создании
- 28:14модуля оплат под этот интерфейс уже
- 28:15клали бы какой-то конкретный способ в
- 28:17зависимости от того, который нам нужен.
- 28:19То есть именно сами способы проведения
- 28:21оплат. Предположим, что уже написали
- 28:22наши коллеги. То есть наши коллеги
- 28:24написали нам пакеты, в которых у нас
- 28:26есть файлы, которые мы можем просто
- 28:28использовать для проведения оплаты в
- 28:30крипте, в PayPнковской карты. То есть
- 28:32это уже написали наши коллеги.
- 28:33Предположим, нам лишь нужно разработать
- 28:35сам модуль, который на основе вот этих
- 28:37способов оплаты сможет производить
- 28:39представленные операции и при этом не
- 28:41зависеть от какого-то конкретного
- 28:43способа оплаты, потому что вдруг в
- 28:44будущем у нас ещё добавится какая-нибудь
- 28:46оплата там токенами какими-нибудь
- 28:47игровыми. Нам придётся четвёртый модуль
- 28:50писать. Ну как-то совсем уже грустно
- 28:51становится. Хочется, чтобы мы просто
- 28:53этот новый способ оплаты могли бы
- 28:54подставить в наш интерфейсик и всё. И
- 28:57наш модуль проведения оплат был
- 28:58полностью рабочий. Никакого бы кода
- 29:00дополнительного писать не пришлось. Мы
- 29:02бы просто новый способ оплаты, который
- 29:04наши коллеги написали, подставили бы в
- 29:05этот интерфейс. Ну давайте сейчас начнём
- 29:07писать код. Посмотрим, как бы это всё
- 29:09выглядело в коде более наглядно. Ну,
- 29:11первым делом создаю главный файл и
- 29:13объявляю главный пакет и главную
- 29:15функцию, откуда наша программа начинает
- 29:17своё исполнение. Я понимаю, что у нас
- 29:20программка уже выдастся довольно
- 29:21большая, и я хочу отдельные пакеты
- 29:24завести под модуль проведения оплаты,
- 29:26потому что если это всё будет напихано в
- 29:29мейне, ну, будет совсем некрасиво всё
- 29:31это читать. Несколько сотен строк кода в
- 29:32одном файле, всё это неудобно. Поэтому я
- 29:34хочу завести отдельный пакет или
- 29:36несколько пакетов, посмотрим далее, для
- 29:38модуля проведения оплаты. Ну, чтобы нам
- 29:40работать с пакетами, для начала нужно
- 29:41объявить модуль. Ну, давайте я это и
- 29:43сделаю. Go mode init.
- 29:46Ну, также назову его стадии, чтобы
- 29:47ничего не выдумывать. Всё, у нас
- 29:49появился наш модуль. Теперь можем
- 29:51создавать пакет. Ну, сразу же создам
- 29:53пакет, назову его payments. Тут будет
- 29:55всё, что связано с нашими оплатами. В
- 29:56этом пакете нужно создать файл
- 29:58какой-нибудь гошный, назвать его как
- 30:00угодно, но пакет объявить payments.
- 30:02О'кей. Теперь можем накидать наброски
- 30:05нашего модуля проведения оплат. То есть
- 30:07помним, это какая-то структура, у
- 30:08которой есть три метода. Провести
- 30:10оплату, отменить оплату и отдать
- 30:11информацию о всех прошедших операциях.
- 30:13Ну, давайте объявим нашу структуру.
- 30:16Payment модуль. Это структура. Пока что
- 30:19у неё никаких полей не будем
- 30:20прописывать. Пока что это просто пустая
- 30:22структура. И опишем три её метода,
- 30:24которые мы хотим в ней видеть. Первый
- 30:26метод pay. Просто тоже пока без
- 30:29конкретного описания, просто метод pay,
- 30:31который будет отвечать за проведение
- 30:33оплаты. Второй метод - это отменить
- 30:34оплату.
- 30:37Ccel.
- 30:40И третий метод - это отдать информацию о
- 30:42прошедших операциях. Я бы хотел вместо
- 30:43одного этого метода сделать два. Первый
- 30:45бы метод возвращал информацию о какой-то
- 30:48конкретной операции, а второй метод
- 30:50возвращал бы информацию о всех
- 30:52операциях. Ну, значит, первый метод
- 30:55будет называться inf и возвращать
- 30:57информацию какой-то одной операции.
- 30:59Второй метод будет называться All Info и
- 31:03возвращать информацию уже о всех
- 31:04проведённых операциях. Методы мы просто
- 31:07создали, но мы ещё не написали в них
- 31:09ничего. Давайте отдельно разберём
- 31:11каждый. То есть pay, что он должен
- 31:14принимать. Ну, наверное, хотелось бы
- 31:16принимать какое-то текстовое описание
- 31:18той оплаты, которую мы проводим. Там
- 31:20типа гамбургер или игровая консоль,
- 31:23чтобы понимать, что именно мы покупаем,
- 31:24что именно мы оплачиваем. Поэтому первым
- 31:26делом Pay будет принимать описание
- 31:29проводимой оплаты. Ну, там типа
- 31:32гамбургер, не гамбургер, смотря, что мы
- 31:33покупаем. Второе, что должен принимать
- 31:35метод pay, ну, это как бы сумму, на
- 31:38которую эта оплата производится. сумма
- 31:41оплаты. Ну хорошо, а что он должен
- 31:43возвращать? Возвращать этот метод ID
- 31:47проведённой
- 31:50операции. Что такое ID? Ну вот,
- 31:52допустим, у нас в социальных сетях,
- 31:54таких как Telegram или ВКонтакте, есть
- 31:56ID пользователя. То есть не имя, а
- 31:58какое-то цифровое значение, которое
- 32:00идентифицирует конкретного пользователя.
- 32:02Там, например, пользователь 300.528
- 32:04- это такой-то человек, пользователь с
- 32:06другим айдишником - это другой человек.
- 32:08И вот то же самое мы введём для наших
- 32:10операций. То есть у каждой операции
- 32:11будет какой-то свой айдишник. Мы по
- 32:13этому айдишнику сможем потом получать
- 32:15информацию об этой операции либо
- 32:17отменять эту операцию. Соответственно,
- 32:18метод pay, он у нас принимает описание
- 32:21проводимой оплаты, ну, типа, что именно
- 32:22мы покупаем, сумму оплаты и возвращает
- 32:25айдишник к проведённой операции. О'кей.
- 32:27Теперь метод cancel принимает. Значит,
- 32:32что будет принимать метод? Ну, нам,
- 32:33чтобы понимать, какую именно операцию мы
- 32:35отменяем, нужно тоже ей передать
- 32:37айдишник. Потому что каждый раз, когда
- 32:38мы какую-то операцию будем создавать, то
- 32:41есть что-то оплатили, да, создали
- 32:42операцию оплаты, у этой операции оплаты
- 32:44будет айдишник. И чтобы отменить
- 32:46проведённую операцию оплаты, нужно этот
- 32:47айдишник передать, чтобы методу дать
- 32:49понять, какую именно операцию мы будем
- 32:51отменять. То есть отменяем операцию с ID
- 32:53таким-то, соответственно, принимает ID
- 32:56операции возвращает. Ну, пускай для
- 32:59простоты ничего не возвращает. ничего не
- 33:02возвращает, потому что мы просто
- 33:04отменили нашу операцию по какому-то
- 33:07айдишнику. Ну и всё, отменили и
- 33:08отменили. Теперь метод инфо.
- 33:11Что он будет принимать? Ну, метод
- 33:13инфонимает то же самое, что и метод Cel.
- 33:16То есть нам нужен какой-то конкретный
- 33:17айдишник операции, чтобы понять
- 33:19информацию, о какой именно операции мы
- 33:21должны вернуть. То есть мы должны
- 33:22вернуть информацию об операции с
- 33:24таким-то айдишником. То есть принимает
- 33:25ID операции, возвращает
- 33:29информацию. о проведённой операции.
- 33:33Хорошо. Теперь метод All info
- 33:38принимает. Ничего
- 33:40не принимает. Ну потому что зачем нам
- 33:43какая-то дополнительная информация в
- 33:44аргументах этого метода? Он просто
- 33:46ничего не принимает. Зато будет
- 33:47возвращать информацию о всех проведённых
- 33:49операциях.
- 33:51Ну и возвращает
- 33:52информацию о всех проведённых операциях.
- 33:56Хорошо, вроде бы определились с набором
- 33:59методов, определились с тем, как они
- 34:01должны работать. Давайте дальше думать.
- 34:03Хорошо, вот у нас метод pay, то есть он
- 34:06принимает описание проводимой операции,
- 34:08то есть я назову это description
- 34:12строкового формата. Ну и сумму оплаты.
- 34:14Сумму оплаты будем принимать в USD, то
- 34:17есть в долларах. Если у нас там какая-то
- 34:18криптовалюта, она потом конвертирует это
- 34:20в USDT. Ну типа криптодоллар. Если у нас
- 34:23там банковская операция, но она
- 34:24воспримет это как доллары и всё. То есть
- 34:26USD integer, как бы целое число наших
- 34:30долларов, типа сколько долларов мы
- 34:31оплачиваем, и возвращает ID операции. ID
- 34:34- это у нас какой-то инджер. То есть наш
- 34:37метод первым делом будет проводить
- 34:39оплату,
- 34:41получать ID проведённой
- 34:45оплаты,
- 34:47сохранять информацию о проведённой
- 34:50операции.
- 34:53И четвёртым возвращать ID проведённой
- 34:58оплаты.
- 35:00Хорошо, тут встаёт сразу же вопрос, то
- 35:02есть хорошо, проводим оплату. Мы помним,
- 35:04что проводить оплату мы будем с помощью
- 35:07нашего способа оплаты. Абстрагироваться
- 35:09от конкретного способа оплаты мы будем
- 35:11через интерфейс. Давайте сразу же
- 35:13подумаем про то, как этот наш интерфейс
- 35:15может выглядеть. То есть нам по сути
- 35:16нужно, чтобы способ оплаты, написанный
- 35:18нашими коллегами, чтобы через него мы
- 35:20могли платить и чтобы через него мы
- 35:22могли отменять наши операции. А уже
- 35:23информацию о том, что было заплочено,
- 35:25что было отменено, мы уже как-нибудь в
- 35:27нашем Payмент-модуле сохраним. Хорошо,
- 35:29нам нужно, чтобы интерфейс способа
- 35:31оплаты умел платить и отменять оплату.
- 35:34Ну хорошо, давайте этот самый интерфейс
- 35:35объявим. Назову его payment method.
- 35:38Это у нас интерфейс. Значит, что в этом
- 35:41интерфейсе должно быть? Что мы бы хотели
- 35:43видеть в этом payйment методе? То есть,
- 35:45что мы хотим от наших способов оплаты?
- 35:47Ну, во-первых, мы хотим с помощью них
- 35:48уметь платить. То есть мы будем
- 35:50передавать туда количество уплачиваемой
- 35:51валюты и получать замен айдишник
- 35:53операции. То есть пока что условимся,
- 35:55что каждый айдишник каждой операции он у
- 35:58нас будет во всей системе уникален. То
- 36:00есть, если у нас операции по крипте
- 36:01вернуло айдишник 10, то все остальные
- 36:04способы оплаты это будут учитывать и не
- 36:06будут нам возвращать десятку. Поэтому
- 36:07можем пока считать, что у нас айдишники
- 36:09во всей системе, айдишники наших
- 36:11операций, они уникальны. Ну вот мы с
- 36:12помощью payймент метода будем платить
- 36:14ему сколько-то долларов и получать
- 36:16айдишник операции. И с помощью этого
- 36:18payймент-метода мы сможем отменять нашу
- 36:20операцию по айдишнику. Собственно, это
- 36:22всё, что нам нужно от нашего способа
- 36:24оплаты. Ну хорошо. Теперь в нашем
- 36:27Payment-модуле. Вот он наш вот этот вот
- 36:29большой кружочек. Это наш
- 36:30Payment-модуль. В нём мы хотим зависеть
- 36:32от этой абстракции над способом оплаты,
- 36:34чтобы потом просто туда передавать
- 36:36какой-то конкретный способ, и он под
- 36:38капотом использовался. А на самом деле
- 36:39мы даже можем и не знать, что конкретно
- 36:41там за способ оплаты, потому что у нас
- 36:43интерфейс. Ну хорошо, мы хотим зависеть
- 36:45от этого payment метода. Я его так и
- 36:46назову. Payment method, типа payment
- 36:49метод, то есть название поля и тип этого
- 36:51поля. Почему с маленькой буквы? Потому
- 36:53что я хочу инкапсулировать вот это вот
- 36:55поле, чтобы один раз в конструкторе
- 36:57человек, который будет пользоваться
- 36:58payймен модулем, задал payймен метод и
- 37:01всё, чтобы на литу он не смог сюда
- 37:02подложить какую-нибудь свою кастомную
- 37:04структуру, чтобы у нас оплата там
- 37:06производилась на чей-то чужой
- 37:07какой-нибудь там пиратский
- 37:09криптокошелёк. Вот для этого я создам
- 37:10конструктор New Payment модуль. И
- 37:13принимать этот конструктор будет Payment
- 37:15method
- 37:17и возвращать наш paymentмодуль.
- 37:20Здесь мы будем сразу же создавать
- 37:22Payment модуль и указывать ему полем
- 37:24переданный payment метод. То есть то,
- 37:26что нам будут передавать в конструкторе,
- 37:28оно сохранится в поле Payment модуля.
- 37:30Тут у нас тонкий вопрос встаёт:
- 37:33возвращать в конструкторе копию Payment
- 37:36модуля или всё-таки указатель на него?
- 37:39Если указатель, нам тут придётся
- 37:41ставить, что мы как бы сразу же создаём
- 37:43Paymentмодуль и возвращаем указатель на
- 37:45него. Вот что лучше? Это вопрос очень
- 37:48дискуссионный. Он очень сильно зависит
- 37:50от того, какие у нас поля в структуре.
- 37:52Но я могу вам посоветовать так. Если у
- 37:55вас какая-то сложная структура,
- 37:57например, какой-нибудь репозиторий для
- 37:59похода в базу данных или модуль для
- 38:01проведения разных оплат, то в
- 38:02большинстве случаев лучше возвращать его
- 38:04по указателю, потому что мы один раз
- 38:06создадим в нашем конструкторе этот
- 38:09модуль, вернём указатель на него и
- 38:11везде, где в нашей программе будет
- 38:12использоваться этот модуль, мы будем
- 38:14работать с конкретной областью в памяти,
- 38:16с конкретной одной переменной. Особенно,
- 38:18когда у нас начинается конкурентное
- 38:20взаимодействие с нашим модулем. Ещё
- 38:22более важно становится держать именно
- 38:25указатель, а не копию. Мы пока ещё до
- 38:27этого не дошли. Мы ещё не дошли до
- 38:29конкурентной работы и синхронизации этой
- 38:31конкурентной работы. Пока что для нас
- 38:32это просто пустой звон. Но вот хочу
- 38:34обратить ваше внимание, что в данном
- 38:36случае довольно дискуссионный момент.
- 38:38Как возвращать лучше: копией или по
- 38:40указателю? Я буду возвращать по
- 38:42указателю просто потому, что хочу быть
- 38:44уверенным, что я в конструкторе один раз
- 38:46создал payйmentмодуль и потом через
- 38:48указатель буду всегда работать именно с
- 38:50этим Payment-модулем, а не с его копией,
- 38:52потому что вдруг у меня рано или поздно
- 38:54появится какое-нибудь поле, которое я не
- 38:56могу просто так скопировать безопасно,
- 38:58например, вспоминаем те же самые
- 38:59подключения к базе данных. И тогда то,
- 39:01что я в конструкторе возвращаю копию,
- 39:03может для меня стать проблемой. Поэтому
- 39:06пока я буду возвращать по указателю.
- 39:08Хотя повторюсь, который раз. Тут можно
- 39:10подумать над тем, чтобы возвращать и
- 39:12копию. Это очень сильно зависит от того,
- 39:14что именно в нашей структуре содержится,
- 39:16и каждый раз требует детального
- 39:18рассмотрения. Вот у нас есть
- 39:19конструктор, который принимает paymentд,
- 39:21создаёт paymentмодуль и возвращает на
- 39:23него указатель. Хорошо, потом мы через
- 39:24этот указатель спокойно сможем с
- 39:26созданным модулем работать. Теперь
- 39:27переходим к нашим методам. Вот у нас
- 39:29есть метод payй нашего payment-модуля.
- 39:31Что тут использовать в качестве
- 39:33ресивера? Копию или же указатель. Это
- 39:35тоже дискуссионный момент, потому что в
- 39:37данный момент у нас поле единственное,
- 39:40которое есть - это наш способ оплаты, и
- 39:41мы будем его вызывать. Потом мы будем
- 39:44как-то куда-то сохранять информацию о
- 39:45проведённой операции. Ну, тут сразу же
- 39:47хорошо бы подумать, куда мы эту самую
- 39:49информацию будем сохранять. Ну, давайте
- 39:51вернёмся чуть позже к этому моменту.
- 39:53Значит, первое, что мы должны делать в
- 39:55методе Pay - это проводить оплату.
- 39:57Хорошо, мы берём, используем payment
- 39:59метод, который нам в конструкторе
- 40:01передали. А payment метод - это что-то,
- 40:02что удовлетворяет вот этому интерфейсу.
- 40:04То есть нам пока что неважно, что это
- 40:06будет, крипта, PayPal там или ещё
- 40:07какой-нибудь четвёртый метод оплаты.
- 40:09Неважно, если он удовлетворяет вот этому
- 40:11интерфейсу payment method, у него есть
- 40:13метод Pay Councel, тогда мы можем с ним
- 40:15работать. Вызываем этот payment метод и
- 40:18у него pay. Передаём туда размер оплаты
- 40:21и будем получать айдишник этой оплаты.
- 40:24Хорошо, провели оплату, получили ID
- 40:26проведённой оплаты. Отлично. Потом нам
- 40:28нужно сохранить информацию о проведённой
- 40:29операции. Что вообще нам бы хотелось
- 40:32сохранить? Ну, во-первых, я бы хотел
- 40:34сохранить описание операции. То есть,
- 40:36да, что именно мы купили. Я бы хотел
- 40:38запоминать: бургер, не бургер. Потом я
- 40:40бы хотел запомнить, сколько было
- 40:43потрачено. То есть вот это вот, сколько
- 40:45мы заплатили. Я бы хотел это тоже
- 40:46запоминать. Ну и ещё я бы хотел знать, у
- 40:49нас операция отменённая, да, или ещё
- 40:50нет. То есть я вот когда инфу буду
- 40:52получать, я хочу знать, эту операцию
- 40:53отменяли или не отменяли. Отменённая ли
- 40:56операция? Вот примерно вот эти три
- 40:59значения я бы хотел знать, когда я
- 41:01получаю информацию о какой-то операции.
- 41:02Ну вот как, условно говоря, мы с вами в
- 41:04чек смотрим, что мы вообще купили. Я бы
- 41:06хотел понимать, что я купил, сколько я
- 41:08на это потратил и отменил ли я эту
- 41:10операцию или не отменил, вернул деньги
- 41:12или не вернул там, да, ну, если была
- 41:13потребность. То есть мы, когда будем
- 41:15сохранять вот эту информацию о
- 41:17проведённой операции, мы хотим сохранять
- 41:18вот эти три поля. То есть, чтобы нам
- 41:20разом сохранять все вот эти вот три
- 41:22значения, их можно объединить в какую-то
- 41:24одну структуру. Ну и пускай она
- 41:25называется как-нибудь payment info, типа
- 41:27информация об оплате. Как раз-таки в ней
- 41:29мы можем сохранять все эти значения. Я
- 41:31создам в пакете Payments ещё один файл,
- 41:33назову его info. И в нём я создам
- 41:37структуру Payment Info.
- 41:42Для чего я делаю это в отдельном файле?
- 41:44Да, просто, чтобы не загрязнять наш файл
- 41:46payments. Потому что в нём мы объявляем
- 41:50именно Paymentмодуль, интерфейс для
- 41:52работы с Payment-модулем, конструкторы,
- 41:54в общем, всё, что относится к
- 41:55Payment-модулю. Описание структуры
- 41:57Payment info, просто хочу вынести в
- 41:59другой файл, чтобы тут это не засорять.
- 42:00И так много кода здесь будет. Ну,
- 42:02payment info, мы смотрим, первое, что мы
- 42:04хотим - это описание операции, то есть
- 42:06description.
- 42:08Description строкового типа. Далее,
- 42:11сколько было потрачено? Ну, то есть USD,
- 42:13и была ли эта операция отменена?
- 42:17Canceled, ну, булевского типа. То есть,
- 42:19если canceled у нас false, значит, нет,
- 42:21не отменена, значит, ну, просто обычная
- 42:23операция была произведена. Если canceled
- 42:25true, значит, да, она была отменена. Мы
- 42:27там вернули деньги. Вот это наша
- 42:29информация об оплате. То есть я бы хотел
- 42:31вот эту структуру. Ну, повторюсь, так
- 42:32как у нас эта структура объявлена в том
- 42:34же пакете Payments, что и наш
- 42:35Paymentмодуль, то мы тут можем спокойно
- 42:37её использовать. Ну, давайте создадим
- 42:39эту структуру. То есть info, это у нас
- 42:41payment info. И тут сразу же можно
- 42:43передать description.
- 42:45Сохраняем description, который нам в
- 42:47метод payable был передан. Потом USD
- 42:49сохраняем, то есть количество оплаты,
- 42:51которое нам тоже было передано. И cancel
- 42:53ставим false. Почему? Потому что мы
- 42:54только произвели оплату и у нас
- 42:56создалась новая информация о новой
- 42:58операции. Мы ещё не успели бы её
- 43:01отменить. Поэтому изначально cancel -
- 43:02это у нас флse. Хорошо, мы эту
- 43:04информацию создали, но как нам её
- 43:06сохранять? То есть давайте подумаем, как
- 43:08бы мы могли сохранять эту операцию и
- 43:10множество других. Ну, понятно, это нам
- 43:11нужно делать в поле нашей структуры. И
- 43:14какие у нас есть варианты?
- 43:16Как сохранять информацию об оплатах?
- 43:20Значит, что же мы тут могли бы
- 43:22использовать для того, чтобы сохранять
- 43:24нашу информацию об оплатах? Ну,
- 43:25во-первых, нужно понимать, что у нас эта
- 43:27информация об оплате не в
- 43:28одномединственном экземпляре будет, а
- 43:30то, что информации об оплате будет
- 43:32много. Почему? Потому что самих оплат
- 43:33много. То есть у нас не так, что один
- 43:35юзер там когда-то там пришёл, сделал
- 43:37однуединственную оплату и всё. Тогда бы
- 43:40мы, в принципе, могли сохранять эту инфу
- 43:42в отдельную какую-то переменну в нашем
- 43:44Payment-модуле. Но так как у нас
- 43:45пользователей может быть много, так как
- 43:47один пользователь может производить
- 43:49множество оплат, поэтому мы не можем все
- 43:51эти большие массивы оплат, все эти
- 43:53большие массивы информации о разных
- 43:55оплатах хранить в одной переменной, как
- 43:57бы, да, потому что мы тут только одну
- 43:58оплату можем сохранить. Нам это не
- 44:00подходит. То есть нам нужна какая-то
- 44:02коллекция, нам нужна какая-то сущность,
- 44:04которая бы нам позволила сохранять сразу
- 44:05несколько этих информации об оплате в
- 44:08одном каком-то поле. Ну что это может
- 44:10быть? Первое, что, наверное, приходит на
- 44:11ум - это статический массив. О'кей? То
- 44:14есть, да, статический массив нам
- 44:15действительно позволил бы сохранить
- 44:17сразу несколько вот этих информации об
- 44:18оплате в нашем Payment-модуле. Но в чём
- 44:21проблема статического массива? То, что
- 44:22он, собственно, статический. То есть мы
- 44:24задаём ему какой-то строго заданный
- 44:25размер, там 5, 10, 100.000, например, и
- 44:29мы не можем его потом расширить. Либо
- 44:31нам придётся какие-то очень страшные
- 44:32костыли для этого придумывать. В общем,
- 44:33это очень удобно. Ну, допустим, вот
- 44:35сделали там этот статический массив,
- 44:37задали ему размерность там 50.000,
- 44:39например, да, и то есть мы сможем
- 44:40сохранить информацию о 50.000 оплат.
- 44:43Сегодня у нас 10 оплат произвёл наш
- 44:44модуль оплаты, завтра 1.000 оплат,
- 44:47послезавтра там 50.000. И 50.000 первую
- 44:50оплату мы уже не сможем положить в этот
- 44:52статический массив, потому что он
- 44:53статический и всё. И мы не сможем туда
- 44:55докинуть новых элементов. Получается,
- 44:56статический массив ограничивает наш
- 44:58модуль оплаты возможность охранить
- 45:00большое количество вот этой вот
- 45:02информации об оплате. И мы заранее, ну,
- 45:04не сможем предугадать, сколько у нас
- 45:05будет оплат. Это как позвонить в
- 45:06Сбербанк и спросить: "А вы не знаете,
- 45:08сколько у вас на следующей неделе будет
- 45:09произведено операций?" Да откуда они
- 45:11знают? Никто не знает. Сегодня
- 45:13пользователь захотел там зайти на Озон,
- 45:14купить себе что-нибудь там, обувь новую,
- 45:16завтра не захотел. Поэтому это мы не
- 45:18можем предугадать. Заранее размер
- 45:20статического массива мы предугадать,
- 45:22поэтому, соответственно, тоже не можем.
- 45:23Поэтому статический массив тут нам не
- 45:24подходит. О'кей. Второе, что бы мы могли
- 45:27тут использовать, ну, это слайс. Слайс
- 45:28уже лишён главного недостатка
- 45:30статического массива, а именно
- 45:31невозможность динамически увеличить
- 45:33количество хранимых элементов.
- 45:35Получается, слайс, в принципе, нам бы
- 45:36тут мог подойти. То есть мы бы как поле
- 45:38структуры Payment Modду хранили бы
- 45:40слайс. При оплатах мы бы в этот слайс
- 45:42докидывали новую информацию об оплате.
- 45:44При поиске информации какой-то
- 45:46конкретной оплате мы бы также могли
- 45:47пробегать этот слайс в каком-нибудь
- 45:49цикле, искать нужную нам информацию об
- 45:51оплате и возвращать эту информацию об
- 45:53оплате. Ну, в принципе, звучит довольно
- 45:55неплохо. Но давайте подумаем, что ещё у
- 45:57нас есть. Ещё у нас есть мапа.
- 46:00И мапа нам тоже бы подошла. Мы бы могли
- 46:02по айдишнику оплаты сохранять информацию
- 46:05об этой оплате. Потом при помощи этого
- 46:07самого айдишника мы могли бы получать
- 46:09уже сохранённую информацию об оплате,
- 46:11потому что мапа - это, я напоминаю, KWю
- 46:13хранилище, и мы по ключу ID могли бы
- 46:16получать значение. О'кей? То есть нам
- 46:19здесь в теории могли бы подойти как
- 46:21слайсы, так и мапы. И мы можем написать
- 46:23реализацию и с тем, и с другим. Так что
- 46:25же в итоге выбрать? Для того, чтобы нам
- 46:28понять, что в итоге нужно в данной
- 46:29ситуации выбирать: слайс или мапу, я
- 46:31предлагаю разобрать на тестовом примере,
- 46:33как себя поведёт слайс, как себя поведёт
- 46:35мапа. Ну, как основу для нашего
- 46:36тестового примерчика, я возьму вот этот
- 46:38пустой одинокий файл. И вот мы тут будем
- 46:41рассматривать, что же выбрать в нашей
- 46:42ситуации: слайс или мапу. Хорошо, какие
- 46:45у нас вообще в Payment модуле есть
- 46:46операции? Pay, то есть провести оплату.
- 46:49Cancel- отменить операцию. info,
- 46:51получить информацию о проведённой
- 46:52операции и all in получить информацию о
- 46:55всех проведённых операциях. Ну о'кей. То
- 46:57есть мы должны рассмотреть каждый
- 46:58отдельный сценарий, когда мы с вами в
- 47:00теории можем как-то прямо или косвенно
- 47:02взаимодействовать с Paymentin info,
- 47:04чтобы потом понять, каким образом вообще
- 47:06эта структура у нас сохраняется, каким
- 47:08образом она у нас достаётся из памяти. И
- 47:10в связи с этим мы сможем сделать
- 47:12оптимальный выбор всё-таки слайс или
- 47:14мапу. Я понимаю, сейчас может быть
- 47:15довольно непонятно, но надеюсь в скором
- 47:18времени для вас всё станет более
- 47:20прозрачно. Что же мы тут всё-таки
- 47:21думаем? Слайс ли мапы и почему это
- 47:23вообще сейчас важно? О'кей, давайте
- 47:25смотреть. Мы когда проводим оплату, мы с
- 47:28вами проводим через payment метод, да,
- 47:29то есть через какой-то сторонний метод
- 47:31эту нашу самую оплату. Напоминаю, вот
- 47:32наш модуль проведения оплат и вот наш
- 47:35payйment метод, то есть именно
- 47:36конкретный метод проведения оплат. Там
- 47:37может быть биткои, PayPal, да, ну и так
- 47:39далее. То есть мы через этот payймент
- 47:41метод проводим оплату, получаем
- 47:42айдишник, проведённый операции. и
- 47:44собираем структурку информацию о
- 47:46проведённой оплате. После чего нам нужно
- 47:49сохранить эту информацию. Хорошо? То
- 47:50есть мы, получается, при каждой оплате
- 47:52мы будем что-то сохранять. Получается,
- 47:54нам нужно уметь сохранять большое
- 47:58количество payment info.
- 48:01Хорошо. Это когда мы будем оплачивать,
- 48:03да? Мы будем после этого сохранять вот
- 48:05эту вот нашу payment info. О'кей. Когда
- 48:07мы с вами будем отменять какую-то
- 48:09определённую оплату, что нам нужно
- 48:11сделать? Для начала нам нужно опять же
- 48:13через наш payment метод вызвать. То есть
- 48:15повторюсь, мы благодаря вот этому
- 48:17payment-методу, который нам написали
- 48:18наши коллеги, мы будем у него вызывать
- 48:20метод Cel, отменять оплату и потом уже
- 48:23просто на уровне нашего Payment-модуля
- 48:25мы должны будем запомнить, отменили ли
- 48:27мы эту операцию или нет. Для того, чтобы
- 48:29нам после фактической отмены операции
- 48:31через способ проведения оплаты запомнить
- 48:34то, что мы эту операцию отменили, нам
- 48:37нужно найти payment info среди нашего
- 48:40слайса либо мапы и поменять вот это вот
- 48:43значение cancel. И тогда мы узнаем в
- 48:45итоге-то, что мы с вами отменили или
- 48:47наоборот не отменили. И потом мы сможем
- 48:49эту информацию получать, выводить на
- 48:51экран и смотреть там cancel false или
- 48:53true. Получается, нам нужно будет найти
- 48:56во всём нашем множестве, будь то слайса
- 48:58или мапа, нужную payment inнфо и
- 49:00переключить в ней вот этот вот флажок с
- 49:02false на true. Получается, нам нужно
- 49:05уметь находить конкретный элемент
- 49:10payment info в множестве. Хорошо, когда
- 49:14мы с вами будем получать информацию об
- 49:16оплате, по сути, то же самое. Нам нужно
- 49:18во всём множестве наших элементов, во
- 49:21всём множестве наших Paymentino info
- 49:22находить нужный. То есть нам, например,
- 49:24сюда будет передаваться какой-нибудь
- 49:25айдишник целочисленный. И мы потом по
- 49:28этому айдишнику должны вот во всём нашем
- 49:30множестве payment info найти именно ту
- 49:32payйment info, которая относится к
- 49:33оплате с переданным айдишником и вернуть
- 49:36эту payment info. То есть опять же
- 49:37находить конкретный элемент payment inf
- 49:40в множестве. Хорошо. Ну и all info. Тут,
- 49:42в принципе, как бы просто всё множество
- 49:43вернуть. Это тут каких-то дополнительных
- 49:45операций вроде не требует. Хорошо. То
- 49:47есть пока на первый взгляд, что имеем?
- 49:49То, что нам с нашим множеством, будь то
- 49:52слайс или мапа, необходимо будет
- 49:54глобально производить очень часто две
- 49:56операции: сохранение нового Payment Info
- 49:58и нахождение конкретного Payment Info во
- 50:00всём множестве. Ну давайте приступим. Ну
- 50:02для начала я опишу ещё раз эту нашу
- 50:04Paymentin info. У неё есть description
- 50:06строкового типа, у неё есть количество
- 50:08долларов, на которое была продена
- 50:09оплата. И у неё ещё есть информация о
- 50:12том, отменена она или нет, булевского
- 50:14типа. Хорошо. Теперь давайте опишем две
- 50:17структурки, два тестовых Payment модуля.
- 50:19В первом мы будем использовать слайз для
- 50:21того, чтобы хранить различные информации
- 50:23об оплате, а во втором мы будем
- 50:24использовать мапу. Ну, поехали. То есть
- 50:26первое - это у нас payment модуль with
- 50:29slice. То есть paymentмодуль основанный
- 50:31на слайсе. Ну, собственно, он будет у
- 50:33нас хранить слай.
- 50:35Таким вот образом. Дальше у нас есть
- 50:37payment модуль без map. В принципе, то
- 50:40же самое. Функциональность будет та же
- 50:41самая, только мы уже будем основываться
- 50:43не на слайсе, а на мапе. Назову её М.
- 50:46Мапа у нас будет хранить ключом это
- 50:48айдишник, проведённый операции, а
- 50:50значением этой информации об этой самой
- 50:52операции. Вот. Но тут сразу же встаёт
- 50:54некоторый вопрос. То есть мы в слайсе,
- 50:56если храним нашу payment info, мы
- 50:58сохраняем информацией об оплате, но мы в
- 51:02случае со слайсом нигде не сохраняем
- 51:04айдишник операции. И как нам потом в
- 51:07этом всём слайсе понять, какая операция
- 51:09имела какой айтишник? То есть тут уже мы
- 51:11начинаем встречаться с первым отличием,
- 51:13то, что когда мы храним при помощи мапы,
- 51:15то ключом уже будет айдишник, поэтому
- 51:18нам как-то отдельно не придётся его
- 51:19нигде сохранять. И мы просто сможем в
- 51:21эту мапу передать айдишник операции и
- 51:23сразу же получить нужную pймен инфу. А в
- 51:25случае со слайсом нам придётся в каждую
- 51:28payment инфу дополнительно сохранять ID
- 51:30этой операции, чтобы потом она у нас
- 51:32никуда не потерялась, и мы могли найти
- 51:34нужный элемент. То есть уже первое такое
- 51:36отличие, которое может нам как-то уже
- 51:37намекать, что нам лучше выбрать всё-таки
- 51:39слайс или мапу. Но пока это ничего
- 51:41критичного нету. Здесь мы, в принципе,
- 51:42да, можем вот так вот сохранить айдишник
- 51:44и потом в слайсе его, если что, потом
- 51:46находить. Хорошо. Теперь давайте опишем
- 51:49наши операции сохранения Paymentino inf
- 51:52для слайса. То есть у нас будет метод,
- 51:53он будет для payment модуль слайс. Да,
- 51:56давайте я для более большой наглядности
- 51:57разделю вот таким вот комментарием наши
- 51:59блоки кода, которые отвечают за слайс и
- 52:01которые отвечают за мапу. Просто для
- 52:03того, чтобы визуально было более
- 52:04понятно. Хорошо. Payment modul with
- 52:06slides. И тут я назову add info, то есть
- 52:09добавить информацию. У нас это как бы
- 52:11тестовые такие модули оплаты, поэтому
- 52:13тут нету методов pay, там info, да. Тут
- 52:16именно мы рассматриваем операции,
- 52:17которые нам необходимо будет производить
- 52:19с нашим payment infфо. Это сохранение
- 52:21большого количества payment info и
- 52:23нахождение какого-то конкретного Payment
- 52:24info. Во всём множестве. Получается, вот
- 52:27info - это сохранение payment info. Ну
- 52:29давайте я вот уберу большое количество,
- 52:30чтобы не путало. просто сохранять
- 52:32payment инфу. Хорошо? То есть мы
- 52:33принимаем какую-то инфу и должны её
- 52:35уметь сохранять. А тут сразу же мы
- 52:37сталкиваемся с тем, что нам необходимо
- 52:39выбрать. Какой у нас будет здесь ресивер
- 52:41по значению или по указателю? Что будет,
- 52:44если у нас тут будет ресивер по
- 52:45значению? То есть я из нашего
- 52:47Paymentмоду слайса беру слайс и хочу в
- 52:50него добавить новую инфу таким вот
- 52:52образом через функцию append. Но что
- 52:54произойдёт? Я добавлю эту инфу в слайс,
- 52:58который содержится в копии
- 53:00Payment-модуля, который нам пришёл в
- 53:01ресивере. А оригинальный Payment-модуль
- 53:04при этом никак не изменится. Поэтому,
- 53:06чтобы я всё-таки мог при помощи метода
- 53:08Add ino менять оригинальный
- 53:10paymentмодуль, я здесь ресивер делаю по
- 53:12указателю. Хорошо? То есть вот таким вот
- 53:14образом мы умеем добавлять нашу
- 53:15информацию. Ну и теперь второе, что нам
- 53:17необходимо делать - это находить
- 53:19конкретный элемент во всём множестве.
- 53:20Хорошо, как бы мы с вами могли находить
- 53:23конкретный элемент во всём множестве?
- 53:24назову метод get info.
- 53:28И находить конкретную информацию мы
- 53:29будем по айдишнику, да? Потому что в
- 53:31случае отмены или в случае попытки
- 53:33поиска информации об оплате мы будем
- 53:35искать именно по ID-операции. Изначально
- 53:38во время оплаты мы получили этот
- 53:39айдишник операции, а потом мы по этому
- 53:41айдишнику сможем получать конкретную
- 53:43информацию об оплате, которую мы
- 53:45сохранили ранее, либо отменять какую-то
- 53:47оплату. Хорошо? То есть тут мы будем
- 53:48получать какой-то айдишник
- 53:50целочисленный. И как же нам в слайсе
- 53:52найти конкретное payment info, которое
- 53:54бы содержало переданный айдишник? Да,
- 53:56всё на самом деле довольно просто. Ну,
- 53:58во-первых, мы должны обусловиться, что
- 53:59наш метод будет возвращать найденное
- 54:01payment info. Далее мы будем при помощи
- 54:03цикла бежать по всем сохранённым в нашем
- 54:05слайсе payment info и сравнивать у
- 54:08каждого payment info его айдишник и
- 54:11смотреть, что если у нас info ID равно
- 54:15тому ID, что передали, это значит, что
- 54:17очередное payment info - это именно то,
- 54:19что нам нужно. и мы будем его
- 54:21возвращать. Вот он наш info. Если же мы
- 54:23дошли до тридцатой строчки и до сих пор
- 54:25ещё не вернули никакое payment infфо,
- 54:27это значит, что во всём нашем слайсе не
- 54:29было payment с передаваемым айдишником.
- 54:31Ну получается, мы можем просто вернуть
- 54:32вот пустую структурку, да, пока вот мы
- 54:34не умеем как-то обрабатывать такие
- 54:36исключительные ситуации, поэтому мы вот
- 54:38просто будем в таком случае пустую
- 54:39структуру возвращать. Хорошо, теперь мы
- 54:41с вами написали payment model with,
- 54:43который умеет сохранять новую информацию
- 54:45и искать какую-то информацию, которая
- 54:46уже была в него добавлена. О'кей. Теперь
- 54:48давайте то же самое сделаем с мапой. У
- 54:51нас будет
- 54:51[музыка]
- 54:52payment модуль без mapap. Он точно также
- 54:55будет уметь добавлять информацию.
- 54:58Каким образом происходит добавление
- 55:00элементов каких-то в мапу? Да всё
- 55:01просто. Мы берём нашу мапу, указываем ей
- 55:03ключ. Напоминаю, ключом у нас будет ID,
- 55:05а значение будет сама вот эта payment
- 55:07info. Ну, повторюсь, что если бы мы
- 55:09остановились уже жёстко на реализации с
- 55:11мапой, нам бы не пришлось в Payment Info
- 55:13хранить айдишник, потому что он бы нам,
- 55:15ну, в принципе, как бы был не нужен. Мы
- 55:16этот айдишник сохраняем в ключе. Но так
- 55:19как у нас одновременно примеры со
- 55:20слайсом, и с мапой, то нам необходим
- 55:22этот айдишник, потому что в слайсе мы
- 55:24просто потеряли бы ID оплаты, если бы мы
- 55:26не сохранили это в конкретном элементе
- 55:28payment info. О'кей. Добавлять
- 55:30информацию мы теперь умеем. И хочется
- 55:32ещё научиться как бы искать эту
- 55:34информацию get info. По переному
- 55:36айдишнику будем возвращать payment info.
- 55:38Ну и как это делается в случае с мапой.
- 55:40То есть у нас есть какое-то значение inf
- 55:43и есть флажок окей, который бы нам
- 55:45позволял понять, найден ли элемент по
- 55:48ключу в мапе или нет. То есть я передаю
- 55:50в мапу айдишник и буду искать, что если
- 55:52у меня не ок, то есть если не найден
- 55:54элемент, я буду возвращать пустой
- 55:56payment infфоo. То же самое, как мы
- 55:58делали с payment-модулем, когда он у нас
- 56:00был основан на слайсе. То есть, если мы
- 56:02ничего не нашли, мы возвращаем payment
- 56:04info. Просто вот такую пустую структуру.
- 56:06Ну а если у нас ок, тогда будем просто
- 56:08возвращать info. Хорошо. Мы с вами
- 56:10написали две реализации. Одна у нас на
- 56:13слайсе, вторая реализация у нас на мапе.
- 56:16Теперь давайте посмотрим, как они
- 56:17работают. Я создам два payment модуля. Я
- 56:19первый назову PLI, то есть это payment
- 56:22модуль with SLI.
- 56:24И второй у меня будет P mapap, то есть
- 56:26payment модуль withmap.
- 56:29О'кей, давайте начнём сравнивать, как
- 56:30они работают. Я могу вот здесь вот
- 56:32создать какую-то информацию. Info1,
- 56:34например, она у меня будет называться.
- 56:35Это у меня будет payment info. И в неё я
- 56:37положу ID. И, к примеру, у меня будет
- 56:3910пtion у меня будет бургер, количество
- 56:43долларов пять. Ну и false, конечно же,
- 56:46потому что у нас изначально ещё вот
- 56:47неотменённая как бы вот эта операция.
- 56:49Ну, в принципе, в тестовом примере можно
- 56:50было бы и CLEL true поставить. Это
- 56:52никакого значения сейчас не имеет.
- 56:53Хорошо, давайте попробуем сохранить эту
- 56:55нашу информацию об оплате в два разных
- 56:58наших payment модуля. Попробуем
- 56:59сохранить в первый, который основан на
- 57:01слайсе. Add info. И вот это наша info 1.
- 57:04Потом Pmap. Сюда тоже хочу add info и
- 57:07тут уже ту же самую информацию хочу
- 57:10сохранить. После чего просто выведу их
- 57:11на экран при помощи библиотеки pп.
- 57:13Напомню, чтобы у нас использовать
- 57:14какие-то сторонние библиотеки, нам нужно
- 57:16быть модулем. Но мы уже являемся
- 57:18модулем, потому что мы объявили все
- 57:19модули для того, чтобы работать с
- 57:21пакетами. Поэтому я просто могу начать
- 57:23писать pп. Вот у меня эта библиотека.
- 57:26И потом в консоли просто напишу modди
- 57:28для того, чтобы вот эта импортированная
- 57:29библиотека автоматически добавилась мне
- 57:31в зависимости. Сохраняю файл, пишу
- 57:33тайди.
- 57:34Всё, автоматически всё подсосалось,
- 57:36подтянулось. Хорошо. Выведу PLI
- 57:41и то же самое сделаю для Pmap.
- 57:45[аплодисменты]
- 57:47Pmap.
- 57:49Давайте запустим нашу программу. Что же
- 57:51будет у нас?
- 57:53А будет у нас паника. В какой? В
- 57:55шестьдесят шестой строчке. Почему?
- 57:56Потому что мы попытались что-то записать
- 57:59в мапу. При этом эту мапу мы ранее никак
- 58:02не инициализировали. А кто смотрел
- 58:04внимательно предыдущий урок, тот помнит,
- 58:06что мапу нам необходимо всегда
- 58:08инициализировать. Для этого здесь, когда
- 58:10я буду создавать Payment Mod with Map, я
- 58:12буду нашу мапу, собственно,
- 58:13инициализировать следующим образом. Всё,
- 58:15в принципе, эту логику можно было бы
- 58:17вынести в конструктор, каждый отдельный
- 58:19Payment-модуль вынести в отдельный
- 58:21пакет, но у нас тестовый пример, поэтому
- 58:23сейчас вот так вот просто всё на коленке
- 58:24у нас происходит. Ещё раз запускаем и
- 58:26видим, что PLI у нас сохранил нашу
- 58:29payment infу с бургером и Pmмапа тоже
- 58:31сохранила Paymentino inf с бургером. То
- 58:32есть, ну, в принципе, как бы оно
- 58:34работает и там, и там. Теперь давайте
- 58:36попробуем найти информацию. Plies, get
- 58:39info. И вот по айдишнику 10, например,
- 58:41вот I1 назову и I2 назову. Это мы уже
- 58:44будем искать в мапе. Get inнфо тоже
- 58:46подишнику 10. И теперь просто выведу на
- 58:48экран. I1,
- 58:53I2,
- 58:55I2. Сохраняю, запускаю. I1 10 Burger 5
- 59:00FSEs I2 10 Burger 5 FSEs. Окей. То есть
- 59:02мы умеем и при помощи payment модуля,
- 59:04который основан на слайсе, и при помощи
- 59:06payment модуля, который основан на мапе,
- 59:08добавлять новые элементы и находить уже
- 59:11существующие. То есть это не проблема.
- 59:13Это можно делать и как при помощи
- 59:15слайса, так и при помощи мапы. И как
- 59:17будто бы сейчас может показаться, что
- 59:19какая вообще нафиг разница, как хранить
- 59:21в слайсе или в мапе. Но давайте
- 59:24чуть-чуть усложним наш пример и добавим
- 59:26немножечко больше элементов в него,
- 59:28нежели сейчас у нас просто одна вот эта
- 59:30payment info. Для этого я сделаю
- 59:32переменную iterations,
- 59:34которая у нас будет хранить количество
- 59:36итераций нашего цикла. Сейчас поймёте,
- 59:38что за итерации. Изначально будет просто
- 59:39100. И пишу обычный цикл I от нуля I
- 59:43меньше iterations, I++. На каждой
- 59:46итерации я буду создавать новую
- 59:47структуру payment info. В айдишник её
- 59:50класть переменную i. В desрипtion я буду
- 59:52класть тоже переменную i, только
- 59:54переведённую в строку. Есть в гошке
- 59:56такой пакет стандартный str conv, и в
- 59:59нём есть функция integer to ask. Вот и
- 1:00:02она переводит переданный integer в
- 1:00:05строку. То есть если я, например, туда
- 1:00:06передам единицу, мне вернётся вот такая
- 1:00:08вот строка. Если я туда передам,
- 1:00:10например, 123, мне вернётся строка 123.
- 1:00:13Ну, просто для того, чтобы я мог вот эту
- 1:00:15циферку переводить в строку. Сюда я
- 1:00:17передам i. Соответственно, у нас на
- 1:00:19каждой новой итерации будет создаваться
- 1:00:20новая вот это payment info и будет
- 1:00:22айдишник 01 2 3 4 5 и description тоже
- 1:00:25будет 01 2 3 4 5. Просто это будет уже
- 1:00:27являться строкой. Сейчас это не имеет
- 1:00:29значения, что desрипtion у нас
- 1:00:31бессмысленный. Повторюсь, у нас сейчас
- 1:00:32тестовый пример. Хорошо, мы будем
- 1:00:34создавать эту payment info, и я буду
- 1:00:36сохранять эту самую payment инфу в наш
- 1:00:38payment слайд. Давайте я вот здесь вот
- 1:00:40это скопирую, буду создавать наши
- 1:00:42payment модули чуть-чуть заранее. Ну и,
- 1:00:45соответственно, буду брать, да, и
- 1:00:46сохранять PLI.
- 1:00:48info, добавлять вот эту информацию и
- 1:00:51замерять время, сколько займёт времени
- 1:00:53вот это вот добавление информации в наш
- 1:00:56paymentмодуль, который основан на
- 1:00:58слайсе. Мы дальше с этим тоже будем
- 1:00:59сталкиваться, но для того, чтобы в Гошке
- 1:01:02замерить время выполнения какого-то
- 1:01:03куска кода, нам нужно изначально
- 1:01:05запомнить время, которое было до этого
- 1:01:08куска кода, да, вот, например, здесь
- 1:01:10вот, а потом посмотреть, сколько времени
- 1:01:13прошло вот здесь вот. Тем самым мы из
- 1:01:16вот этого времени, когда вычтем это, мы
- 1:01:18узнаем, сколько времени у нас выполнялся
- 1:01:20этот кусок кода. Давайте здесь запомним
- 1:01:22наше время. Я назову его before. То есть
- 1:01:24before - это с английского до. Это будет
- 1:01:26у нас время до начала выполнения вот
- 1:01:28этого куска кода. Before у нас будет
- 1:01:32time now. Пакет time, в нём метод now.
- 1:01:35Now нам возвращает текущее время. То
- 1:01:38есть запомнили время, которое у нас было
- 1:01:39до выполнения вот этого куска кода. А
- 1:01:41теперь вот здесь вот я буду выводить на
- 1:01:44экран время, которое было пройдено с
- 1:01:48того момента, когда мы запомнили since.
- 1:01:51Что тут произошло? Я изначально запомнил
- 1:01:53время, которое было до выполнения вот
- 1:01:55этого куска кода. А здесь я выведу,
- 1:01:57сколько времени прошло с того момента,
- 1:01:59как я запомнил. Тем самым я узнаю,
- 1:02:01сколько вот этот кусок кода выполнялся
- 1:02:03по времени, и напишу его. Обозначу
- 1:02:05немножечко. Это у меня будет слайс,
- 1:02:09то есть добавление новой информации в
- 1:02:11слайс. Что я тут именно замеряю? Я тут
- 1:02:13замеряю за сколько времени я добавлю 100
- 1:02:17информации по оплате в наш payment
- 1:02:19модуль, который основан на слайсе. То же
- 1:02:21самое я хочу написать, но уже для мапы.
- 1:02:24Опять сделаю. Тут уже у меня не
- 1:02:27двоеточие равно,
- 1:02:29а просто равно, потому что before у нас
- 1:02:31переменная уже созданная, и мы тут
- 1:02:33просто будем перезаписывать в неё новое
- 1:02:34значение. То же самое я буду делать в
- 1:02:37цикле. По сути, вообще тот же самый у
- 1:02:38меня код будет, только здесь я буду уже
- 1:02:41не в слайс добавлять, а P mapap, то есть
- 1:02:43payment модуль, который основан на мапе,
- 1:02:44и добавлять нашу информацию. И тут то же
- 1:02:47самое. Я буду у меня выводить map. Вот
- 1:02:49это я пока всё сотру. Ну и давайте пока
- 1:02:52запустим и посмотрим, что вообще будет
- 1:02:54происходить, да. Сколько времени будет
- 1:02:55занимать добавление сотни элементов в
- 1:02:57слайс и добавление сотни элементов в
- 1:02:59мапу. Запускаем.
- 1:03:03Ну смотрим. 100 элементов слайс мы с
- 1:03:05вами добавили за 15 микросекунд и 100
- 1:03:08элементов мапа мы с вами добавили за 5
- 1:03:10микросекунд. Микросекунда одна - это 1
- 1:03:13миллион секунды. То есть, чтобы вы
- 1:03:15понимали, это настолько мало, что в
- 1:03:17данном случае это цифры одного порядка.
- 1:03:20Это даже можно списать на погрешность.
- 1:03:21То есть, ну, как бы что 15 микросекунд,
- 1:03:24что 5 микросекунд. Разницы никакой нету.
- 1:03:26То есть по скорости тут всё плюс-минус
- 1:03:27одинаковое. Хорошо. А теперь давайте то
- 1:03:29же самое мы будем делать. также замерять
- 1:03:31наше время, только уже для поиска
- 1:03:33информации. В принципе, я напишу тот же
- 1:03:36самый код, он будет выглядеть точно так
- 1:03:38же. Давайте вот здесь вот я опять же
- 1:03:41сделаю визуальное разделение
- 1:03:42комментариями, которое просто нам будет
- 1:03:43немножко помогать ориентироваться в
- 1:03:46приставленном коде на глаз.
- 1:03:48[аплодисменты]
- 1:03:54Здесь мы с вами добавляли в слайсы в
- 1:03:56мапу, а здесь мы будем искать по слайсу
- 1:03:58и по мапе. Ну, хорошо, то есть то же
- 1:04:00самое, только в этот раз я уже не буду
- 1:04:02создавать никакую информацию об оплате,
- 1:04:04я буду её получать. То есть из payment
- 1:04:06модуль основанном на слайсе, я беру
- 1:04:08информацию и передаю в неё I. То есть до
- 1:04:10этого я от нуля до 100 сохранил в этот
- 1:04:13слайс, а теперь я буду получать. Вот оно
- 1:04:15наше info.
- 1:04:17Ну и, в принципе, я не хочу её выводить
- 1:04:19на экран, потому что вывод на экран -
- 1:04:20это дополнительная какая-то задача,
- 1:04:22которая может нагрузить нашу программу,
- 1:04:23и мы получим нечистые результаты.
- 1:04:25Поэтому я просто вот так вот обнулю, так
- 1:04:27скажем, эту переменную. Просто сделаю
- 1:04:29так, чтобы компилятор, чтобы язык
- 1:04:30программирования не ругался на то, что я
- 1:04:32тут создал переменную инфо в цикле и
- 1:04:34нигде её не использовал. Вот как бы
- 1:04:35такую заглушку делаю, что типа я её как
- 1:04:37бы где-то использовал, да, по факту нет.
- 1:04:39Это у меня будет слайс get. То есть, да,
- 1:04:41за сколько времени мы с вами нашли 100
- 1:04:43элементов из слайса. Ну и то же самое
- 1:04:45для мапы, в принципе. Вот тут вот
- 1:04:46возьму, скопирую, вставлю вот это вот,
- 1:04:48удалю.
- 1:04:50И тут я буду не из слайса, а из мапы
- 1:04:52получать наш элементик. И тут уже у меня
- 1:04:55map.
- 1:04:56Хорошо, давайте сейчас посмотрим, что
- 1:04:58будет. Запускаем нашу программу. Всё
- 1:05:00также отработало очень быстро. Опять же,
- 1:05:02какие-то микросекунды. Вообще это просто
- 1:05:05очень-очень быстро, очень мало. И как
- 1:05:08будто бы опять же может показаться, что,
- 1:05:10ну, вообще вот оно всё очень похоже
- 1:05:12работает. Какая разница там слайс или
- 1:05:13мапа вообще? И то, и то как бы отлично
- 1:05:15работает. И на том, и на том мы в
- 1:05:17принципе можем написать функциональность
- 1:05:18как бы добавления и поиска каких-то
- 1:05:20элементов. Хорошо. А теперь давайте
- 1:05:22попробуем не 100 элементов сохранять и
- 1:05:24находить, а 1.000 элементов. То есть
- 1:05:27теперь мы будем 1.000 раз сохранять
- 1:05:29что-то в слайс, 1.000 раз сохранять
- 1:05:32новый элемент в мапу. Потом мы будем
- 1:05:341.000 раз находить что-то в слайсе и
- 1:05:361.000 раз находить что-то в мапе. И
- 1:05:38теперь посмотрим на наши результаты.
- 1:05:40Запустим программу. Видим, всё ещё всё
- 1:05:43происходит очень быстро. Как бы в слайд
- 1:05:45добавляется, в мапу добавляется всё как
- 1:05:48бы примерно одного порядка цифры. Но
- 1:05:50если мы обратим внимание на поиск
- 1:05:52элемента, то поиск наших 1.000 элементов
- 1:05:55в слайсе занял 440 мкросекунд, а поиск
- 1:05:581.000 элементов в мапе занял 20
- 1:06:00микросекунд. То есть это в 21 раз меньше
- 1:06:04времени. В 21 раз. Это уже на самом деле
- 1:06:06довольно серьёзные порядки. В 21 раз
- 1:06:08мапа оказалась быстрее, чем слайс в
- 1:06:11поиске 1.000 элементов. Когда нам нужно
- 1:06:13было найти 1.000 элементов в слайсе, мы
- 1:06:16это сделали за 440 микросекунд. Да. Вот
- 1:06:19здесь вот вот эта наша логика. Вот эта
- 1:06:21логика выполнялась 440 мкроссекунд, а
- 1:06:23вот эта логика выполнялась 20
- 1:06:25микросекунд. То есть поиск элемента в
- 1:06:27мапе оказывается быстрее, что ли. Ну, на
- 1:06:30самом деле, может и быстрее, но у нас
- 1:06:31как бы всё равно, да, до сих пор очень
- 1:06:32быстро отрабатывает наша программка. Не
- 1:06:34зависает ничего, как бы, да, какая
- 1:06:35разница, сколько там этих микросекунд?
- 1:06:37Это всё равно очень быстро. Ну хорошо,
- 1:06:39давайте посмотрим, что будет, если у нас
- 1:06:41будет уже не 1.000 информации об оплате,
- 1:06:43а 10.000 информации об оплате. Запускаем
- 1:06:45нашу программу. И что мы начинаем
- 1:06:48видеть? Если бегло посмотреть, можно
- 1:06:50увидеть, что добавление в слайс было, в
- 1:06:52принципе, в три раза быстрее. Ну там в 2
- 1:06:54с2 раза быстрее. А вот поиск элементо в
- 1:06:56слайсе оказался настолько медленнее, что
- 1:07:00у нас в мапе микросекунды, а в слайсе
- 1:07:02уже миллисекунды. Давайте спросим у
- 1:07:04неронки вообще, насколько здесь слайс
- 1:07:06оказался медленней. Вот я просто взял,
- 1:07:08поделил 32 мисекунды на 109 микросекунд.
- 1:07:12И мне нейронка сказала, что это
- 1:07:15практически в 300 различаются эти
- 1:07:18значения. То есть с одними и теми же
- 1:07:21операциями Слайс справился в 300 раз
- 1:07:24хуже. В 300 раз хуже Слайс искал
- 1:07:28элементы. То есть поиск 10.000 элементов
- 1:07:31в мапе было в 300 раз лучше. Ну уже
- 1:07:34какие-то звоночки начинаются, да?
- 1:07:35Давайте попробуем тут поставить не
- 1:07:3710.000, а 100.000 и посмотрим. И мы
- 1:07:40видим, у нас программка теперь даже
- 1:07:41подвисла. И мы, в принципе, видим, что
- 1:07:43добавление, что в слайд, что в мапу,
- 1:07:44примерно одинаково, да? И тут как бы там
- 1:07:465 мсекунд примерно, тут 6,5 мсекунд. Ну,
- 1:07:49как бы разница там, ну, такие небольшие
- 1:07:50порядки. А вот поиск элементов в слайсе
- 1:07:53в мапе, давайте посмотрим, насколько
- 1:07:55отличается для 100.000 элементов. Теперь
- 1:07:57уже в 2.230
- 1:07:59раз. В 2.230 раз отличается. То есть
- 1:08:03слайс тут уже справился в 2.230
- 1:08:06раз хуже, чем мапа. И тут в наших тестах
- 1:08:10у нас всегда сначала идёт слайс, потом
- 1:08:11мапа. Давайте я сделаю наоборот. Сначала
- 1:08:13я сделаю мапу, потом слайс, потому что
- 1:08:14мы тогда иначе всегда будем ждать этот
- 1:08:16самый слайс, пока он там доработает. Я
- 1:08:19просто возьму и вот так вот перенесу наш
- 1:08:20код, который отвечает за мапу вперёд. А
- 1:08:22слайс мы будем измерять после мапы.
- 1:08:28[аплодисменты]
- 1:08:30Ну всё, теперь у нас сначала мы в мапу
- 1:08:32добавляем какое-то количество элементов.
- 1:08:34Сейчас миллион. Сначала мы миллион
- 1:08:35элементов добавляем в мапу, потом
- 1:08:37миллион элементов добавляем в слайс,
- 1:08:38потом мы ищем миллион элементов в мапе и
- 1:08:40ищем миллион элементов в слайсе. Давайте
- 1:08:42запустим нашу программу. Опа, мапа уже
- 1:08:45давно отработала. Тем временем Слайс.
- 1:08:48Слайс, ты где, Слайс? Что-то как-то нету
- 1:08:51нашего Слайса. И я вам могу вот по
- 1:08:52секрету сказать, мы, наверное, его даже
- 1:08:54не дождёмся. Просто нужно будет ждать
- 1:08:56несколько минут, пока миллион элементов
- 1:08:58будут найдены в слайсе. Короче, вы
- 1:09:00видите, да? Ну, давайте тут не миллион
- 1:09:02элементов, вот 200.000, например. То
- 1:09:03есть вот 200.000. Запускаем. Мапа
- 1:09:06отработала уже, как обычно. Ждём слайс.
- 1:09:08То есть я даже вырезать сейчас не буду
- 1:09:09ничего, чтобы вот мы могли с вами вот
- 1:09:11подождать слайс. Видите, всё ещё ждём
- 1:09:14слайс. Это это 200.000 элементов.
- 1:09:16Наконец-то слайс отработал. Вот он 11
- 1:09:17секунд выполнялся. Давайте у неронки
- 1:09:19спросим, во сколько раз хуже в этот раз
- 1:09:20справился наш слайс. В этот раз слайс
- 1:09:23справился хуже, ну, практически в 4.000
- 1:09:25раз. То есть и в отличие от предыдущих
- 1:09:27примеров, теперь действительно это даже
- 1:09:30на наше ощущение влияет, насколько
- 1:09:32дольше слайс работает. То есть одно и то
- 1:09:34же количество однотипных операций, поиск
- 1:09:36элементов в коллекции для слайса и для
- 1:09:39мапы имеет просто колоссальную разницу.
- 1:09:41Мапа за 3 мисекунды отработала. 3
- 1:09:44мисекунды - это никакой человек это, в
- 1:09:46принципе, ощутить никогда не сможет. Вот
- 1:09:48это вот 3 мисекунды и слазь просто 11
- 1:09:51секунд. Секунд. Ни наносекунд, ни
- 1:09:53микросекунд, ни миллисекунд, просто
- 1:09:55секунд. А теперь представьте, что у нас
- 1:09:57каждый пользователь будет много раз там
- 1:10:00в неделю производить оплату. У нас будет
- 1:10:02множество пользователей, которые просто
- 1:10:04туда-сюда гоняют эти деньги, а у нас всё
- 1:10:06завязано на слайсе. Это просто наше
- 1:10:09приложение встанет как бы, ну, одним
- 1:10:11местом пойдёт, как говорится, по одному
- 1:10:13месту, потому что у нас может быть
- 1:10:14далеко за 1 млн операций. У нас может
- 1:10:17быть 100 млн операций, да, если у нас
- 1:10:19какая-нибудь крупная там банковская
- 1:10:21система. У нас может быть, не знаю,
- 1:10:22миллиард операций. А теперь представьте,
- 1:10:23что вот мы на 200.000х
- 1:10:25со слайсом вот просто на 11 секунд
- 1:10:27встали. А если у нас тут будет там, ну,
- 1:10:2910 млн, например, да, мы не дождёмся
- 1:10:31просто. Наши пользователи вот так вот
- 1:10:32будут смотреть в приложение, там будет
- 1:10:34вот этот вот кружочек крутиться,
- 1:10:36подождите, и они никогда не дождутся.
- 1:10:37Почему? Казалось бы, у нас со слайсом и
- 1:10:39с мапой всё работало. Когда у нас было
- 1:10:41там 10 операций, да, дали даже просто
- 1:10:431.000, всё работало отлично. Вот 1.000
- 1:10:45операций, вот всё как бы отлично, быстро
- 1:10:47отрабатывает. Почему же, когда у нас
- 1:10:49вдруг становится здесь 100.000,
- 1:10:50например, то мапа всё так же круто
- 1:10:52отрабатывает, а слайс почему-то, ну,
- 1:10:55гораздо медленнее начинает работать.
- 1:10:56Давайте узнаем. Всё дело в том коде,
- 1:10:58который необходимо написать для того,
- 1:11:00чтобы найти элемент в слайсе и в мапе.
- 1:11:04Как мы ищем элемент в слайсе? Мы идём
- 1:11:06циклом по всем элементам, которые
- 1:11:09содержатся в слайсе. И для каждого
- 1:11:12элемента мы проводим сравнение. И когда
- 1:11:14мы находим нужный айдишник, мы
- 1:11:16возвращаем. И почему это хорошо
- 1:11:18работает, когда у нас всего лишь там 10
- 1:11:20элементов в слайсе? Да потому что нам
- 1:11:23нужно провести цикл всего лишь для
- 1:11:24десяти элементов. Первый элемент не
- 1:11:26нашли. Второй элемент не нашли, третий
- 1:11:28элемент не нашли, там четвёртый, пятый,
- 1:11:30десятый элемент. Ага, нашли. Возвращаем
- 1:11:33его. А 10 итераций для языка
- 1:11:35программирования, ну, это, в принципе,
- 1:11:36довольно быстро, как бы это, да, большое
- 1:11:38проблемы не составит. А когда у нас,
- 1:11:40например, уже 100 payйment inf в слайсе
- 1:11:42лежит, когда у нас 100 элементов в
- 1:11:43слайсе, получается, нам нужно делать то
- 1:11:45же самое, только уже не 10 раз, а 100
- 1:11:47раз. Первый раз, второй раз, третий и
- 1:11:50так 100. То есть у нас такая ситуация,
- 1:11:52что от количества элементов, которые
- 1:11:54лежат в слайсе, у нас зависит время
- 1:11:57поиска какого-то конкретного элемента.
- 1:11:59Если у нас три элемента в слайсе, мы 1 2
- 1:12:023 нашли нужный элемент. Если у нас 10
- 1:12:04элементов в слайсе, мы там 1 2 3 4 5 6 7
- 1:12:068 9 10 нашли нужный элемент. А если у
- 1:12:08нас миллион элементов в слайсе, это
- 1:12:10получается нам нужно пробежать по
- 1:12:12миллиону элементов, каждый сравнить, и
- 1:12:15мы, когда найдём из этих миллион
- 1:12:17элементов нужный, мы его вернём. Поэтому
- 1:12:20слайс, именно операция поиска
- 1:12:22конкретного элемента в слайсе, она имеет
- 1:12:25алгоритмическую сложность
- 1:12:27О от N. Что это значит? Это значит, что
- 1:12:30вот сложность поиска какого-то элемента
- 1:12:32в слайсе, вот она обозначена буковкой О,
- 1:12:34алгоритмическая сложность, она зависит
- 1:12:37прямо от количества элементов в этом
- 1:12:38самом слайсе. То есть, да, допустим, у
- 1:12:40нас вот вот тут я подпишу, что это
- 1:12:42слайс. Допустим, у нас в слайсе
- 1:12:44обработка одного элемента занимает 10
- 1:12:46там микросекунд. О'кей. Получается,
- 1:12:49обработка 10ти элементов в слайсе будет
- 1:12:51занимать уже 100 микросекунд, да? Потому
- 1:12:53что для того, чтобы нам обработать один
- 1:12:54элемент, нам нужно 10 микросекунд. У нас
- 1:12:57в слайсе 10 элементов. И мы 10 миссекунд
- 1:12:59потратили, ещё 10 мскунд потратили, ещё
- 1:13:0110 мискунд потратили, ещё 10 мскунд
- 1:13:02потратили. В итоге, чем больше у нас
- 1:13:04элементов в этом самом слайсе, тем
- 1:13:06больше нам придётся производить итераций
- 1:13:08для того, чтобы найти какой-то
- 1:13:10конкретный элемент, который мы ищем. И
- 1:13:12если у нас тут, например, будет уже 100
- 1:13:13элементов, то это получается у нас уже
- 1:13:15будет 1.000 микросекунд, да? А если у
- 1:13:18нас тут будет, например, 1 млн
- 1:13:20элементов, 1 млн элементов - это уже у
- 1:13:22нас получается 10 млн этих самых
- 1:13:24микросекунд, а это уже у нас 10 секунд,
- 1:13:27да? То есть вы видите, насколько сильно
- 1:13:28у нас в слайсе зависимость от количества
- 1:13:31хранимых элементов. Но, повторюсь, это
- 1:13:34именно касается операции поиска. К
- 1:13:36примеру, доступ к каким-то конкретным
- 1:13:38элементам в слайсе у нас никак не
- 1:13:40замедляется независимо от того, сколько
- 1:13:42бы у нас там не было элементов. То есть
- 1:13:44хочу, например, получить слайс там с
- 1:13:45индексом ноль. Это у меня будет занимать
- 1:13:46там, например, 10 микросекунд, да? Или я
- 1:13:48хочу получить слайс там с индексом 10
- 1:13:50млн. Ну, это всё равно у меня будет
- 1:13:52занимать 10 микросекунд. Обращение к
- 1:13:54элементу по индексу, оно никак не
- 1:13:56зависит от того, сколько у нас там
- 1:13:57элементов в слайсе. А вот поиск элемента
- 1:13:59в слайсе, да, когда мы именно с
- 1:14:01конкретным условием хотим найти элемент,
- 1:14:03вот у которого именно айдишник, тот, что
- 1:14:05передали, вот поиск элементов в слайсе,
- 1:14:07он уже сильно зависит от того количества
- 1:14:09элементов, которые хранятся уже в этом
- 1:14:11слайсе. Потому что нам, чтобы найти
- 1:14:13конкретный элемент, нужно обойти все. Ну
- 1:14:15вот просто вот мы все элементы обошли,
- 1:14:17нашли конкретный. Поэтому такая
- 1:14:19удручающая картина. Поэтому наш
- 1:14:21paymentмодуль, если мы забазируем его на
- 1:14:24слайсе, он будет хорошо работать в
- 1:14:26первое время, пока у нас мало оплат. 10
- 1:14:29оплат, там 30 оплат, 50 оплат, и мы
- 1:14:31будем смотреть: "Ох, ничего себе, как
- 1:14:33всё круто работает". Вообще наш payment
- 1:14:35модуль отлично забазировался на слайсе,
- 1:14:36вообще всё чётко. Но потом будет идти
- 1:14:38время, операции будет становиться всё
- 1:14:40больше, наш слайс будет разрастаться, и
- 1:14:43внезапно мы увидим, что нифига наше
- 1:14:45приложение не работает, потому что
- 1:14:48количество элементов в слайсе выросло
- 1:14:49там до миллиона. И нам уже каждую
- 1:14:51отдельную операцию оплаты, чтобы
- 1:14:53провести, нужно не 10 микросекунд, а 10
- 1:14:55секунд, а потом минута, а потом 2
- 1:14:58минуты. А потом мы увидим, что все
- 1:14:59пользователи ушли из нашей системы,
- 1:15:01потому что что-то долго всё лагает.
- 1:15:02О'кей. А как обстоят дела с мапой? Как
- 1:15:04обстоят дела с мапой? У мапы
- 1:15:06алгоритмическая сложность О от одного.
- 1:15:09Что это значит? Один - это как
- 1:15:11константа. Что значит константа? Это
- 1:15:13значит, что независимо от того, сколько
- 1:15:16у нас элементов в мапе, поиск элемента
- 1:15:19по ключу будет иметь постоянную
- 1:15:22длительность, постоянное время. Как это
- 1:15:24выражается? Допустим, у нас один элемент
- 1:15:26в мапе, мы хотим его найти. Это будет
- 1:15:27занимать 10 микросекунд. Хочу сразу же
- 1:15:29оговориться, вот эти цифры, что тут
- 1:15:30называют, 10 микросекунд и так далее,
- 1:15:32это просто из головы. Сейчас для
- 1:15:33примера. Реальный порядок цифр очень
- 1:15:35сильно зависит от того, как вы напишете
- 1:15:37программу, от того, какой у вас вообще
- 1:15:38процессор, какая у вас операционная
- 1:15:40система, какая у вас фоновая загрузка
- 1:15:42вашего компьютера. Поэтому это очень всё
- 1:15:44примерные цифры. Ну давайте продолжим.
- 1:15:46Вот у нас, то есть мапа. Допустим, не
- 1:15:48один элемент. Поиск этого элемента будет
- 1:15:49занимать 10 микросекунд. Допустим, в
- 1:15:51мапе 10 элементов, и поиск одного
- 1:15:53элемента будет занимать 10 микросекунд.
- 1:15:56А если у нас тут, например, 100
- 1:15:57элементов, то поиск одного элемента
- 1:15:59будет занимать 10 микросекунд. А если у
- 1:16:01нас тут, например, 1 млн элементов, то
- 1:16:04поиск элемента в мапе, в которой 1 млн
- 1:16:07элементов, будет занимать 10
- 1:16:09микросекунд. Чувствуете разницу, что
- 1:16:12слайс у нас и мапа, они на маленьких
- 1:16:15значениях, в принципе, то, что мы видели
- 1:16:17в тестах, работают похоже, но на самом
- 1:16:19деле слайс даже побыстрее. Вот на
- 1:16:21маленьких значениях слайс побыстрее
- 1:16:22обычно, но потом, когда у нас вырастает
- 1:16:25количество хранимых значений, то
- 1:16:27внезапно оказывается, что слайс не то,
- 1:16:29что медленнее, чем мапа работает, он, в
- 1:16:31принципе, непригоден для использования,
- 1:16:33потому что он просто грузит наше
- 1:16:34приложение и оно перестаёт работать.
- 1:16:36Получается, мы можем сделать вывод о
- 1:16:38том, что в нашем Payment-модуле
- 1:16:40однозначно нужно выбрать именно мапу и
- 1:16:43ничего другое, потому что статический
- 1:16:45массив нам, ну, как бы не подойдёт, мы
- 1:16:47это уже поняли, и слайс нам тоже не
- 1:16:48подойдёт, потому что если наш
- 1:16:50payment-модуль будет сохранять всё
- 1:16:51больше и больше количество информации об
- 1:16:54оплате, то рано или поздно он просто
- 1:16:55перестанет работать, да? То есть, ну
- 1:16:57там, к примеру, представьте, вы своему
- 1:16:58другу там в Сбербанке переводите деньги
- 1:17:01и вместо того, чтобы нажать на кнопочку
- 1:17:02деньги ему пришли, вы просто стоите,
- 1:17:04короче, три часа ждёте, пока придут
- 1:17:05деньги. Ну, как бы, наверное, не
- 1:17:07захочется таким банком пользоваться,
- 1:17:08поэтому мы не можем просто себе
- 1:17:10позволить здесь взять слайс. Нам нужно
- 1:17:12брать мапу. Ну, я надеюсь, с этим
- 1:17:14определились, то, что у нас есть разница
- 1:17:16во времени между слайсом и между мапой.
- 1:17:18И выражается она в основном в момент
- 1:17:22поиска какого-то конкретного элемента.
- 1:17:24То есть, когда мы добавляли что-то и
- 1:17:25слайсы в мапы, мы видели, что, ну, слайс
- 1:17:26он как бы даже побыстрее, да, обычно вот
- 1:17:28он тут вообще в три раза быстрее слайс,
- 1:17:30тут в два раза быстрее слайс. То есть,
- 1:17:31когда мы добавляем новый элементы, слайс
- 1:17:32у нас, ну, чуть-чуть, ну, там побыстрее
- 1:17:34вон там, может, в полтора, там, в два, в
- 1:17:35три раза побыстрее. Но когда мы ищем
- 1:17:38какие-то элементы, то слайс он в 1.000
- 1:17:41раз медленнее, чем мапа. Значит ли это,
- 1:17:44что слайс - это бесполезная коллекция,
- 1:17:46которая нам никогда нигде не пригодится
- 1:17:48и нам всегда нужно использовать мапу?
- 1:17:50Нет, это не значит. Нам очень хорошо
- 1:17:52подойдёт слайс, когда нам нужно
- 1:17:54сохранить чёткий порядок элементов. К
- 1:17:57примеру, мы с вами пользуемся ДНСом, да,
- 1:17:59и хотим купить себе ноутбук там, ну или
- 1:18:01Mвидео там Ильдорадо. И у нас вот есть
- 1:18:03там ноутбуки. Первый там с ценником
- 1:18:0410.000 там руб. Второй с ценником 50.000
- 1:18:08руб. Третий с ценником 12.000 руб. Там
- 1:18:10четвёртый с ценником 6.000 руб. Там
- 1:18:12пятый с ценником 100.000 руб. И нам
- 1:18:15нужно все эти ноутбуки показать
- 1:18:17пользователю. Мало того, пользователь
- 1:18:18ещё нажал там кнопочку, знаете, типа
- 1:18:20сортировать по цене, там типа сначала
- 1:18:22дешёвые. И нам нужно в конкретном
- 1:18:24порядке показать пользователю все эти
- 1:18:26ноутбуки. Поэтому нам нужно сохранить
- 1:18:28порядок. Сначала взять вот этот вот, вот
- 1:18:30эту коллекцию элементов, потом
- 1:18:32отсортировать её по цене и потом в
- 1:18:35отсортированном виде показать
- 1:18:36пользователю, что сначала 6.000 ноутбук,
- 1:18:38который стоит, потом 10.000 ноутбук,
- 1:18:40который стоит, потом 12.000 ноутбук,
- 1:18:42который стоит, потом 50.000 ноутбук,
- 1:18:44который стоит, потом 100.000 ноутбук,
- 1:18:46который стоит с мапй мы бы этого не
- 1:18:48смогли сделать, потому что я сейчас не
- 1:18:50буду разбирать конкретные причины этого.
- 1:18:52Кому интересно, посмотрите на Ютубе
- 1:18:53устройство хэштаблиц и как вообще
- 1:18:55устроены операции во время работы с
- 1:18:57хэштаблицами, когда мы добавляем что-то
- 1:18:59в хэш-таблицу, когда мы в ней что-то
- 1:19:00ищем, когда мы что-то удаляем из
- 1:19:02хэш-таблицы, как вообще хранятся
- 1:19:03элементы в хэштаблицы. Но кто не хочет
- 1:19:05это смотреть, просто на слово мне
- 1:19:07поверьте, что в мапе элементы не могут
- 1:19:10храниться в каком-то конкретном порядке.
- 1:19:11Когда мы храним что-то в слайсе или в
- 1:19:13массиве, это всё в памяти расположено
- 1:19:15последовательно, друг за дружкой. Когда
- 1:19:17мы что-то храним в мапе, оно разбросано
- 1:19:19может быть вообще рандомом по разным
- 1:19:21ячекам в памяти. Поэтому, когда мы хотим
- 1:19:23сохранить порядок элементов, мапа нам не
- 1:19:25подойдёт. Опять же, вот когда нам нужен
- 1:19:27порядок, когда мы в ДНСе каком-нибуд или
- 1:19:29Эльдорадом видео хотим взять там наши
- 1:19:31ноутбуки и отсортировать их по
- 1:19:33возрастанию цены. Вот в мапе мы ничего
- 1:19:36отсортировать не сможем, потому что, в
- 1:19:37принципе, такое понятие, как порядок
- 1:19:39элементов, он мапе не знаком. Мапа умеет
- 1:19:42получать ключ и отдавать значение. Вот
- 1:19:44здесь вот мы с вами в мапу передали ключ
- 1:19:46ID, получили значение info. Это мапа
- 1:19:49умеет делать, а сохранять порядок
- 1:19:50элементов мапа не умеет. И тем более мы
- 1:19:52не можем никак мапу отсортировать.
- 1:19:54Поэтому, несмотря на то, что в нашем
- 1:19:55конкретном кейсе мапа оказалась
- 1:19:57многократно лучше слайса, это не значит,
- 1:20:00что слайс - это что-то бесполезное,
- 1:20:01ненужное. Нет, слайсы очень часто
- 1:20:03используются. Это очень удобный
- 1:20:04инструмент, но в нужной ситуации. В
- 1:20:07данной ситуации мы с вами рассмотрели,
- 1:20:09что нам мапа будет гораздо лучше. Вы
- 1:20:11меня спросите, что это вообще сейчас
- 1:20:12было за такое большое логическое
- 1:20:14отступление про слайсы и мапы, если у
- 1:20:16нас тема интерфейсы. Но я решил, что
- 1:20:18если я тут просто молча воткну мапу, то
- 1:20:20многие зададутся вопросом: "Почему не
- 1:20:22слайс? Почему там не статический
- 1:20:23массив?" Сейчас мы уже с вами начинаем
- 1:20:25писать какой-то код, который уже
- 1:20:26чуть-чуть сложнее, чем Hello World.
- 1:20:28Поэтому хорошо бы понимать, почему те
- 1:20:30или иные решения были приняты конкретно
- 1:20:32в данном случае, почему я выбрал именно
- 1:20:34мапу, а не слайс. Я надеюсь, что с
- 1:20:35помощью этого примера я вам смог
- 1:20:36показать вообще для чего в этом
- 1:20:38конкретном случае я выбрал мапу, а не
- 1:20:40слайс. В каких случаях лучше всё-таки
- 1:20:42выбрать слайс и почему у нас настолько
- 1:20:44сильно отличается время выполнения
- 1:20:47операции поиска элементов в слайсе и в
- 1:20:49мапе. Советую это вам запомнить. Это
- 1:20:51вам, как в написании ваших пэпроектов,
- 1:20:53так и на реальной работе очень сильно
- 1:20:55поможет. просто знание того, в каких
- 1:20:57ситуациях лучше использовать слайс, в
- 1:20:59каких лучше мапу и почему мапа иногда
- 1:21:02может быть многократно быстрее слайса.
- 1:21:04Наконец-таки мы с вами можем вернуться к
- 1:21:06нашему модулю оплаты. И мы уже здесь
- 1:21:08определились с тем, что именно мы будем
- 1:21:10использовать для того, чтобы сохранять
- 1:21:12нашу payment infфо. Это именно мапа.
- 1:21:14Мапу я назову payment info, то есть
- 1:21:17информацией об оплате. Это будет мапа,
- 1:21:18ключом которой будет у нас integer, то
- 1:21:20есть целое число, то есть айдишник нашей
- 1:21:23какой-то отдельной операции. И значение
- 1:21:25будет payment info. То есть мы по ключу,
- 1:21:27по айдишнику оплаты сможем получать
- 1:21:29информацию об этой самой оплате. Хорошо,
- 1:21:33давайте начнём это делать. То есть мы в
- 1:21:34нашем методе Pay, нашего Payment модуля,
- 1:21:37первое, что делаем, повторюсь - это
- 1:21:39проводим операцию оплаты при помощи
- 1:21:41метода оплаты. Что это значит? Это
- 1:21:43значит, у нас вот здесь вот лежит
- 1:21:44какой-то метод оплаты, например, PayPal,
- 1:21:46и мы при помощи него, при помощи
- 1:21:48какого-то стороннего кода, который
- 1:21:50написали наши коллеги, проводим,
- 1:21:51собственно, эту операцию. Фактически там
- 1:21:53списываем деньги у клиента со счёта, а
- 1:21:56потом на уровне нашего модуля проведения
- 1:21:57оплат просто запоминаем информацию об
- 1:21:59этом. Хорошо, мы провели через условный
- 1:22:01PayPal оплату, получили айдишник этой
- 1:22:03оплаты, собрали нашу информацию об
- 1:22:05оплате, в которой у нас есть
- 1:22:06desрипtionшн, то есть описание оплаты,
- 1:22:08там бургер мы купили или что-нибудь
- 1:22:09другое, количество денег, которое мы
- 1:22:11потратили во время этой операции, ну и
- 1:22:12отменённая она или нет. Ну так как у нас
- 1:22:14только оплата производится, поэтому
- 1:22:15операция не может быть отменена. Она
- 1:22:17ещё, в принципе, вот только-только была
- 1:22:19оплачена. Если она и будет отменена, то
- 1:22:20позже. Хорошо. Если вы помните, то мы
- 1:22:22вот в тестовом примерщике в Paymentin
- 1:22:24inf сохраняли здесь ещё поле ID. Но кто
- 1:22:26внимательно смотрел, тот помнит, что это
- 1:22:28поле ID нам нужно было лишь для того,
- 1:22:29чтобы сделать реализацию нашего payйment
- 1:22:31модуля на слайсе, потому что слайс не
- 1:22:34может как-то где-то в каком-то ключе
- 1:22:36хранить айдишник. Ему нужно вот вот,
- 1:22:38чтобы вот в каждой отдельной нашей
- 1:22:40структурке, в каждом отдельном элементе
- 1:22:42слайса был айдишник, чтобы мы потом
- 1:22:43могли циклом бежать и искать нужный. Но
- 1:22:46так как у нас сейчас реализация на мапе,
- 1:22:48то этот самый айдишник я буду просто
- 1:22:50класть в ключ этой самой мапы, и у меня
- 1:22:52уже не будет никакой необходимости
- 1:22:53запоминать этот айдишник в payment info.
- 1:22:56Хорошо, провели оплату. Давайте эти
- 1:22:57комментарии я буду здесь держать рядом.
- 1:22:59А получили айдишник, собственно, этой
- 1:23:01самой оплаты. Вот здесь вот мы провели
- 1:23:02оплату, а здесь вот мы получили айдишник
- 1:23:04этой самой оплаты. Потом мы собрали нашу
- 1:23:06payment infфо, собрали нашу информацию
- 1:23:08об оплате, и нам теперь нужно сохранить
- 1:23:09эту информацию. Как мы её будем
- 1:23:11сохранять? Мы просто будем брать и в
- 1:23:13нашу мапу Payments Info сохранять по
- 1:23:15айдишнику значение. Вот так вот. И после
- 1:23:18этого нам ещё нужно вернуть ID
- 1:23:20проведённой операции. Для чего мы тут
- 1:23:21вот возвращаем ID проведённой операции?
- 1:23:23Чтобы потом где-то извне мы когда
- 1:23:25вызывали метод нашего payймент-модуля,
- 1:23:27мы проводили оплату и могли потом знать
- 1:23:30айдишник этой оплаты, чтобы потом мы
- 1:23:31этот айдишник могли передавать в наши
- 1:23:33другие методы для того, чтобы отменять,
- 1:23:35например, оплату какую-то или получать
- 1:23:37информацию о проведённой оплате. Если бы
- 1:23:39мы здесь айдишник не возвращали, мы бы
- 1:23:41просто проводили эти оплаты и не знали
- 1:23:43бы, а какой айдишник у каждой очередной
- 1:23:44оплаты. И потом мы бы не смогли как бы
- 1:23:46не отменить никакую оплату, не получить
- 1:23:48информацию по ней, потому что просто,
- 1:23:49ну, не знаем, какую оплату искать, как
- 1:23:52бы какую оплату отменять, непонятно. А
- 1:23:53вот так как у нас есть айдишник,
- 1:23:54благодаря нему мы можем однозначно
- 1:23:56определять, какую оплату мы там
- 1:23:58отменяем, какую оплату мы ищем
- 1:23:59информацию. Хорошо. Но здесь, чтобы не
- 1:24:01столкнуться с той же проблемой, что мы
- 1:24:02столкнулись в тестовом примерчике, а
- 1:24:04именно попытка записать что-то в мапу,
- 1:24:06при том, что мы мапу не
- 1:24:07инициализировали, я в конструкторе
- 1:24:09payment модуля нашу мапу буду
- 1:24:11инициализировать через make стандартно.
- 1:24:13Всё, теперь мы в конструкторе мапу
- 1:24:14проинициализировали, и при попытке в неё
- 1:24:16что-то записать у нас никаких паник не
- 1:24:18будет. Хорошо, умеем платить и
- 1:24:20запоминать информацию об оплате. Дальше
- 1:24:22мы хотим уметь отменять какую-то нашу
- 1:24:24операцию, да? То есть заплатили, купили
- 1:24:26там, например, что-нибудь, телефон, а он
- 1:24:28нам бракованный приехал. Ну вот хочется
- 1:24:29отменить эту оплату. Хорошо, у нас метод
- 1:24:31Ccel, он принимает ID-операции.
- 1:24:34ID операции ничего не возвращает и
- 1:24:36должен, собственно, взять и отменить эту
- 1:24:38оплату. У нас в payйment методе, да,
- 1:24:40каждый payment метод, неважно, это
- 1:24:42Bitcoin, PayPal там или банковская
- 1:24:43карта, каждый payment метод у нас помимо
- 1:24:45метода Pay должен предоставлять ещё
- 1:24:47метод cancel, да? Поэтому мы при помощи
- 1:24:49этого Payment метода будем отменять нашу
- 1:24:52оплату по переданному ID. Но мало нам её
- 1:24:55просто отменить, нам нужно ещё и
- 1:24:57запомнить, что мы её отменили. Потому
- 1:24:59что именно помнить, какие операции
- 1:25:01вообще были, помнить, какие операции
- 1:25:03отменены, какие нет - это задача
- 1:25:05payймент-модуля. Потому что задача
- 1:25:07payймен метода - это просто фактически
- 1:25:09провести операцию, либо заплатить, либо
- 1:25:10отменить. Но он ничего не помнит о том,
- 1:25:12какие операции были, какие отменены
- 1:25:14были, какие не отменены, он ничего не
- 1:25:15помнит. Он просто делает, либо проводит,
- 1:25:17либо отменяет. А вот запоминать уже
- 1:25:19информацию об оплатах - это задача,
- 1:25:21повторюсь, нашего Payment-модуля. Так
- 1:25:22вот, мы отменили какую-то нашу
- 1:25:24конкретную операцию. Теперь нам нужно
- 1:25:25запомнить, что мы её отменили. Для этого
- 1:25:27я нахожу мою payment info в маap. То
- 1:25:29есть вот наши payments info. И я получаю
- 1:25:31конкретную payment inf по айдишнику.
- 1:25:33Видите, вот здесь вот если бы у нас была
- 1:25:35реализация на слайсе, нам бы пришлось в
- 1:25:37цикле искать конкретную payment info,
- 1:25:40чтобы в ней перевести
- 1:25:42в true, то есть отменённую. А так как у
- 1:25:45нас мапа, это всё происходит быстро, за
- 1:25:46константное время, и нас всё устраивает.
- 1:25:48Ну, здесь, конечно же, нам нужно
- 1:25:49проверить, что если не ОК. То есть, если
- 1:25:51у нас, в принципе, была попытка отмены
- 1:25:54операции, которой ещё не было, то мы,
- 1:25:56наверное, должны просто, ну, ничего не
- 1:25:58делать, как бы, да, и завершиться сразу
- 1:25:59же. Тут нужно подумать о том, как мы с
- 1:26:02нашими коллегами договорились вот
- 1:26:03конкретно в этом моменте. Если мы
- 1:26:06пробуем через их метод оплаты отменить
- 1:26:08оплату, которой даже не было, будет ли
- 1:26:11это ошибка или нет, будет ли это ломать
- 1:26:13нашу программу или нет. Если нет, то, в
- 1:26:15принципе, то, как мы написали,
- 1:26:16нормально. Если это будет ошибкой, то
- 1:26:18лучше сначала проверять, есть ли в
- 1:26:21принципе такая плата, была ли она, и
- 1:26:23если нет, то ничего не делать. А если мы
- 1:26:26попали в пятьдесят девятую строчку, это
- 1:26:27значит, что у нас оплата была, потому
- 1:26:29что мы вот здесь вот не завершились. А
- 1:26:31если мы тут не завершились, значит, у
- 1:26:32нас оклоue. А если ок true, значит,
- 1:26:35найден нужный элемент. И вот если он был
- 1:26:37найден, только тогда мы отменяем нашу
- 1:26:39оплату фактически через сторонний вот
- 1:26:42этот вот метод оплаты и потом меняем в
- 1:26:44ней вот это поле и перезаписываем.
- 1:26:47payment in с айдишником равно вот эта
- 1:26:50новая инфа. То есть почему я
- 1:26:51перезаписываю? Для чего вообще нужна
- 1:26:53шестьдесят третья строчка? Потому что
- 1:26:55здесь в пятьдесят четвёртой строчке мы
- 1:26:56получили всего лишь копию того элемента,
- 1:27:00который хранился в мапе. И здесь, в
- 1:27:02шестьдесят первой строке, мы просто
- 1:27:03поменяли эту копию. И теперь нам нужно
- 1:27:05перезаписать хранимый ранний элемент.
- 1:27:08Да, именно так это работает. Если я
- 1:27:09просто поменяю копию, то это на хранимый
- 1:27:12в мапе элемент никакого значения,
- 1:27:14никакого влияния не окажет. Видите, нам
- 1:27:16даже сейчас тут подчёркивает что-то
- 1:27:17редактор кода, и он говорит, что
- 1:27:19неиспользуемая запись в поле канцела. То
- 1:27:21есть я как бы записал, но не сохранил
- 1:27:23это нигде. Поэтому я перезаписываю в
- 1:27:25мапу вот этот вот самый элемент. Всё.
- 1:27:26Таким образом мы с вами отменили оплату.
- 1:27:28То есть мы сначала проверили, есть ли
- 1:27:29вообще такая операция, была ли она, есть
- 1:27:31ли нам что отменять. Если да, то
- 1:27:33отменили и запомнили это. О'кей. Теперь
- 1:27:36метод инфо. Тут, в принципе, он
- 1:27:37принимает ID операции, возвращает
- 1:27:39информацию, проведённой операции. То
- 1:27:40есть вот это вот payment info,
- 1:27:41собственно, мы будем возвращать. Точно
- 1:27:43так же мы смотрим, есть ли у нас вообще
- 1:27:45это payйment info, была ли она
- 1:27:46сохранена. Если нет, то мы вернём просто
- 1:27:49пустую структуру. Повторюсь, мы вот
- 1:27:51очень скоро с вами научимся обрабатывать
- 1:27:53такие вот ошибочные ситуации, работать с
- 1:27:54ошибками в Гошке, но пока мы этого не
- 1:27:56умеем делать. Поэтому вот в каких-то
- 1:27:57таких ситуациях исключительных, когда
- 1:27:59идёт попытка отменить оплату, да,
- 1:28:01которой не было, или получить информацию
- 1:28:03об оплате, которой не было, ну и вот, ну
- 1:28:04просто как бы пустую структурку будем
- 1:28:06возвращать, но а тут мы ничего не будем
- 1:28:07возвращать просто. О'кей. Теперь, а если
- 1:28:10у нас всё-таки найдена наша информация,
- 1:28:12мы её будем возвращать. Ну и all info.
- 1:28:14Это, в принципе, получение всей нашей
- 1:28:16мапы вот этой вот. И тут очень
- 1:28:18соблазнительно сделать вот так вот. P
- 1:28:21payment info. То есть мы просто берём и
- 1:28:23мапу, которая у нас вот здесь вот
- 1:28:24хранится в девятой строчке. Берём её и
- 1:28:27возвращаем. Почему так делать нельзя?
- 1:28:29Это связано с подкапотным устройством
- 1:28:31мапы. Мапа - это, на самом деле,
- 1:28:33структура, и одно из полей этой
- 1:28:35структуры - это указатель. Ну вот такую
- 1:28:37вот простую зарисовочку я сделал сейчас,
- 1:28:38чтобы понять, о чём я говорю. Вот это
- 1:28:40наша мапа. Вот это наша мапа, которая у
- 1:28:42нас хранится вот здесь вот. Вот эта вот
- 1:28:43наша мапа. Мапа под собой имеет точно
- 1:28:46так же, как и слайз, указатель на
- 1:28:48какие-то данные. Что будет, если мы вот
- 1:28:50тут вот в восемьдесят шестой строчке
- 1:28:51просто вернём нашу мапу? Мы возьмём и
- 1:28:54вернём копию нашей мапы. Мы скопируем
- 1:28:56эту структуру, а указатель останется тем
- 1:28:59же самым. Указатель будет указывать на
- 1:29:01те же самые данные. Поэтому, если
- 1:29:03кто-то, какой-то потребитель нашей
- 1:29:05структуры, какой-то там недобросовестный
- 1:29:07коллега через метод All Info получит
- 1:29:10нашу мапу, мы как бы мапу-то ему
- 1:29:11копированную вернём, но данные, над
- 1:29:14которыми эта мапа будет работать, будут
- 1:29:16теми же самыми, и наш коллега сможет
- 1:29:18получить вот эту копию мапы и через эту
- 1:29:21копию мапы изменить исходные данные и
- 1:29:23просто перемешать там, переписать все
- 1:29:25наши информации об оплате. Просто сломав
- 1:29:27нам paymentмодуль, мы на это можем
- 1:29:29потерять очень много денег, потому что
- 1:29:30просто через скопированную мапу, через
- 1:29:32скопированный под капотом этой мапы
- 1:29:34указатель у человека, который
- 1:29:36воспользовался методом all info, будет
- 1:29:37доступ к исходным данным. И он сможет
- 1:29:39эти исходные данные менять, и мы уже
- 1:29:41потом тоже никогда не восстановим, а что
- 1:29:42же в итоге там было. Поэтому, чтобы
- 1:29:44такой ситуации избежать, нам нужно
- 1:29:46возвращать не просто копию мапы, а копию
- 1:29:49мапы, которая указывает на копию данных,
- 1:29:52чтобы если тот человек, который получил
- 1:29:54вот эту вот нашу скопированную мапу,
- 1:29:55что-то в ней попытался поменять в этих
- 1:29:57данных, то наши оригинальные данные,
- 1:29:59которые хранятся в нашем Payment модуле,
- 1:30:01не претерпели никаких изменений, чтобы
- 1:30:03нам там ничего нельзя было сломать. Ну
- 1:30:05давайте, собственно, это и сделаем. Как
- 1:30:06это выглядит? Нам нужно создать руками
- 1:30:08новую мапу. То есть я назову её temp
- 1:30:10map, то есть временная мапа. И это будет
- 1:30:12у нас такая же мапа, ключом, которая
- 1:30:15является integer, значением payment
- 1:30:16info. Но я заранее могу предвыделить
- 1:30:18количество элементов в этой мапе. Так
- 1:30:20как я создаю полную копию уже
- 1:30:22существующей мапы, я знаю, сколько в ней
- 1:30:25должно быть элементов. Ровно столько же,
- 1:30:26сколько в той мапе, которую я хочу
- 1:30:28скопировать. Поэтому я тут могу передать
- 1:30:30len payments info. То есть сколько вот у
- 1:30:33нас в Payments Info тут лежит в
- 1:30:35оригинальной, столько как бы я и хочу
- 1:30:37предвыделить для своей новой мапы. Для
- 1:30:38чего я, в принципе, использую
- 1:30:40предвыделение, а элементов в мапе, я
- 1:30:42объяснял в предыдущем уроке. Для того,
- 1:30:44чтобы заранее подготовить себе место,
- 1:30:45куда я буду сохранять эти элементы,
- 1:30:47чтобы компьютер, процессор, там,
- 1:30:48операционная система не тратили время на
- 1:30:51перевыделение новой памяти зря, как бы,
- 1:30:53если я заранее знаю, сколько мне нужно
- 1:30:54элементов, я сразу их должен выделить.
- 1:30:56Хорошо, создали временную мапу. Теперь я
- 1:30:58беру и бегу K value через цикл по нашей
- 1:31:02оригинальной мапе. И все ключи и
- 1:31:05значения я переписываю в новую. И после
- 1:31:08этого я возвращаю ту самую новую мапу,
- 1:31:09которая, по сути, является временной
- 1:31:10мапой. Всё, я решил описанную ранее
- 1:31:13проблему. Здесь я сначала создал вот эту
- 1:31:15вот мапу новую, а потом перегнал в неё
- 1:31:19все данные старые, взял и перекопировал,
- 1:31:21благодаря чему я получил в итоге
- 1:31:23отдельную мапу с отдельными данными. И
- 1:31:26изменение этих данных не будет нам
- 1:31:28руинить, как бы, да, весь наш
- 1:31:30payйmentмодуль оригинальный. Ну вот
- 1:31:32таким вот образом по сути своей мы с
- 1:31:34вами уже описали наш payment модуль. и
- 1:31:37он даже будет работать. Мы умеем
- 1:31:38проводить оплату, сохранять информацию
- 1:31:40об оплате. Мы умеем отменять оплату, мы
- 1:31:42умеем получать информацию о какой-то
- 1:31:44конкретной оплате. И мы умеем с вами
- 1:31:46получать информацию о всех оплатах. При
- 1:31:49том безопасно это делать. Ну, теперь нам
- 1:31:51всё, что осталось - это протестировать
- 1:31:52наш модуль проведения оплат. И для того,
- 1:31:55чтобы сейчас в данном уроке, как бы это
- 1:31:57смешно не звучало, нам не реализовывать
- 1:31:58какую-то криптовалюту, оплату там или
- 1:32:01переписывать свой модуль для
- 1:32:03взаимодействия с Пеayпалом, это всё на
- 1:32:04самом деле сейчас займёт очень много
- 1:32:06времени, и это не тема урока. Поэтому в
- 1:32:08качестве способа проведения оплаты через
- 1:32:10крипту, через PayPal, через банковскую
- 1:32:12карту я сделаю такие упрощённые
- 1:32:14заглушки. Сейчас давайте посмотрим, что
- 1:32:15я имею в виду. То есть я создаю в этом
- 1:32:18paymentсе ещё один пакет, а назову его
- 1:32:20Methods. И тут создам три отдельных
- 1:32:23файла. Первый файл будет называться
- 1:32:25крипто
- 1:32:27пакет Methods, конечно же, да, потому
- 1:32:28что у нас директория называется Methods.
- 1:32:29И тут будет у меня структура крипта. То
- 1:32:31есть это тот самый мой способ проведения
- 1:32:34оплаты через скрипту. Давайте вспомним,
- 1:32:36что должны уметь наши способы проведения
- 1:32:38оплаты. Они должны платить уметь и уметь
- 1:32:40отменять оплату. То есть должны
- 1:32:42удовлетворять интерфейсу payment method.
- 1:32:44Ну давайте это сделаем. Так как у нас
- 1:32:46заглушки, то, в принципе, хранить что-то
- 1:32:48в этой структуре мне не нужно. Мне
- 1:32:49просто нужно ей два метода описать.
- 1:32:52Первый метод у меня будет Pay, который
- 1:32:54принимает USD и возвращает ID операции.
- 1:32:57То есть я буду просто здесь выводить на
- 1:32:58экран. Уплата криптовалютой.
- 1:33:04Размер оплаты здесь вот я буду через
- 1:33:07двоеточие выводить USD, но писать USDT.
- 1:33:11Ну, типа у нас как бы крипта, поэтому
- 1:33:13доллары преобразуются в USDT. Размер
- 1:33:15оплаты вот столько-то, сколько нам
- 1:33:16передали USDT. Так, теперь нам осталось
- 1:33:19вернуть айдишник оплаты. Откуда я его
- 1:33:21возьму? Ну, так как у нас опять же
- 1:33:23заглушка, я его просто, ну, рандомно
- 1:33:25сгенерирую. То есть вот есть пакет RН из
- 1:33:28библиотеки МАс, и в нём есть функция
- 1:33:30замечательная int, которая, если
- 1:33:31почитать описание, просто возвращает
- 1:33:33рандомный негативный integer. Ну, что
- 1:33:35значит негативный? Это либо ноль, либо
- 1:33:38положительный, то есть от нуля до плюс
- 1:33:40бесконечности. Рандомный инт. Это наш
- 1:33:42айдишник. И его, в принципе, вот можно
- 1:33:44вернуть. Ну, как бы это такой упрощённый
- 1:33:46вариант. Можно, конечно, без переменной
- 1:33:48посредника это всё сделать, сразу же
- 1:33:50вернуть результат выполнения функции int
- 1:33:52таким вот образом. Это то же самое.
- 1:33:53О'кей. Теперь мы в нашей крипте умеем
- 1:33:55оплачивать криптовалютой и генерировать
- 1:33:57наш рандомный айдишник. И теперь хочется
- 1:34:00ещё уметь отменять нашу оплату, да,
- 1:34:03потому что иначе мы не сможем
- 1:34:04удовлетворять интерфейсу Payment Method
- 1:34:06и мы не сможем тогда использоваться в
- 1:34:07Payment модуле. И тогда, в принципе, наш
- 1:34:09Paymentмодуль как бы на вот этой крипте
- 1:34:11не сможет завестись. Поэтому делаем
- 1:34:13здесь полное удовлетворение вот этому
- 1:34:15интерфейсу. И необходимо для этого ещё
- 1:34:16описать метод cancel. Он принимает ID
- 1:34:18integer. И что мы будем с этим делать?
- 1:34:21Мы будем просто выводить на экран, что
- 1:34:23отмена крипто операции ID. Ну вот так
- 1:34:27вот айдишник ещё выведу. Там может
- 1:34:28кому-то интересно будет. Вот всё, мы
- 1:34:30отменили эту криптооперацию. Теперь у
- 1:34:32нас есть способ оплаты криптой, который
- 1:34:35нам позволяет платить и отменять наши
- 1:34:37операции. И если приглядеться, мы
- 1:34:38полностью удовлетворяем интерфейсу
- 1:34:40payment метод, что позволит нам
- 1:34:42использовать вот эту крипту в
- 1:34:44Paymentмодуле. Вот здесь вот в
- 1:34:45конструкторе мы сможем её передать. Ну
- 1:34:47давайте ещё сделаем конструктор для
- 1:34:49крипты, просто для красоты, который нам
- 1:34:51вот будет возвращать эту крипту, да? И
- 1:34:52тут вот return, крипто. Всё. Вот такой
- 1:34:54вот простой конструктор, в котором нет
- 1:34:55особого смысла, но для какой-то красоты.
- 1:34:58Вот пускай он будет. Также у нас там
- 1:34:59есть что? PayPal и у нас есть ещё банк.
- 1:35:03Ну давайте вот банк. То есть это у нас
- 1:35:05то же самое, всё абсолютно то же самое.
- 1:35:07Такие же заглушки. BН здесь New BН. И,
- 1:35:10конечно же, здесь нам нужно банк
- 1:35:11создавать и возвращать банк в
- 1:35:13конструкторе.
- 1:35:15А метод мы описываем для банка.
- 1:35:19И тут уже у нас оплата
- 1:35:22через банк. Размер оплаты столько-то. И
- 1:35:25когда вот мы в банке будем платить, я
- 1:35:27буду писать долларов. Ну, то есть банк
- 1:35:29какая-то вот официальная такая
- 1:35:30структура, да, там вот всё вот в
- 1:35:31официальных таких монетах
- 1:35:33государственных, то есть вот в долларах.
- 1:35:34О'кей. А отмена банковской операции. Ну
- 1:35:39и вот, собственно, айдишник этой
- 1:35:40операции. Точно так же будем выводить.
- 1:35:41Всё осталось делать то же самое для
- 1:35:43Пеayпала. Это PayPal у нас. Давайте вот
- 1:35:44так вот. PAL New Paypal.
- 1:35:48Здесь у нас будет возвращаться структура
- 1:35:50PayPal. Здесь у нас будет создаваться
- 1:35:51эта структура Пеayпала. Здесь это у нас
- 1:35:53будет метод PayPже, а не крипты. Для
- 1:35:56крипты мы уже описали в другом файле. Ну
- 1:35:57и всё. Смотрим. То есть через PayPal у
- 1:35:59нас оплата. Через PayPal размер оплаты
- 1:36:03столько-то. А тут я буду просто писать
- 1:36:04USD. То есть как бы и не доллары, и не
- 1:36:07USDT. Ну вот через PayPal. Вот так вот.
- 1:36:08И тут отмена операции
- 1:36:12PayPal.
- 1:36:14Ну всё, вот тут PayPal. Хорошо, мы с
- 1:36:17вами сделали вот эти наши три заглушки.
- 1:36:19Как будто бы это то, что нам должны были
- 1:36:20написать наши коллеги. Но так как у нас
- 1:36:22коллеги сейчас только метафизические
- 1:36:24какие-то, да, которых не существует, к
- 1:36:26сожалению, в данный момент, то мы будем
- 1:36:28пользоваться такими заглушками, потому
- 1:36:29что мы сейчас, ну, в целях урока мы не
- 1:36:30можем реально писать вот оплату через
- 1:36:32скрипту там, да, это просто на 10 лет
- 1:36:34всё затянется. Вот давайте посмотрим,
- 1:36:36как это всё у нас будет устроено. Вот
- 1:36:38здесь вот я создам переменную Methodд и
- 1:36:41в неё я положу methods new crypto, то
- 1:36:45есть изначально мы будем через крипту
- 1:36:46оплачивать. После этого я создаю payment
- 1:36:49модуль. Это у нас payments new payment
- 1:36:52modдуul. И передаю сюда payment метод, а
- 1:36:55именно вот этот вот new крипt, который
- 1:36:56мы создали.
- 1:36:58Создали наш paymentмодуль. Теперь
- 1:37:00давайте воспользуемся его методами. Для
- 1:37:02начала я хочу совершить несколько
- 1:37:03покупок и потом посмотреть, как
- 1:37:05информацию о всех этих покупках наш
- 1:37:07Paymentмодуль сохранил. Давайте я
- 1:37:10проведу первую нашу покупку при помощи
- 1:37:11нашего payment-модуля. Pay Description.
- 1:37:14Ну, первое, что я куплю - это вот
- 1:37:15бургер. И стоит он там, ну, 5 долларов,
- 1:37:18например. Хорошо. Потом второе, что я
- 1:37:20хочу купить- это, допустим, у меня будет
- 1:37:22телефон какой-нибудь. Телефон и телефон
- 1:37:26там стоил 500 долларов. После этого я
- 1:37:28вот в стиме хочу ещё купить какую-то
- 1:37:30игру, да, например, это вот у меня будет
- 1:37:31игра. И игра стоила 20 долларов. Всё, я
- 1:37:34совершил через наш paymentмодуль три
- 1:37:36покупки. И теперь я хочу их все вывести
- 1:37:38на экран, просто получить информацию о
- 1:37:40всех проведённых операциях. Хорошо. All
- 1:37:43info. И вот наш payment модуль All info.
- 1:37:46И теперь мы можем вывести её на экран.
- 1:37:48Выведу через библиотеку PP, пожалуй,
- 1:37:50просто для красоты, потому что обычный
- 1:37:52FMT Printel с коллекциями не очень
- 1:37:53работает. И вот я через ПП выведу все.
- 1:37:57наши оплаты. И здесь через запятую наша
- 1:38:01all info. Давайте запустим нашу
- 1:38:02программу
- 1:38:04goun main.go. И смотрим. Во-первых, мы
- 1:38:08видим вывод наш. То есть у нас проходили
- 1:38:10оплаты. И так как мы оплачивали через
- 1:38:13крипту, потому что в Paymentмодуль,
- 1:38:15payment метод мы положили именно крипту.
- 1:38:17У нас раз оплата, два оплата, три
- 1:38:19оплата, они были через крипту. оплата
- 1:38:22криптовалютой 5 USDT, потом 500 USDT,
- 1:38:25потом 20 USDT. И мало того, что мы с
- 1:38:28помощью нашего payment-модуля провели
- 1:38:30все оплаты через крипту, так мы ещё и
- 1:38:33сохранили информацию о всех этих
- 1:38:35оплатах. Мы сохранили, что мы бургер с
- 1:38:37вами купили за 5 долларов. Вот как бы
- 1:38:40тому подтверждение. Мы сохранили, что мы
- 1:38:41телефон купили за 500 долларов. И мы
- 1:38:44сохранили, что мы игру купили за 20
- 1:38:45долларов. И все эти операции не были
- 1:38:47отменены. Ну, потому что мы их не
- 1:38:49отменяли. Давайте теперь попробуем
- 1:38:50отменить какую-то операцию. Для того,
- 1:38:52чтобы нам отменить операцию, нам нужно
- 1:38:54её айдишник знать. Айдишник у нас
- 1:38:56возвращается через метод payй, когда мы
- 1:38:58его вызываем. То есть для того, чтобы
- 1:39:00нам отменить покупку телефона, нам нужно
- 1:39:02здесь сохранить IDфон. Ну, то есть это
- 1:39:05как бы ID вот покупки телефона. О'кей. И
- 1:39:07вот здесь вот после всех покупок я
- 1:39:09возьму и через paymentмодуль отменю
- 1:39:12покупку телефона через айдишник, который
- 1:39:14я запомнил после оплаты. Посмотрим, что
- 1:39:16теперь будет выведено нашей программой.
- 1:39:18Оплата криптой. Оплата криптой. Ещё одна
- 1:39:21оплата криптой. То есть вот мы сначала
- 1:39:22купили бургер, потом здесь мы с вами
- 1:39:24купили телефон, потом мы купили с вами
- 1:39:25игру, а потом отмена криптооперации с
- 1:39:28таким-то айдишником. Вот. То есть у нас
- 1:39:30был айдишник, который вернулся из метода
- 1:39:32Pay. Мы его сохранили в IDфоne и потом
- 1:39:34отменили покупку телефона. Теперь
- 1:39:36давайте посмотрим, как это отразилось на
- 1:39:38информации, которую мы сохранили в
- 1:39:39Payment модуле. Мы с вами видим, что
- 1:39:41телефон true. Что это значит? Это значит
- 1:39:45покупка телефона была отменена. И мы это
- 1:39:47мало того, что действительно провели
- 1:39:49через криптовалюту, мы действительно
- 1:39:51вызвали вот этот вот метод каel и
- 1:39:53действительно отменили криптооперацию,
- 1:39:55так мы ещё это и запомнили. Ну, в
- 1:39:57принципе, можно ещё что протестировать.
- 1:39:58Давайте подумаем. Мы можем получать
- 1:40:00информацию о какой-то отдельной оплате.
- 1:40:02Давайте запомню айдишник операции, когда
- 1:40:04мы покупали с вами игру. Назову это ID
- 1:40:06Game. И вот после всего я захочу
- 1:40:08получить информацию об этой операции. То
- 1:40:09есть я вот Game Info, это payment модуль
- 1:40:14info. То есть я хочу получить инфу. И
- 1:40:16вот передаю айдишник операции, который я
- 1:40:18получил во время оплаты этой самой игры.
- 1:40:20И теперь выведу на экран
- 1:40:23вот это вот Game Info.
- 1:40:26Запущу наше с вами приложение. И вот
- 1:40:28вижу, что в конце я просто смог отдельно
- 1:40:30взять и получить по айдишнику информацию
- 1:40:33об оплате игры. Всё, наш payment модуль
- 1:40:35с вами полностью работает. Но так как у
- 1:40:38нас тема интерфейсы, в чём вообще вот
- 1:40:40вся суть того была, что мы сейчас с вами
- 1:40:43делали? Теперь благодаря тому, что наш
- 1:40:46вот этот крутой payймент-модуль, который
- 1:40:48умеет проводить операции, который умеет
- 1:40:50запоминать информацию о проведённых
- 1:40:52операциях, который умеет отменять
- 1:40:54операции, и благодаря тому, что он
- 1:40:57завязан на интерфейсе, я могу поменять
- 1:41:00одну строчку кода, и у меня теперь все
- 1:41:03операции об оплате будут происходить не
- 1:41:05через скрипту, а через банк, например.
- 1:41:07Как это сделать? Метод я буду делать не
- 1:41:10New Crypto, а New Bank. Всё, я поменяв
- 1:41:15одну строчку кода, я перенаправил вообще
- 1:41:18весь поток платёжных операций
- 1:41:20скриптокошелька на банковскую карту.
- 1:41:22Давайте запустим. И мы видим, что теперь
- 1:41:25у нас оплата идёт через банк. И размер
- 1:41:27оплаты у нас считается не в USDT, а в
- 1:41:30долларах. Это нам свидетельствует о том,
- 1:41:32что теперь мы с вами платим через банк,
- 1:41:34а не через скрипту, как мы это делали
- 1:41:36ранее. И вот это уже действительно прямо
- 1:41:38такой жирный и очень близкий к
- 1:41:40настоящему пример использования
- 1:41:42интерфейсов. То есть да, мы в теории
- 1:41:44могли бы с вами тут обойтись без
- 1:41:46интерфейсов. Мы могли бы написать три
- 1:41:47отдельных Payment-модуля, делали бы они
- 1:41:50одно и то же, только у одного бы здесь в
- 1:41:52зависимостях была крипта, у второго
- 1:41:54здесь вот в поле как бы, да, была бы
- 1:41:56PayPal и у третьего банк. Но это
- 1:41:58дублирование кода и в принципе все
- 1:42:00проблемы, которые за этим идут, мы с
- 1:42:01вами уже разобрали. А так у нас один
- 1:42:03payment модуль, и мы просто поменяв одну
- 1:42:05строчку кода, можем платить либо в
- 1:42:07крипте, либо через банк, либо давайте
- 1:42:08вот через PayPal протестим. Запускаю.
- 1:42:12Оплата через PayPal 5 USD. Отлично. И
- 1:42:15независимо от того, какой метод оплаты
- 1:42:17мы используем, у нас paymentмодуль
- 1:42:19продолжает всё также корректно
- 1:42:20отрабатывать, потому что наша логика
- 1:42:22payment-модуля по сохранению информации
- 1:42:24об оплате, она общая для всех методов
- 1:42:27оплаты. И ещё одно огромное
- 1:42:30преимущество. Представьте, что через
- 1:42:31время к нам пришёл наш босс и сказал:
- 1:42:33"Всё чётко, но теперь я хочу ещё уметь
- 1:42:35платить бонусными баллами". Мы просто
- 1:42:37берём, просим наших коллег или сами
- 1:42:39пишем, например, бонус, да, и это будет
- 1:42:42у нас оплата через бонусы. Вот наша
- 1:42:44оплата через бонусы. Тут new бонус.
- 1:42:49Бонус, конечно же, тут тоже у нас бонус.
- 1:42:52И тут методы для нашего бонуса.
- 1:42:56Это у нас оплата бонусами.
- 1:43:00Размер оплаты, там столько-то бонусов.
- 1:43:03Ну, пускай у нас один доллар будет. Это
- 1:43:04один бонус. Отмена операции бонусами.
- 1:43:08Всё. И теперь мы в мейне берём. И здесь
- 1:43:11вместо пайпала new бонус.
- 1:43:14Запускаем. Оплата бонусами. Всё отлично.
- 1:43:17Отмена операций бонусами. То есть нам
- 1:43:19теперь вообще нет никакой необходимости
- 1:43:21лезть в кишки нашего payйment-модуля,
- 1:43:23что-то тут придумывать, что-то тут
- 1:43:24менять. Мы просто сказали, что, ребята,
- 1:43:26мы написали вот такую вот крутую
- 1:43:28универсальную штуку, и вам всё, что
- 1:43:30требуется - это удовлетворять вот этому
- 1:43:32интерфейсу Paymentд. Если ваш метод
- 1:43:35оплаты умеет платить и отменять оплаты,
- 1:43:37добро пожаловать, вы можете спокойно
- 1:43:39пользоваться нашим Payment-модулем. Вот
- 1:43:41для этого нам и нужны интерфейсы. Таким
- 1:43:44образом, мы с вами поближе познакомились
- 1:43:45с интерфейсами. Мы с вами обсудили, для
- 1:43:47чего вообще могут быть нужны интерфейсы.
- 1:43:49Потом мы с вами посмотрели на каком-то
- 1:43:50совсем простом примерчике с BMW Жигой и
- 1:43:53ламбой, как эти интерфейсы работают, а
- 1:43:55потом на таком большом, объёмном и, что
- 1:43:57самое главное, достаточно сильно
- 1:43:58приближенном к реальной разработке
- 1:44:00примере, мы с вами разобрали, как
- 1:44:02интерфейсы используются на практике. И в
- 1:44:04данном случае интерфейсы нам позволили
- 1:44:07очень сильно сократить количество кода,
- 1:44:09которое мы могли бы написать, и очень
- 1:44:11сильно упростить взаимодействие с этим
- 1:44:14нашим самым новым кодом. Плюс очень
- 1:44:16сильно упростить написание новых методов
- 1:44:19оплат. Теперь нам для того, чтобы
- 1:44:20написать новый метод оплаты, нам не
- 1:44:22придётся лезть в кишке этого модуля
- 1:44:24проведения оплат. Просто пишите новый
- 1:44:25метод себе и впихаете его в наш модуль
- 1:44:29проведения оплат, который просто зависит
- 1:44:30от интерфейса PaМетод. Интерфейсы, на
- 1:44:32самом деле, тема может быть довольно
- 1:44:34неочевидная, поэтому прошу вас обратить
- 1:44:36на это внимание. Интерфейсы в реальных
- 1:44:37проектах встречаются крайне часто,
- 1:44:39поэтому это вот тема, которую необходимо
- 1:44:41впитать, осознать, понять и, что самое
- 1:44:43главное, попрактиковаться в ней.
- 1:44:46[музыка]
- 1:44:51Следующая наша тема - это обработка
- 1:44:52ошибок. Посмотрим вообще, как мы можем
- 1:44:54обрабатывать ошибки в Гошке, для чего
- 1:44:57нам это нужно, в каких случаях у нас
- 1:44:58могут возникать ошибки, и рассмотрим
- 1:45:00разные подходы вообще к этому делу.
- 1:45:02Перед тем, как изучать инструмент ошибок
- 1:45:04и обработки ошибок, мы должны понять,
- 1:45:06для чего нам вообще этот инструмент
- 1:45:08осваивать. Ну, к примеру, вот мы
- 1:45:10вызываем какую-то функцию проведения
- 1:45:12оплаты. Эта функция может завершиться
- 1:45:15успешно, а может завершиться с ошибкой.
- 1:45:18Почему она может завершиться с ошибкой?
- 1:45:20Ну, к примеру, при проведение оплаты
- 1:45:22выяснилось, что мы хотим купить вещь,
- 1:45:24которая стоит больше денег, чем у нас
- 1:45:26есть на балансе. То есть мы имеем на
- 1:45:28балансе, допустим, 80 долларов, а вещь,
- 1:45:31которую мы хотим купить, стоит 100
- 1:45:32долларов. Соответственно, у нас
- 1:45:34недостаточно средств, и мы получим
- 1:45:36ошибку. То есть мы не смогли оплатить.
- 1:45:38Ну, если всё проходит хорошо, то мы
- 1:45:40получаем уведомление о том, что мы
- 1:45:41успешно оплатили нашу покупку. Но в
- 1:45:43случае ошибки нам бы хотелось ещё
- 1:45:46понимать, что именно пошло не так. То
- 1:45:48есть просто увидеть сообщение ошибка
- 1:45:50оплаты, ну как-то, блин, непонятно,
- 1:45:53почему ошибка произошла. Это какая-то
- 1:45:55сетевая ошибка или у нас денег на
- 1:45:57балансе недостаточно? То есть обычно при
- 1:45:59возникновении ошибки хочется ещё и
- 1:46:01понимать, что именно пошло не так. Ну
- 1:46:03давайте обратимся к нашему коду на и
- 1:46:05представим, как бы мы могли с уже
- 1:46:07имеющимися знаниями реализовать вот
- 1:46:09такую вот обработку ошибок, где нам бы
- 1:46:10возвращалось сообщение, что пошло не
- 1:46:12так. А если всё хорошо, то мы бы
- 1:46:14выводили успех. Ну вот представим, что у
- 1:46:16нас есть какая-то функция pay. Она будет
- 1:46:19списывать у какого-то пользователя
- 1:46:20какой-то баланс. Для этого для начала
- 1:46:22давайте создадим простенькую структуру
- 1:46:24пользователя. У него будет имя и у него
- 1:46:28будет баланс
- 1:46:30целочисленный. Ну, допустим, количество
- 1:46:32там долларов, например. И вот функция
- 1:46:34pay, она будет принимать юзера и будет
- 1:46:36принимать количество долларов, которое
- 1:46:37нужно списать.
- 1:46:40Соответственно, в мейне мы создаём
- 1:46:41экземпляр юзера. Пускай зовут его Олек.
- 1:46:46И баланс будет 10 долларов. После этого
- 1:46:49мы вызываем функцию Pay, передаём туда
- 1:46:51юзера и количество долларов, которые
- 1:46:52нужно списать. Ну, допустим, мы хотим
- 1:46:53совершить покупку на 5 долларов. Можете
- 1:46:55обратить внимание, что если мы тут
- 1:46:56сейчас у юзера что-то изменим на юзера
- 1:46:59измейна, это никак не повлияет, потому
- 1:47:00что при вызове функции pay мы передаём
- 1:47:03лишь копию, то есть тут создаётся новая
- 1:47:05копия. Поэтому мы хотим юзера принимать
- 1:47:07по указателю, чтобы вот этого исходного
- 1:47:08юзера поменять. Ну и, соответственно,
- 1:47:10тут нам нужно взять указатель на юзера.
- 1:47:12О'кей. Теперь мы функции P берём и
- 1:47:13списываем у юзера
- 1:47:16баланс настолько, насколько нужно
- 1:47:18списать для покупки. О'кей. Теперь я
- 1:47:20хочу выводить юзера до покупки и после.
- 1:47:22Для этого хочу воспользоваться
- 1:47:23библиотекой ПП. Чтобы мне
- 1:47:24воспользоваться сторонней библиотекой,
- 1:47:25мне для начала нужно объявить модуль.
- 1:47:30После этого я уже ранее скачанную
- 1:47:31библиотеку могу использовать в коде.
- 1:47:36и написать goйди, потому что в гомоде у
- 1:47:39меня ещё нету, но я её использую в коде.
- 1:47:41Для того, чтобы она автоматически
- 1:47:43используемая библиотека добавилась к нам
- 1:47:44в зависимости, нужно прописать gid.
- 1:47:50Вот всё скачалось, добавилось. И теперь
- 1:47:52я могу выводить usеer до
- 1:47:56и usеer после.
- 1:48:01Запускаю нашу программу. Вот user до
- 1:48:04Олег с балансом 10. Потом произвели
- 1:48:06списание на 5 долларов. Олег баланс 5.
- 1:48:09Ну 10 - 5 = 5. Всё правильно. Теперь
- 1:48:11представим, что у нас списание было бы
- 1:48:12на 50 долларов, да? То есть Олег
- 1:48:15попытался купить что-то на 50 долларов.
- 1:48:16Запускаем. И как мы видим, у нас баланс
- 1:48:19ушёл в отрицательный. А может ли быть
- 1:48:21такой? Нет. То есть если у нас на
- 1:48:22балансе 10 долларов, то это максимум,
- 1:48:25что мы можем потратить. Поэтому наша
- 1:48:27программа работает неверно. Мы,
- 1:48:28вообще-то, хотим у пользователя
- 1:48:30списывать баланс только, если он
- 1:48:32действительно может оплатить покупку,
- 1:48:34которую он хочет оплатить. Ну, для этого
- 1:48:35можем добавить условия, что мы будем
- 1:48:37выполнять вот этот код, будем списывать
- 1:48:39баланс только если баланс юзера хватает
- 1:48:42для того, чтобы оплатить. Как это
- 1:48:44проверить? Мы можем посмотреть, что если
- 1:48:46баланс юзера минус покупка больше либо
- 1:48:48равно нулю, значит, денег хватает.
- 1:48:50Значит, мы можем произвести списание,
- 1:48:52иначе мы ничего не делаем. Ну, хорошо.
- 1:48:54Запускаем нашу программу. Зер до, usеer
- 1:48:57после. Вот, ничего не изменилось, баланс
- 1:48:59не списался. Но с точки зрения
- 1:49:00пользователя, который вот эту покупку
- 1:49:02осуществляет, как вот с его точки зрения
- 1:49:05это выглядит? Он попытался совершить
- 1:49:06оплату, но не увидел никакого
- 1:49:08результата. То есть деньги не списались,
- 1:49:10просто молча не списались деньги. И
- 1:49:12непонятно типа почему вообще была
- 1:49:15покупка, не было покупки. Ну непонятно,
- 1:49:17что с этим делать. Нам нужно как-то
- 1:49:19изнутри функции pay сигнализировать тем,
- 1:49:21кто эту функцию использует, было ли
- 1:49:23списание или нет. Ну хорошо, давайте мы
- 1:49:25будем возвращать переменную буll. Мы, в
- 1:49:28принципе, можем. И мы будем возвращать
- 1:49:30true, если списание было. То есть вот у
- 1:49:33нас списание return true. Если списания
- 1:49:35не было, тогда return false. О'кей. Мы
- 1:49:39можем эту булискую переменную получать.
- 1:49:41Назовём её paid, то есть оплачено ли. И
- 1:49:44тут, если,
- 1:49:47тогда была произведена оплата.
- 1:49:51Иначе
- 1:49:52[аплодисменты]
- 1:49:54оплаты не было. О'кей. Запускаем нашу
- 1:49:57программу. Ну, видим, что оплаты не
- 1:49:59было. Ага. А если тут поставим,
- 1:50:00например, покупку на 3 доллара, была
- 1:50:03произведена оплата. Хорошо. Но в чём
- 1:50:05сейчас у нас недостаток нашей программы?
- 1:50:07В том, что если оплаты не было, то мы
- 1:50:09даже не знаем, почему её не было. Нам
- 1:50:11просто функция Pay вернула булин. Нам
- 1:50:14сказали, что оплаты не было, но даже
- 1:50:16никаких причин не написали, почему этой
- 1:50:18оплаты не было. То есть у нас могло не
- 1:50:20хватить баланса. А если у нас писание бы
- 1:50:22производилось через какой-нибудь внешний
- 1:50:24модуль, в который мы ходим по сети, да,
- 1:50:26был бы какой-нибудь сетевой запрос, и у
- 1:50:29нас ошибка была бы в этом сетевом
- 1:50:30запросе, нам бы тоже просто вернулся
- 1:50:32фолs и всё. И мы бы даже не поняли,
- 1:50:34почему оплаты не было. На самом деле
- 1:50:36тоже совсем не информативно и неудобно.
- 1:50:38Мы даже не знаем, в чём ошибка. Мы даже
- 1:50:40не знаем, как её исправить. Ну хорошо,
- 1:50:42мы бы тогда вместо булина могли бы
- 1:50:43возвращать строку,
- 1:50:45и в этой строке мы бы ничего не писали,
- 1:50:48если бы ошибки не было. То есть пустую
- 1:50:50строку бы возвращали. Но если ошибка
- 1:50:52была бы, мы бы в этой строке возвращали
- 1:50:53описание ошибки. То есть у нас здесь
- 1:50:55ошибка, потому что недостаточно средств.
- 1:50:59Всё, давайте немножечко перевернём наш
- 1:51:00код и будем смотреть на то, что usеer
- 1:51:04банс минуше нуля и в таком случае сразу
- 1:51:07же возвращать недостаточно средств.
- 1:51:10Иначе просто будем производить списание
- 1:51:12таким вот образом. Всё. То есть у нас
- 1:51:15логика осталась той же, что если у нас
- 1:51:17не хватает средств, то мы вернём эту
- 1:51:19ошибку в строке, иначе вернём пустую
- 1:51:21строку. Ну хорошо, мы бы могли получать
- 1:51:23эту строку и тут смотреть, что если у
- 1:51:25нас пустая строка, тогда была
- 1:51:27произведена оплата. Иначе оплаты не
- 1:51:29было. Причина.
- 1:51:32И здесь добавляем нашу строку.
- 1:51:34Запускаем. Ну и видим, что оплаты не
- 1:51:36было. Причина недостаточно средств.
- 1:51:38Вроде как выглядит достаточно здраво,
- 1:51:39что мы теперь можем снаружи функции Pay
- 1:51:42смотреть на то, что нам вернулось. Если
- 1:51:44пустая строка, значит, у нас никаких
- 1:51:46ошибок не было. Если строка не пустая,
- 1:51:49тогда была ошибка, и мы выводим эту
- 1:51:51ошибку. Недостаточно средств. Один из
- 1:51:53самых больших недостатков вот такого
- 1:51:55подхода, то что мы, вызывая функцию Pay,
- 1:51:57заранее не можем знать, для чего это
- 1:52:00возвращаемое значение string у нас
- 1:52:02нужно. Мы можем думать, что тут у нас
- 1:52:03закодирован какой-нибудь айдишник
- 1:52:05операции. Нам его, может быть, нужно
- 1:52:07куда-нибудь сохранить, чтобы потом иметь
- 1:52:09доступ к этой операции. Нам нужно прямо
- 1:52:11залезать внутрь этой функции pay и
- 1:52:13смотреть, для чего именно используется
- 1:52:15strст. Тут вот конкретно для обработки
- 1:52:17ошибок. Но ведь у нас strтринг может
- 1:52:19использоваться и для других целей. К
- 1:52:20примеру, может быть после оплаты в этой
- 1:52:22строке должна быть возвращена дата
- 1:52:24оплаты или время оплаты в строке или имя
- 1:52:27человека, который произвёл оплату.
- 1:52:29Непонятно. нам вот отсюда вот с точки
- 1:52:30зрения потребителей функции Pay, с точки
- 1:52:33зрения тех, кто её использует, нам вот
- 1:52:35непонятно, что эта строка вообще делает,
- 1:52:37для чего она, а уж тем более нам нужно
- 1:52:39каким-то образом догадаться, что она
- 1:52:40нужна для индикации ошибок в Голенге.
- 1:52:43Для того, чтобы однозначно говорить,
- 1:52:45когда у нас возвращаемое значение служит
- 1:52:47индикатором ошибки, придумали er. Что
- 1:52:51это такое? Erрор - это интерфейс,
- 1:52:54который написан разработчиками голенга.
- 1:52:57Всё, что в этом интерфейсе есть - это
- 1:52:58метод ир, который возвращает string. То
- 1:53:00есть всё, что у нас содержит метод ир,
- 1:53:03возвращающий strтринг, это всё у нас
- 1:53:05может быть ошибкой. Хорошо, мы
- 1:53:08объявляем, что мы возвращаем не строку,
- 1:53:10а то, что мы возвращаем именно ошибку.
- 1:53:13Что это значит? Это значит, что когда
- 1:53:15теперь мы будем вызывать функцию Pay и
- 1:53:18она возвращает ошибку, мы точно знаем,
- 1:53:20что в этом возвращаемом значении не
- 1:53:22какое-то полезное там значение,
- 1:53:24результат выполнения функции или ещё
- 1:53:26что-нибудь. То, что тут именно ошибка.
- 1:53:28Ошибка, как и указатель у нас может
- 1:53:31быть, что значит, что ошибки нету, или
- 1:53:33может быть чем-то наполнено. Чуть
- 1:53:35попозже мы про это поговорим. Давайте
- 1:53:37посмотрим, как используются ошибки у нас
- 1:53:39в Goldeng. Ну, для начала мы бы хотели
- 1:53:40возвращать ошибку, да? Ну, мы не можем
- 1:53:42вернуть строку, потому что строка и
- 1:53:44ошибка - это всё-таки две разные
- 1:53:46сущности. У строки нету никакого метода
- 1:53:49Erро, потому что это обычная строка,
- 1:53:51поэтому строка не может удовлетворить
- 1:53:52этому интерфейсу. Мы не можем под этот
- 1:53:54интерфейс вернуть вот эту строку. Нам
- 1:53:56нужно возвращать ошибку. Для того, чтобы
- 1:53:58ошибки создавать, есть стандартный пакет
- 1:54:01Erors. И в нём есть функция new. В эту
- 1:54:04функцию new мы можем передать
- 1:54:06необходимое сообщение об ошибке,
- 1:54:08недостаточно средств, и она нам вернёт
- 1:54:10ошибку.
- 1:54:12Вот, то есть наша ошибочка. Мы в неё
- 1:54:15положили ошибку, которую создал нам
- 1:54:17метод New из пакета Eros. Давайте
- 1:54:19посмотрим, что он делает под капотом.
- 1:54:20Под капотом он просто создаёт какую-то
- 1:54:23структурку error string. И эта
- 1:54:25структурка, как мы видим, удовлетворяет
- 1:54:26нашему интерфейсу. Она содержит метод
- 1:54:28ир, который возвращает strтн. И всё. То
- 1:54:30есть, мы в эту структурку кладём наше
- 1:54:33сообщение. И потом, если будем вызывать
- 1:54:35у неё метод eror, то будет выводиться
- 1:54:36наша сообщенька, возвращаться в строке.
- 1:54:39Ну и всё. И мы возвращаем этот error
- 1:54:40string. Возвращается он по указателю. В
- 1:54:42принципе, тут, если бы не было
- 1:54:44указателя, то и возвращать можно было бы
- 1:54:45просто копию. Ну, как уж написали.
- 1:54:47Хорошо, создали ошибку. И теперь можем
- 1:54:50её вернуть. Всё, у нас - это переменная,
- 1:54:54которая удовлетворяет интерфейсу eror.
- 1:54:56Могли бы сразу написать return errors
- 1:54:58new для того, чтобы не создавать
- 1:54:59переменную посредника. Всё хорошо. Таким
- 1:55:02образом, мы возвращаем ошибку. А что
- 1:55:04делать, если ошибки нет? Если ошибки
- 1:55:06нет, возвращаем л. просто л. Это значит,
- 1:55:09что ошибки нету. Теперь мы вызываем,
- 1:55:12когда функцию Pay, мы получаем ошибку. И
- 1:55:15после этого смотрим, что если у нас
- 1:55:17ошибка неравна Нил, это значит, что
- 1:55:20какая-то ошибка была, потому что она не
- 1:55:22равна Нил. Если была ошибка, мы выводим,
- 1:55:25оплаты не было. Причина
- 1:55:28тор, соответственно, когда мы вызовем
- 1:55:30этот метод ир, то вернётся наша строка,
- 1:55:33которую мы положили внутрь этой ошибки.
- 1:55:35Вернётся наша строка недостаточно
- 1:55:37средств. Иначе, то есть, если всё-таки
- 1:55:40ошибки не было, если она всё-таки равна
- 1:55:41NILл, тогда оплата была произведена.
- 1:55:45Сохраняем и запускаем нашу программу.
- 1:55:47Оплаты не было, причина недостаточно
- 1:55:49средств. Ход выполнения программы
- 1:55:51остался тем же самым, та же самая суть.
- 1:55:53Но в чём мы получили преимущество? В
- 1:55:56том, что когда мы вызываем функцию pay,
- 1:55:58мы однозначно знаем, что возвращаемое
- 1:56:00значение - это не какая-то там строчка,
- 1:56:02в которой что-то закодировано,
- 1:56:04какое-нибудь имя или какой-нибудь
- 1:56:06айдишник операции. Мы точно знаем, что
- 1:56:08нам вот в этом возвращаемом значении
- 1:56:10будут сигнализировать, была ли ошибка во
- 1:56:12время выполнения функции pay. Если
- 1:56:14ошибки не было, нам вернётся л. Всё. И
- 1:56:17мы сможем сравнивать ошибку с Нилом.
- 1:56:19Если там что-то было, то мы сможем
- 1:56:22вывести это сообщение на экран. Сейчас
- 1:56:24будет небольшое отступление для тех, кто
- 1:56:26хочет подушнить чуть-чуть и сам
- 1:56:28подушниться. Я объясню, почему мы вообще
- 1:56:30можем интерфейс
- 1:56:32сравнивать с NILл. Хотя нил - это у нас
- 1:56:34чисто прерогатива указателей, да,
- 1:56:36сравнивать с NILл. Нил - это как бы, ну,
- 1:56:38пустой указатель, типа, почему же мы
- 1:56:40тогда можем сравнивать интерфейс с
- 1:56:41Нилом? Ну, во-первых, можем почитать
- 1:56:43описание, да, то, что NILЛ у нас - это
- 1:56:45идентификатор, который показывает
- 1:56:47нулевое значение для указателей, для
- 1:56:49каналов. Мы ещё узнаем, что такое каналы
- 1:56:51для функций, для интерфейсов, для map,
- 1:56:54для слайсов. то есть. Если у нас
- 1:56:56какой-то интерфейс имеет нулевой типл,
- 1:57:00это значит, что под этим интерфейсом у
- 1:57:02нас не лежит никакого объекта. Как это
- 1:57:04выглядит с точки зрения вообще голенга?
- 1:57:06То есть интерфейс с точки зрения ленга -
- 1:57:08это два указателя. Под капотом интерфейс
- 1:57:11имеет два указателя. Первый указатель -
- 1:57:13это на тип объекта, второй указатель на
- 1:57:15сам объект. Почему так, мы уже будем
- 1:57:17разбирать, когда будем готовиться к
- 1:57:18собеседованиям, но сейчас просто держим
- 1:57:20в голове, что интерфейс - это два
- 1:57:22указателя под капотом. Если у нас под
- 1:57:24интерфейсом возвращается NIL, это
- 1:57:26значит, что и тип объекта NIL, и сам
- 1:57:29объект NILл, то есть, ну, у нас
- 1:57:30получается нулевой интерфейс. Поэтому
- 1:57:32при сравнении интерфейса с Нилом мы
- 1:57:35будем получать true, если у нас пустой
- 1:57:37интерфейс, и фол, если в интерфейсе
- 1:57:39что-то есть. Потому что как только мы в
- 1:57:41наш интерфейс возвращаем какой-то
- 1:57:43объект, вот здесь вот мы возвращаем
- 1:57:45errors new. То есть тип объекта будет
- 1:57:48error string, тип объекта error string,
- 1:57:51а сам объект, вот этот вот наш
- 1:57:53экземпляр, структуры error string. И это
- 1:57:56будет нел, и это будет нел,
- 1:57:58соответственно, и интерфейс тоже будет
- 1:58:00нел. Поэтому здесь мы узнаем, была ли у
- 1:58:03нас ошибка. Если тут что-то есть, то
- 1:58:05интерфейс нел, и вот это условие у нас
- 1:58:07проходит. Также бывают у нас ситуации,
- 1:58:10когда отдельно тип объекта у нас
- 1:58:12определяется, а сам объект не
- 1:58:14определяется. Это ситуации довольно
- 1:58:16редкие, сейчас не будем на них тратить
- 1:58:18время, но вот такое вот небольшое
- 1:58:20душнилово я вам привёл, чтобы понимали,
- 1:58:22почему интерфейс вообще можно сравнивать
- 1:58:24с Нилом. Ну об этом, если честно, можно
- 1:58:26особо даже не задумываться. Просто
- 1:58:27знать, что если у нас в ошибке Нил, это
- 1:58:30значит ошибки не было. Если ошибка нел,
- 1:58:34значит, что-то произошло. И мы можем
- 1:58:35узнать даже причину ошибки, выведя
- 1:58:37сообщение о этой самой ошибке. Ну и
- 1:58:39давайте посмотрим, как всё работает,
- 1:58:40если у нас всё хорошо. Допустим, баланс
- 1:58:42у нас 100 долларов. Запускаем и видим,
- 1:58:44была произведена оплата, потому что мы
- 1:58:47узнали, что у нас хватает баланс.
- 1:58:49Списали баланс. Как мы видим, у нас
- 1:58:51списалось 50 долларов, было 100,
- 1:58:53списалось 50, осталось ещё 50. И была
- 1:58:55произведена оплата. То есть у нас ер был
- 1:58:57равен, и мы вывели то, что у нас была
- 1:59:00успешно произведена оплата. Хорошо.
- 1:59:02Теперь давайте представим случай, когда
- 1:59:04наша функция или наш метод должны
- 1:59:06вернуть помимо ошибки ещё какое-то
- 1:59:08значение. Ну как это вообще понимать?
- 1:59:11Смотрите, представи, у нас есть
- 1:59:14структура, какая-то машина, и у машины
- 1:59:17есть армор, то есть её какая-то
- 1:59:18прочность, представленная целым числом.
- 1:59:21Есть метод у этой машины, называется
- 1:59:23газ, то есть нажатие на газ. Каждый раз,
- 1:59:26когда мы нажимаем на газ, мы разгоняемся
- 1:59:28до какого-то случайного числа, до
- 1:59:30какого-то случайного количества
- 1:59:31километро в час. И мы хотим вернуть это
- 1:59:33самое количество километро/ч.
- 1:59:35В пакете Rant из библиотеки МАЗ есть
- 1:59:38хорошая функция, называется int n. Она
- 1:59:41возвращает случайное число от нуля до n.
- 1:59:43Ноль включительно, n не включительно.
- 1:59:46Ну, это есть наш случайный разгон. Вот у
- 1:59:48нас rand int n. Ну и пускай от нуля до
- 1:59:50150 мы можем разогнаться. возвращаем
- 1:59:54этот наш разгон. Но каждый раз, когда мы
- 1:59:57будем нажимать на газ, прочность нашей
- 1:59:59машины будет уменьшаться на 10 единиц.
- 2:00:03Если прочность становится равна нулю или
- 2:00:06отрицательная, значит, наша машина
- 2:00:07ломается. Ну хорошо, создаём нашу
- 2:00:10машину.
- 2:00:12Пускай у неё прочность будет 25. Выводим
- 2:00:15машину до.
- 2:00:19Выводим машину после.
- 2:00:23Ну и между до и после нажимаем на газ.
- 2:00:25И, конечно же, мы хотим получить наш
- 2:00:27разгон и вывести его на экран.
- 2:00:31[аплодисменты]
- 2:00:38Запускаем программу. Машина до армор 25.
- 2:00:42Разгоняемся. Получившийся разгон 34
- 2:00:44км/ч. Машина после. Вот 15 у нас
- 2:00:47осталось армора. Хорошо. А теперь
- 2:00:49представим, что мы хотим разогнаться три
- 2:00:52раза подряд. 1 2 3. Ну, так как
- 2:00:54километро/ч переменная уже определена,
- 2:00:56поэтому тут двоеточи не нужно. Мы просто
- 2:00:57перезаписываем значение в уже
- 2:00:59существующую переменную. Запускаем
- 2:01:02программу. И, как мы видим, мы
- 2:01:04разогнались сначала до 96, осталось 15
- 2:01:07армора. Разогнались до 29 де, осталось
- 2:01:09пять армора. Потом разогнались до 49, и
- 2:01:12армор ушёл в минус. То есть мы сломали
- 2:01:14нашу машину. Вообще-то ломать машину мы
- 2:01:16не хотели, как бы, да? То есть, если у
- 2:01:18нас вот она вот-вот сломается, мы бы не
- 2:01:20хотели её ломать, хотели бы
- 2:01:21аккуратненько отвезти в сервис
- 2:01:23техобслуживания, чтобы нам её обслужили
- 2:01:25и наша машина не ломалась. Но мы её
- 2:01:26взяли очередным бездумным вызовом метода
- 2:01:30газ сломали. Что бы мы могли предпринять
- 2:01:32для того, чтобы вот такие вот бездумные
- 2:01:34вызовы метода газ у нас не приводили к
- 2:01:36поломке машины? Ну, мы бы могли
- 2:01:37поставить условие, как бы, да, что если
- 2:01:39у нас армор -10 меньше либо равно нуля,
- 2:01:43то есть если мы при очередном нажатии на
- 2:01:45газ сломались бы, то мы просто вернули
- 2:01:48бы ноль, то есть мы бы не стали
- 2:01:49газовать, да, просто ритюр ноль. Всё, мы
- 2:01:51бы не стали разгоняться и не стали бы
- 2:01:53уменьшать армор. Просто типа стоим на
- 2:01:54месте 0 км/ч, потому что мы не хотим
- 2:01:56ломать нашу машину.
- 2:01:58Запускаем и смотрим, что у нас
- 2:02:01получившийся разгон ноль и армор п. Ну
- 2:02:03хорошо, мы не разогнались и сохранили
- 2:02:05нашу машину. Но с точки зрения людей,
- 2:02:07которые пользуются нашей структурой кар,
- 2:02:09которые газуют, они не понимают, почему,
- 2:02:11когда последний раз нажали газ, почему
- 2:02:13разгон ноль? Типа, а почему мы не
- 2:02:15разогнались? Как бы вроде армор остался
- 2:02:17ещё, мы не разогнались. Почему? Это
- 2:02:20очень странно, на самом деле. Нам бы, на
- 2:02:22самом деле, хотелось бы как-то
- 2:02:23сигнализировать людям, которые газуют,
- 2:02:26почему мы вдруг не газанули. Для этого
- 2:02:29мы помимо того, что возвращаем
- 2:02:31количество километро/ч, на которое мы
- 2:02:32разогнались после очередного нажатия на
- 2:02:34газ, мы можем дополнительно возвращать
- 2:02:37ошибку, если она у нас будет возникать.
- 2:02:39Как это выглядит? Через запятую пишем
- 2:02:42возвращаемую ошибку и заключаем
- 2:02:45возвращаемые значения в круглые скобки.
- 2:02:48Мы в гонге из функций и из методов можем
- 2:02:51возвращать сколько угодно значений. Не
- 2:02:53одно, а 0, 1 и сколько угодно. То есть
- 2:02:56тут можем ещё какой-нибудь стринг
- 2:02:57возвращать, какой-нибудь бул ещё
- 2:02:59возвращать и так далее. Можем, да хоть
- 2:03:0110 их тут написать. Ну, неограниченное
- 2:03:02количество. Единственное, что необходимо
- 2:03:05руководствоваться здравым смыслом и
- 2:03:07всё-таки думать о том, что если мы будем
- 2:03:08возвращать слишком много разных
- 2:03:10значений, это может запутать. Придётся
- 2:03:12опять смотреть внутрь функции, понимать,
- 2:03:13что именно каждое возвращаемое значение
- 2:03:15означает. Поэтому обычно принято не
- 2:03:18больше двух. Либо просто ошибку, если
- 2:03:21нам нужна только ошибка, если она
- 2:03:22возникает, как у нас было в случае с
- 2:03:23оплатой. То есть мы просто хотим
- 2:03:25оплатить. Если какая-то ошибка, мы хотим
- 2:03:27об этом узнать. Если ошибки нету, ну и
- 2:03:29замечательно. Либо два возвращаемых
- 2:03:31значения, как в случае с функцией газ,
- 2:03:33что если у нас нет ошибок, то возвращаем
- 2:03:35количество километро/ч, на которой мы
- 2:03:36разогнались. Если есть ошибки,
- 2:03:38возвращаем ошибку. Как это выглядит?
- 2:03:40Если мы возвращаем два значения, то и
- 2:03:41после Ритюрна должны писать два
- 2:03:43значения. Если у нас ошибка, если мы бы
- 2:03:46сломались, то возвращаем 0 км/ч разгон и
- 2:03:51новую нашу ошибку, в которую мы
- 2:03:52передаём. Мы не стали газовать, чтобы не
- 2:03:56сломать машину. Всё. А если у нас всё
- 2:03:59хорошо и мы газанули, не сломали машину,
- 2:04:02тогда на место ошибки возвращаем нил. То
- 2:04:04есть в каком порядке мы объявили
- 2:04:06возвращаемые значения, в таком порядке
- 2:04:08мы их и должны, собственно, возвращать.
- 2:04:09Если у нас какая-то ошибка была, мы
- 2:04:11возвращаем обычно значение по умолчанию
- 2:04:14и нашу ошибку. Если ошибки не было,
- 2:04:16возвращаем наше значение, а вместо
- 2:04:18ошибки пишем newл. Как это выглядит с
- 2:04:20точки зрения потребителя нашей функции?
- 2:04:21Человек нажимает на газ и получает два
- 2:04:23значения. Первое - это километ/ч, на
- 2:04:25которые он разогнался. Второе - это
- 2:04:27возможная ошибка. После этого сразу же
- 2:04:28проверка. Если ошибка не равна, это
- 2:04:31значит, что у нас была ошибка какая-то.
- 2:04:34В таком случае
- 2:04:37ошибка нажатия на газ.
- 2:04:41И вот через два иточие можем вывести, в
- 2:04:42чём, собственно, была ошибка. Y. Вернём
- 2:04:46вот это вот сообщение, которое мы туда
- 2:04:47передали. Если ошибки не было, тогда
- 2:04:49выводим получившийся разгон и машина
- 2:04:52после нашего разгона. В принципе, машину
- 2:04:55после разгона можем всегда выводить. Ну
- 2:04:56и так несколько раз можем повторить два
- 2:04:59и три.
- 2:05:06Ну, хочу ещё видеть каждый раз кардо и
- 2:05:09буду выводить ещё пустую строчку, чтобы
- 2:05:10разделять наши выводы.
- 2:05:14Запускаю программу
- 2:05:16и вижу изначально кардо у нас 25 армора,
- 2:05:20потом газанули, стало 15. Вот такой вот
- 2:05:22разгон 14 км/ч. Потом было 15, газанули,
- 2:05:25стало пять. Получившийся разгон вон 139
- 2:05:27км/ч аж разогнались. Потом третий раз мы
- 2:05:30нажали на газ, у нас армор не
- 2:05:31уменьчился, и нам выявилась ошибка
- 2:05:33нажатия на газ. Мы не стали газовать,
- 2:05:34чтобы не словать машину. Разгон ноль.
- 2:05:37Всё. То есть мы на очередной раз
- 2:05:39увидели, что у нас не равно, что у нас
- 2:05:41вернулась ошибка, и мы её вывели. И мы
- 2:05:43поняли, почему же у нас разгон ноль и
- 2:05:46почему у нас не изменился армор. Мы
- 2:05:48узнали, что у нас возникла ошибка и мы
- 2:05:49не стали газовать. Ну, в принципе, для
- 2:05:51красоты это всё можно в цикл обернуть.
- 2:05:52Давайте вот в бесконечный цикл это всё
- 2:05:54обернём. То есть цикл без всяких условий
- 2:05:56- это вот бесконечный цикл. В него
- 2:05:58только одну итерацию можно записать, это
- 2:06:00всё стереть.
- 2:06:03Ну и мы на каждой итерации будем
- 2:06:04выводить сначала машину до, потом
- 2:06:05пытаться газануть, выводить машину
- 2:06:07после. Если у нас какая-то ошибка
- 2:06:09возникла, мы выведем ошибку нажатия на
- 2:06:11газ и сразу же завершим наш цикл. Ну
- 2:06:13потому что иначе у нас постоянно будет
- 2:06:15вызываться функция газ, постоянно будет
- 2:06:17говориться, что мы не стали газовать, и
- 2:06:19так до бесконечности у нас будет
- 2:06:20выводиться ошибка нажатия на газ. То
- 2:06:22есть, если у нас один раз произошла
- 2:06:23ошибка нажатия на газ, то всё, мы
- 2:06:24завершаем наш цикл, иначе выведем
- 2:06:27получившийся разгон и пустую строчку.
- 2:06:29Ну, давайте запустим нашу программу.
- 2:06:32Вот мы видим, первый раз нажали на газ,
- 2:06:34разогнались, уменьшили армор. Второй раз
- 2:06:36нажали на газ, разогнались, уменьшили
- 2:06:37армор. Третий раз нажали на газ, армор
- 2:06:40не уменьшился. Почему? Нам сказали, у
- 2:06:42нас ошибка, мы не стали газовать. Всё
- 2:06:44отлично. И теперь мы в нашем методе газ
- 2:06:47мало того, что предостерегли
- 2:06:49пользователя нашей машиной от поломки,
- 2:06:51что он не сможет газануть, если машина
- 2:06:53сломается, так мы ещё ему и сообщаем,
- 2:06:56если вдруг у нас какая-то ошибка, мы ему
- 2:06:58сообщаем, почему именно мы не стали
- 2:07:00газовать. Ну а если всё хорошо, мы
- 2:07:01возвращаем Нил. Никаких ошибок нету. По
- 2:07:03сути, повторюсь, мы бы тут могли
- 2:07:05возвращать строку
- 2:07:07и вот таким вот образом бы обойтись, но
- 2:07:09в таком случае стало бы непонятно, когда
- 2:07:12мы вызываем метод газ, что это за
- 2:07:14строка. Может быть, в этой строке было
- 2:07:16бы закодировано там, например, наша
- 2:07:17позиция в рейтинге или какой-нибудь
- 2:07:20положительный комментарий в нашу
- 2:07:21сторону, что-нибудь такое, мы бы не
- 2:07:23понимали. А если у нас тут возвращается
- 2:07:25интерфейс Erorр, то мы однозначно знаем,
- 2:07:27что вот это вот второе возвращаемое
- 2:07:29значение - это именно индикатор ошибки.
- 2:07:31Поэтому в качестве индикатора ошибки
- 2:07:32всегда используем интерфейс Eror. Это
- 2:07:34поможет и вам в будущем разобраться в
- 2:07:36вашем старом коде, и вашим коллегам
- 2:07:38будет сразу же понятнее, что именно вы
- 2:07:40хотели вот этим вот вторым возвращаемым
- 2:07:42значением сказать, а именно, что тут
- 2:07:44может быть какая-то ошибка при вызове
- 2:07:46метода газ. И эту ошибку нужно
- 2:07:48обрабатывать, смотреть, не равна ли она
- 2:07:50случайно Нил.
- 2:07:52[музыка]
- 2:07:56Также один из видов ошибок при
- 2:07:58выполнении нашей программы - это паника.
- 2:08:00Паника немножко отличается от тех
- 2:08:02ошибок, что мы прошли следующим образом.
- 2:08:04Когда мы с вами получаем какую-то ошибку
- 2:08:06во время вызова метода, обычно мы эту
- 2:08:09ошибку ожидаем. Это вполне в рамках
- 2:08:11нашей программы. Мы эту ошибку можем
- 2:08:13обработать, мы можем что-то с ней
- 2:08:14сделать, например, отправить нашу машину
- 2:08:16там на диагностику, отправить её в
- 2:08:18сервисный центр, ну и так далее. Есть
- 2:08:20другой вид ошибок - это паники. Чем они
- 2:08:23отличаются? паники у нас вызываются в
- 2:08:25программе, когда происходят такие
- 2:08:27нарушения в работе, которые мы уже
- 2:08:29совсем никак не можем исправить. И
- 2:08:31обычно они уже связаны не с какой-то
- 2:08:33нашей логикой приложения, а с
- 2:08:35техническим выполнением самой программы.
- 2:08:37Ну, к примеру, у нас где-то есть
- 2:08:38переменная А, она равна нулю. Потом мы
- 2:08:41хотим создать переменную B, и она будет
- 2:08:43равна 1/ А. Ну и потом вывести это B на
- 2:08:46экран.
- 2:08:48B. Вот наше B. Казалось бы, что может
- 2:08:50пойти не так? Запускаем нашу программу и
- 2:08:53видим панику. Что произошло? Нам
- 2:08:56говорят, что в сорок шестой строчке
- 2:08:58произошло деление на ноль. А деть на
- 2:09:02ноль мы не можем. И язык
- 2:09:03программирования тоже не знает, что
- 2:09:05такое поделить на ноль. То есть тут
- 2:09:07произошла такая ошибка, которую ты уже
- 2:09:09никак не обработаешь. Ну что ты
- 2:09:11сделаешь? Тебе именно программу нужно
- 2:09:12переписывать, чтобы у тебя не
- 2:09:14происходило деление на ноль. там менять
- 2:09:16значение переменной или какие-то
- 2:09:17проверки проводить, что типа если а не
- 2:09:20равно нулю, только тогда
- 2:09:24находить значение 1 по а. Ну и так
- 2:09:27далее. То есть паника означает, что нам
- 2:09:29нужно не просто ошибочку обработать, а
- 2:09:32то, что нам прямо нужно наш код
- 2:09:33переписать, что он как-то в корне
- 2:09:35неверно написан. Поэтому ошибочки,
- 2:09:37которые у нас в интерфейсике Erроorр
- 2:09:39возвращаются, мы как-то на уровне кода
- 2:09:41можем обработать, что-то с ними сделать.
- 2:09:43А вот паники, они сигнализируют о том,
- 2:09:45что наша программа написана в принципе
- 2:09:48неверно, что ошибка в написании кода. Ну
- 2:09:50и поэтому наша программа просто сразу же
- 2:09:53завершается. То есть если мы какую-то
- 2:09:54ошибочку не обработаем, ну ничего не
- 2:09:56произойдёт, мы просто потеряем это
- 2:09:57значение. Если мы панику не
- 2:09:59обрабатываем, то у нас программа просто
- 2:10:02вылетает и завершается. И пишется нам,
- 2:10:04что у нас была паника. Паника может
- 2:10:06вызываться делением на ноль. Или,
- 2:10:07например, у нас есть какой-нибудь слайс
- 2:10:11целочисленный, там 1 2 3 в нём лежит, а
- 2:10:14мы захотим вывести на экран слайс с
- 2:10:17индексом четыре, да, то есть 0 1 2. Мы
- 2:10:21вот тут вот три пропустили и вот вот
- 2:10:23этот вот вообще элемент хотим вывести.
- 2:10:25Что будет? Тоже будет паника. То есть мы
- 2:10:28вообще хотим обратиться к элементу в
- 2:10:29слайсе, которого нету. Тут уже опять
- 2:10:32ошибка написания нашей программы, что мы
- 2:10:34допустили возможность обращения к
- 2:10:36памяти, которая нам не принадлежит. Тоже
- 2:10:39вызывается паника и пишется, что мы
- 2:10:41попытались обратиться к индексу четыре.
- 2:10:43При том, что у нас, в принципе, длина
- 2:10:45нашего слайса три. Опять же нам паника
- 2:10:47тут сигнализирует о том, что мы
- 2:10:49допустили в написании нашего кода
- 2:10:50какую-то критическую ошибку, которую
- 2:10:52нужно исправлять. Но, несмотря на то,
- 2:10:55что паники сигнализируют о том, что мы
- 2:10:56допустили ошибку, которую необходимо
- 2:10:58срочно исправлять, и эта ошибка на
- 2:11:00уровне логики всей программы, что мы не
- 2:11:03просто там, например, у нас закончился
- 2:11:05армор в машинке, да, и мы как-то это
- 2:11:06можем обработать, нет. Грубое нарушение
- 2:11:09произошло, которое заставило программу
- 2:11:10аварийно завершиться. Несмотря на это,
- 2:11:13Goldeng предоставляет возможности
- 2:11:14обрабатывать такие грубые нарушения.
- 2:11:17Выглядит это следующим образом. Мы в
- 2:11:19самом начале функции, где у нас может
- 2:11:21возникнуть паника, пишем def, анонимная
- 2:11:24функция. Ну, то есть самый обычный наш
- 2:11:26фёр вызываем. И здесь мы ловим нашу
- 2:11:29панику. Я её положу в переменную P, то
- 2:11:31есть паника, и пишу recver. Потом
- 2:11:35проверяю, если P не равно,
- 2:11:38тогда вывожу на экран. Была паника.
- 2:11:42[аплодисменты]
- 2:11:44Выведу панику. Всё. Иначе я ничего
- 2:11:47выводить не буду. Вот таким вот образом,
- 2:11:49таким отложенным вызовом анонимной
- 2:11:50функции через встроенную функцию Recover
- 2:11:53я буду отлавливать панику. То есть
- 2:11:54Recover у нас возвращает нам панику,
- 2:11:57если она была. Если не было паники, нам
- 2:11:59вернётся Нил, вот эта проверка не
- 2:12:01пройдёт, ничего не произойдёт. Но если
- 2:12:03паника была, то она будет не Нил, и мы
- 2:12:05её выведем на экран. Давайте запустим
- 2:12:07нашу программу. И мы видим, была паника.
- 2:12:10И мы вывели сообщение нашей панике. То
- 2:12:12есть мы смогли вот эту грубую ошибку
- 2:12:14отловить и вывести на экран. Для чего же
- 2:12:16нам вообще нужен вот этот инструмент
- 2:12:18Recovery, если мы необрабатываемые
- 2:12:20грубые ошибки должны исправлять сразу же
- 2:12:22прямо внутри кода? Потому что когда мы с
- 2:12:25вами разрабатываем большущие приложения,
- 2:12:27которые используются уже в продакшене,
- 2:12:29продакшн - это среда выполнения
- 2:12:32программ, с которой взаимодействуют
- 2:12:33реальные пользователи, то есть вот,
- 2:12:34допустим, Яндекстаси у нас огроменное
- 2:12:36приложение, и мы запускаем это
- 2:12:38приложение на каком-то удалённом
- 2:12:39сервере. Вот этот удалённый сервер, где
- 2:12:41запущено Яндекс- Такси, с которым
- 2:12:43взаимодействуют реальные пользователи,
- 2:12:44это называется Prodдаction. Так вот,
- 2:12:46если мы при написании нашего кода
- 2:12:48случайно где-то допустили ошибку,
- 2:12:50которая ведёт к панике, и как-то её
- 2:12:52упустили, вот не заметили, она у нас там
- 2:12:54только при каких-то определённых сложных
- 2:12:56ситуациях возникает. И вдруг это наше
- 2:12:58приложение в продакшене кинет панику.
- 2:13:01Так у нас всё Яндекста такси упадёт
- 2:13:02тогда. Мы этого бы не хотели. Мы бы на
- 2:13:05самом деле хотели просто эту панику
- 2:13:06поймать, ну и куда-нибудь там вывести,
- 2:13:08там в какой-нибудь файлик записать,
- 2:13:09чтобы потом посмотреть, какие у нас
- 2:13:11паники были и чтобы их поправить. Именно
- 2:13:13для этого существует инструмент поимки
- 2:13:15паник. Просто для того, чтобы, если уж
- 2:13:17грубая ошибкай была допущена, чтобы всё
- 2:13:20приложение у нас не завершилось. И тут
- 2:13:22уже наша задача, как программистов,
- 2:13:23понимать, где нам нужно, чтобы грубые
- 2:13:25ошибки приводили к тому, что программа
- 2:13:27завершается экстренно и мы как-то
- 2:13:29исправляем наш код. А где эти грубые
- 2:13:31ошибки? Мы просто хотим, если что,
- 2:13:33вывести там в какой-нибудь файлик или
- 2:13:35напечатать в консоль, как мы вот здесь
- 2:13:37это сделали, а потом уже их задним
- 2:13:38числом как-нибудь пофиксить, чтобы наши
- 2:13:41случайные неожиданные паники экстренно
- 2:13:43не завершали нам целое Яндекстаси, да,
- 2:13:45условное. Просто нужно понимать, когда
- 2:13:47это лучше использовать, когда нет. Но
- 2:13:49паника, повторюсь, сигнализирует нам
- 2:13:50именно о грубых ошибках самого кода.
- 2:13:53Когда мы хотим обрабатывать ошибочки,
- 2:13:55которые мы ожидаем, ну вот мы ожидаем,
- 2:13:57что рано или поздно наш армор может
- 2:13:58стать нулевым или меньше нуля, мы это
- 2:14:01ожидаем и просто возвращаем ошибочку,
- 2:14:03которую в коде можем следующим образом
- 2:14:05обработать. Паники же - это грубые
- 2:14:06нарушения, которые мы обычно не
- 2:14:08предвидим, но так или иначе они могут
- 2:14:10случаться. И если мы не хотим, чтобы
- 2:14:12из-за этих грубых нарушений, которые
- 2:14:14вообще никак нормально не обработать, мы
- 2:14:16не хотим, чтобы наша программа экстренно
- 2:14:18завершалась и падала, мы можем через def
- 2:14:20recover отлавливать эту грубую ошибку.
- 2:14:23Ну и выводить её на экран, чтобы потом
- 2:14:25попозже зайти в выводы нашего
- 2:14:27приложения, посмотреть, какие паники
- 2:14:28были, и пофиксить их. Таким образом, мы
- 2:14:30узнали о том, что есть красивый способ
- 2:14:33обрабатывать ошибки в go. Не надо ничего
- 2:14:35придумывать, просто используем
- 2:14:36встроенный интерфейс Error, который был
- 2:14:38написан разработчиками голенга. Можем
- 2:14:40туда передавать новые ошибочки и потом
- 2:14:42выводить их текст на экран. Также мы
- 2:14:44можем из функции возвращать несколько
- 2:14:45значений, в том числе какое-то значение
- 2:14:47с ошибкой, если нам нужно получить
- 2:14:50какой-то результат выполнения функции,
- 2:14:51например, наш разгон. Но в случае ошибки
- 2:14:53мы хотим знать о том, что ошибка была
- 2:14:55совершена. Если ошибки не было, то
- 2:14:57возвращаем л. Также в нашей программе
- 2:14:59могут возникать грубые ошибки, которые
- 2:15:01свидетельствуют о том, что в принципе
- 2:15:03наш код написан коряво и это нужно
- 2:15:05исправлять. Такие ошибки кидают панику.
- 2:15:08Паника без дополнительной обработки в
- 2:15:10виде de Recover у нас ломает наше
- 2:15:12приложение. Наше приложение падает,
- 2:15:14экстренно завершается и выводится наше
- 2:15:16сообщение паники, почему она случилась.
- 2:15:18Но если мы не хотим, чтобы наше
- 2:15:20приложение из-за паники падало, можем
- 2:15:23добавить обработку в виде Defer Recover.
- 2:15:26[музыка]
- 2:15:30Следующая наша тема - это рутины и
- 2:15:33каналы. Посмотрим, что это вообще такое,
- 2:15:35для чего нужно и как используется. Тема,
- 2:15:37сразу скажу, одна из самых сложных,
- 2:15:39особенно для тех, кто никогда не
- 2:15:40знакомился с таким понятием, как
- 2:15:42конкурентное, параллельное выполнение
- 2:15:44программы. Я постараюсь объяснить всё
- 2:15:46максимально просто, но сразу готовьтесь
- 2:15:47к тому, что будет ничего непонятно и
- 2:15:49придётся репу почесать. Ну, начнём с
- 2:15:51небольшой теории. У нас есть в программе
- 2:15:54какой-то один поток выполнения. Что я
- 2:15:56имею в виду? У нас программа всегда
- 2:15:58выполняется сверху вниз, слева направо.
- 2:16:00То есть, если мы выводим тут по очереди
- 2:16:02print ln1, println2, println3, ну, я
- 2:16:05думаю, очевидно, что у нас просто
- 2:16:06программа выполнится сверху вниз.
- 2:16:08Давайте запустим.
- 2:16:10Ну и видим 1 2 3. Если у нас будет
- 2:16:11какая-то функция, которая будет
- 2:16:13выводить, к примеру, 4 5 6, и мы её
- 2:16:16вызовем вот тут вот, к примеру, после
- 2:16:17единички, ну, мы увидим, что у нас
- 2:16:19сначала выведется единичка, потом будет
- 2:16:20вызов функции 4 5 6. Функция завершится
- 2:16:242 3. Запускаем. 1 4 5 6 2 3. Ну, в
- 2:16:28принципе, всё, как мы ожидаем.
- 2:16:29Выполнение программы идёт сверху вниз у
- 2:16:31нас, начиная с функции main. Слева
- 2:16:33направо у нас всё выполняется. Просто
- 2:16:35как книжку читаем. Слева направо, сверху
- 2:16:37вниз. Хоп. Вроде всё круто, всё понятно,
- 2:16:40и это нам даёт уверенность в том, что в
- 2:16:42один момент времени у нас выполняется
- 2:16:44лишь одна функция, лишь одна строчка
- 2:16:46кода. У нас выполнение программы идёт
- 2:16:48последовательно. У нас один условный
- 2:16:50рабочий, то есть один поток выполнения
- 2:16:52главный, но он по сути единственный, и
- 2:16:54он выполняет нашу программу.
- 2:16:56Соответственно, у нас могут быть
- 2:16:56какие-то там условные ветвления,
- 2:16:58какие-нибудь циклы, у нас могут быть там
- 2:17:00вызовы функций, но в итоге всё равно у
- 2:17:03нас код будет выполняться в одном потоке
- 2:17:06выполнения сверху вниз. Неважно, там
- 2:17:08даже пускай будет условное ветвление,
- 2:17:10циклы какие-то, вызовы функций, всё
- 2:17:12равно у нас будет один поток исполнения,
- 2:17:14который будет нашу программу выполнять.
- 2:17:17И это, с одной стороны, круто, то, что
- 2:17:18мы можем проследить выполнение нашей
- 2:17:20программы, сверху вниз по ней пройтись,
- 2:17:22понять, что вообще в ней происходит,
- 2:17:23понять, что и в какой последовательности
- 2:17:25будет вызвано. Но у такого подхода есть
- 2:17:28некоторые недостатки. В чём они
- 2:17:30заключаются? То, что у нас один всего
- 2:17:32лишь поток исполнения, один какой-то
- 2:17:34рабочий, да, вот этот главный поток, и
- 2:17:36только он выполняет нашу программу. Ну,
- 2:17:38давайте посмотрим, в чём может быть
- 2:17:40проблема. Разберём такой довольно
- 2:17:41популярный пример, который призван
- 2:17:43объяснить вообще для чего нам нужны
- 2:17:44рутины и что это такое. Пример на
- 2:17:46обычных рабочих. То есть представим, что
- 2:17:48у нас есть вот этот главный поток
- 2:17:50выполнения, что это не просто какой-то
- 2:17:51поток, а что это рабочий шахтёр, который
- 2:17:54может ходить в шахту, добывать уголь.
- 2:17:55Соответственно, опишем функцию, назову
- 2:17:57её майн, то есть
- 2:18:00функция,
- 2:18:01описывающая
- 2:18:03поход рабочего в шахту.
- 2:18:07Вот наша функция. Она будет принимать,
- 2:18:09принимает
- 2:18:11номер похода в шахту, там первый поход в
- 2:18:13шахту, второй, третий, и возвращает
- 2:18:16добытый уголь. Ну, добытый уголь у нас
- 2:18:18будет представлен целочисленным
- 2:18:19форматом. Просто там сколько-то единиц
- 2:18:21угля, соответственно, принимает номер
- 2:18:22похода в шахту. Ну, я назову это ID,
- 2:18:24можно, в принципе, назвать как угодно,
- 2:18:26там N, number, count, что-нибудь такое.
- 2:18:30Ну, давайте вот, в принципе, оставлю.
- 2:18:31Вот наша функция, которая описывает
- 2:18:33поход рабочего шахту. В чём будет
- 2:18:34заключаться поход рабочего шахту? Ну,
- 2:18:36сначала он будет говорить о том, что у
- 2:18:37нас
- 2:18:38поход в шахту номер N начался. Хорошо.
- 2:18:43Потом мы будем имитировать какую-то
- 2:18:45деятельность, имитировать реальный поход
- 2:18:47в шахту, чтобы у нас функция моментально
- 2:18:49не завершалась, а чтобы она
- 2:18:50действительно выполнялась какое-то время
- 2:18:52продолжительное. Для этого мы можем
- 2:18:53воспользоваться пакетом Time
- 2:18:55стандартным. В нём есть функция сп. Что
- 2:18:58она делает? Она, если навести на неё,
- 2:19:00посмотреть описание, она ставит на паузу
- 2:19:02выполнение нашего потока. То есть наш
- 2:19:04вот этот главный единственный поток
- 2:19:06выполнения будет при попадании в шахту
- 2:19:09задерживаться там на секунду.
- 2:19:13Таким образом, мы говорим, что
- 2:19:14задержаться нужно на одну секунду. Ну,
- 2:19:15чтобы сымитировать там какое-то
- 2:19:17пополнение, какой-то сложный алгоритм,
- 2:19:19да? То есть, ну, там в шахту сходить,
- 2:19:20это же не по щелчку пальцев, какое-то
- 2:19:22время занимает. Но для нашего тестового
- 2:19:24примерчика мы вот поставим одну секунду.
- 2:19:25После этого наш шахтёр говорит, что
- 2:19:28поход в шахту номер такой-то закончился.
- 2:19:33Ну и в принципе всё. Мы возвращаем наш
- 2:19:34добытый уголь. Вот типа 10 уголька добыл
- 2:19:36за один поход в шахту. Давайте мы
- 2:19:38воспользуемся этими нашими походами в
- 2:19:40шахту. Я создам переменну кол. Это с
- 2:19:43английского переводится как уголь. И
- 2:19:44буду в неё складывать наш добытый уголь.
- 2:19:46К этой переменной я просто буду
- 2:19:48добавлять результаты выполнения функции
- 2:19:49май. Ну вот первый поход, второй поход,
- 2:19:52третий поход. Каждый раз мы будем
- 2:19:54выполнять функцию MIN. Она будет
- 2:19:55выполняться одну секунду и возвращать
- 2:19:57нам 10 угля. И мы вот этот результат
- 2:19:58выполнения этой функции будем прибавлять
- 2:20:00к переменной кол. Соответственно, три
- 2:20:01раза сходили в шахту. У нас 30 угля
- 2:20:03должно быть. В конце выведем, сколько
- 2:20:05угля мы добыли.
- 2:20:07Добыли
- 2:20:08кол угля. Там добыли столько-то угля.
- 2:20:12Плюс я бы хотел сразу же замерить время
- 2:20:15выполнения вот этих вот трёх походов в
- 2:20:17шахту. Как бы понятно, что оно там будет
- 2:20:19у нас 3 секунды в сумме, 1 секунда, 2
- 2:20:21секунда, 3 секунды, но хотелось бы это
- 2:20:23дополнительно практическим методом
- 2:20:24закрепить и прямо внутри программы
- 2:20:26замерить его время выполнения. Для этого
- 2:20:28воспользуюсь стандартным приёмом. До
- 2:20:30начала кода, который мы хотим замерить,
- 2:20:33создам переменную init time. В неё
- 2:20:35запишу time now. Что это значит? Это
- 2:20:37значит, что я записал в переменную in
- 2:20:39time время, которое было на момент
- 2:20:42выполнения двадцать четвёртой строчки.
- 2:20:43То есть, сколько там вот миллисекунд на
- 2:20:45секунду было, столько я запомнил. После
- 2:20:47этого я воспользуюсь следующей
- 2:20:49конструкцией.
- 2:20:51Прошло времени time, since и time то
- 2:20:55есть. Я запомнил время, которое было до
- 2:20:57начала выполнения наших походов в шахту,
- 2:20:59а потом воспользовался функцией Time Sen
- 2:21:02для того, чтобы узнать, сколько прошло
- 2:21:03времени с момента, как я его запомнил.
- 2:21:06Такой конструкции я посчитаю, сколько
- 2:21:08времени прошло между начальным временем
- 2:21:10и между просчётом разницы во времени.
- 2:21:13Давайте запустим нашу программу,
- 2:21:14посмотрим, что получилось. Поход в шахту
- 2:21:16номер один начался. Поход в шахту номер
- 2:21:18один закончился. Поход в шахту номер два
- 2:21:19начался. Поход в шахту номер два
- 2:21:21закончился. Номер три начался. Номер три
- 2:21:22закончился. Добыто 30 угля. Ну, в
- 2:21:24принципе, понятно, да, что типа три раза
- 2:21:26по 10 угля мы добыли и времени прошло 3
- 2:21:28секунды. О'кей. То есть у нас каждый
- 2:21:30поход в шахту занимал 1 секунду, и в
- 2:21:32сумме у нас было затрачено на выполнение
- 2:21:34программы 3 секунды. Ну вот этот
- 2:21:35хвостик, это там на всякие
- 2:21:36дополнительные операции, там посчитать
- 2:21:37сумму, вывести на экран, там узнать
- 2:21:39текущее время, ну, и так далее. Вот этот
- 2:21:41вот хвостик. То есть вот 3 секунды мы с
- 2:21:42вами три раза ходили в нашу шахту.
- 2:21:45Давайте ещё раз запустим программу и
- 2:21:46действительно посмотрим, что она
- 2:21:47выполняется не моментально, а по
- 2:21:48чуть-чуть. Какая-то часть выполнилась,
- 2:21:50ещё часть выполнилась, ещё часть
- 2:21:51выполнилась. То есть наша программа
- 2:21:52действительно какое-то время занимает.
- 2:21:54Ну и казалось бы, всё хорошо, типа,
- 2:21:55ладно, 3 секунды ждём, о'кей, а теперь
- 2:21:56представим, что нам нужно было бы
- 2:21:57сходить в шахту не три раза, а,
- 2:21:59например, 100 раз. Это уже 100 секунд. А
- 2:22:01теперь представим, что каждый поход в
- 2:22:02шахту бы занимал у нас не одну секунду,
- 2:22:04а 10 секунд. То есть это у нас ещё в 10
- 2:22:06раз увеличивается сложность выполнения
- 2:22:07нашей программы, её длительность. В
- 2:22:09итоге мы уже 1.000 секунд с вами
- 2:22:10ожидаем. 1.000 секунд - это уже, на
- 2:22:12самом деле, довольно серьёзно. То есть,
- 2:22:13ну, 1.000 секунд ждать
- 2:22:15как-то не круто, да. Ну, у нас один
- 2:22:17шахтёр, он вот у нас ходит постоянно в
- 2:22:20шахту. Ну и выглядит это примерно
- 2:22:21следующим образом. То есть вот наш
- 2:22:22шахтёр первый раз пошёл в шахту, да, вот
- 2:22:24на вот этой вот строчке и целую секунду
- 2:22:27он там задержался, оп, плюс 10 угля.
- 2:22:29Второй раз пошёл в шахту после этого ещё
- 2:22:32на секунду задержался, ещё плюс 10 угля.
- 2:22:34Третий раз пошёл в шахту, ещё на секунду
- 2:22:35задержался, ещё плюс 10 угля. В сумме 3
- 2:22:37секунды выполнялись, 30 угля заработали.
- 2:22:39Вроде бы о'кей, но повторюсь, как-то,
- 2:22:41ну, не совсем оптимально, особенно если
- 2:22:43представить, что у нас этих походов в
- 2:22:44шахту может быть очень много. Ну и тогда
- 2:22:46бы мы, в принципе, на очень долго могли
- 2:22:48с вами застрять бы на этих волшебных
- 2:22:50походах в шахту. То есть действительно,
- 2:22:51смотрим, уже оченьочень долго
- 2:22:53становится. У нас там у нас может быть
- 2:22:55100 походов в шахту, а каждый поход в
- 2:22:56шахту может ещё и занимать гораздо
- 2:22:58больше времени, чем 1 секунда. Там может
- 2:23:00быть и час, и 3 часа. Ну и мы очень
- 2:23:03много начинаем терять времени на том,
- 2:23:04что у нас всего лишь один рабочий. То
- 2:23:06есть было бы круто, если бы мы могли
- 2:23:08привлечь к работе ещё других шахтёров, и
- 2:23:10они бы ходили в шахту одновременно. Ну,
- 2:23:12параллельно. То есть не один челик
- 2:23:14бегает в шахту туда-сюда, а сразу же
- 2:23:15запрягает троих. Эти трое каждый по
- 2:23:18секунде добывает 10 угля. И в итоге мы
- 2:23:20за одну секунду сразу же 30 угля
- 2:23:22добываем, потому что они параллельно
- 2:23:23выполняют свою работу. Ну вот этих
- 2:23:25рабочих надо как-то контролировать.
- 2:23:26Нужно их работу синхронизировать.
- 2:23:28Поэтому у нас вот наш главный поток
- 2:23:29выполнения освобождается от работы. Он
- 2:23:31просто передаёт эту работу, делегирует
- 2:23:33её нанятым шахтёрам, а сам будет
- 2:23:35собирать уголь, который они ему
- 2:23:36приносят, и синхронизировать их работу.
- 2:23:38Ну, хорошо, каким образом мы с вами
- 2:23:40можем запустить вот этих вот трёх
- 2:23:42шахтёров? Ну, вообще, в Голенге для
- 2:23:44этого есть ключевое слово го. Давайте
- 2:23:46его напишем:
- 2:23:48Вот этой записью мы с вами запустили
- 2:23:50первого шахтёра. У нас был наш главный
- 2:23:52поток выполнения. Вот он. И мы вот одной
- 2:23:55вот этой вот записью го запустили
- 2:23:57шахтёра. Также можем написать тут го и
- 2:24:00тут го. Таким образом, мы запустим сразу
- 2:24:03трёх шахтёров. У нас будет наш главный
- 2:24:05поток выполнения, и он запустит сразу
- 2:24:07трёх шахтёров. Ну, давайте посмотрим,
- 2:24:09что у нас вообще произойдёт, а потом
- 2:24:10поймём, почему же всё пошло не так, как
- 2:24:12мы хотели. Сохраняем программу,
- 2:24:14запускаем
- 2:24:16и видим, добыли ноль угля. Что-то
- 2:24:19какой-то первый поход в шахту начался,
- 2:24:21второй поход в шахту начался, третий
- 2:24:22поход в шахту даже не начался. Прошло он
- 2:24:25100 микросекунд. Как бы, ну, довольно
- 2:24:27быстро, да, вроде круто, но добыли мы
- 2:24:28ноль угля, вообще ничего не добыли.
- 2:24:30Почему же мы запустили с вами трёх
- 2:24:32рабочих, но в итоге не добыли угля?
- 2:24:34Потому что мы не синхронизировали их
- 2:24:36работу. Что на самом деле произошло в
- 2:24:38нашей программе? Вот он наш главный
- 2:24:40поток выполнения, наш, так сказать,
- 2:24:41главный рабочий, наш прораб. Он запустил
- 2:24:44первого рабочего, послал его в шахту.
- 2:24:46Вот первый рабочий, пошёл в шахту, он
- 2:24:48там, ну, собирает инструменты, начинает
- 2:24:50готовиться к работе. Запустил второго
- 2:24:52рабочего в шахту. Вот первого рабочего
- 2:24:54мы запускаем. Второго рабочего мы
- 2:24:55запускаем, да? Вот он. Го 2, Mind 3. Вот
- 2:24:59запускаем третьего рабочего. Он там тоже
- 2:25:01начинает готовить инструменты, там
- 2:25:02что-то готовится вот уже к выполнению.
- 2:25:05Первый и второй уже даже успели вот
- 2:25:07первый и второй уже даже успели
- 2:25:08приступить к шахте, но они даже не
- 2:25:10успели выкопать нам хоть сколько-нибудь
- 2:25:12угля, и сразу же завершилась программа.
- 2:25:14Почему так случилось? Потому что золотое
- 2:25:16правило гонга, когда у нас завершается
- 2:25:18главный поток выполнения программы,
- 2:25:20тогда завершаются все остальные потоки,
- 2:25:23все рабочие дополнительные. Всё, что мы
- 2:25:24запустили через вот этот вот синтаксис
- 2:25:26GO после этого названия функции, всё,
- 2:25:28что мы запустили, оно вместе с главным
- 2:25:30потоком тоже завершится. Поэтому, как мы
- 2:25:33видим на нашем таймлайне, то есть вот
- 2:25:34это вот по сути линия времени наша, да?
- 2:25:36Вот тут вот у нас начало выполнения
- 2:25:37программы и вот тут вот время у нас
- 2:25:39идёт. Назову его Т. Вот наше время. То
- 2:25:41есть наши рабочие могли даже не все, как
- 2:25:44мы увидели действительно, начать
- 2:25:46работать. Они не то что добыть уголь,
- 2:25:49они ещё даже, ну, подойти к шахте кто-то
- 2:25:51не успел. Третий рабочий даже подойти к
- 2:25:52шахте не успел. А у нас уже сразу же
- 2:25:54основной поток завершился и всё. И вся
- 2:25:56программа завершилась. Они даже свою
- 2:25:58работу не успели выполнить, так как
- 2:25:59главный поток завершился. Всё, программа
- 2:26:01завершилась.
- 2:26:02И мы просто вывели, что мы добыли ноль
- 2:26:04угля. То есть мы вот их отправили
- 2:26:05работать. Мы даже не подождали там их
- 2:26:07результат выполнения работы, ничего. Мы
- 2:26:09просто отправили их работать. Сразу же
- 2:26:10побежали, сказали: "Мы добыли ноль
- 2:26:12угля". Всё. А как-то не круто
- 2:26:13получается, да? Ну, вообще, что бы мы
- 2:26:15могли тут сделать? Мы бы могли отложить
- 2:26:18завершение нашей программы, просто взять
- 2:26:20и попробовать подождать наших рабочих.
- 2:26:22Просто поставить после запуска всех
- 2:26:23рабочих, да, сп здесь. Time sleep. Ну,
- 2:26:27допустим, там на 5 секунд. Ну, давайте
- 2:26:29запустим нашу программу.
- 2:26:31Поход в шахту номер два, номер один,
- 2:26:33номер три начался. Почему-то не в нашем
- 2:26:36порядке, не 1 2 3, да, в другом порядке.
- 2:26:38Потом мы видим, что походы в шахту
- 2:26:39закончились. То есть наши рабочие все
- 2:26:42сходили в шахту, всё вроде хорошо, но
- 2:26:44при этом всё равно добыто ноль угля и
- 2:26:47прошло целых 5 секунд. То есть мы, по
- 2:26:49сути, только хуже сделали вот этим вот
- 2:26:51таймслипом, потому что до вообще всех
- 2:26:53запусков вот этих рабочих у нас
- 2:26:54программа выполнялась 3 секунды, а
- 2:26:56сейчас у нас она выполняется 5 секунд,
- 2:26:57даже несмотря на то, что мы уголь вообще
- 2:26:59не добыли. То есть таймслип слишком
- 2:27:01большой. Нам надо бы поставить поменьше.
- 2:27:03Ну, мы помним, что у нас как бы они
- 2:27:05вроде как параллельно работают, эти
- 2:27:07шахтёры. Каждый из них по одной секунде.
- 2:27:09Ну, наверное, если мы вот одну секунду
- 2:27:11поспим, то как раз они все должны успеть
- 2:27:13завершить свою работу. Ну давайте
- 2:27:15попробуем. То есть вот ставлю одну
- 2:27:16секунду, запускаю программу. Ну и мы
- 2:27:19можем наблюдать вот следующую некрасивую
- 2:27:21ситуацию, что мы начали все три похода в
- 2:27:23шахту. Сейчас мы разберём, почему они в
- 2:27:24таком странном порядке. Начали все три
- 2:27:26похода в шахту. Закончиться успел только
- 2:27:28второй поход.
- 2:27:30Мы сразу же говорим о том, что мы добыли
- 2:27:31ноль угля. И после того, как мы
- 2:27:33сигнализируем о том, что мы добыли
- 2:27:34сколько-то угля, после этого завершаются
- 2:27:36другие походы в шахту. То есть даже если
- 2:27:37бы мы с этих походов в шахту
- 2:27:39действительно могли собирать как-то
- 2:27:40уголь, то мы бы только второй поход в
- 2:27:42шахту засчитали бы. А вот эти вот у нас
- 2:27:43завершились после того, как мы уже
- 2:27:45сигнализируем о добыче угля. О чём это
- 2:27:47может говорить? О том, что вот так вот
- 2:27:49ставить в кодеle в ожидании, когда же
- 2:27:51там какой-то рабочий закончится. По
- 2:27:54сути, мы тут запускаем рутину. Раз,
- 2:27:55горутину, два, горутину, три рутину. Мы
- 2:27:57запустили тут три дополнительных рутины,
- 2:27:58три рабочих запустили. И вот таким вот
- 2:28:00образом таймслипом пытаться как-то
- 2:28:02дождаться вот завершения работы этих
- 2:28:03горутин - это неправильно. Почему?
- 2:28:05Потому что мы никогда точно не узнаем,
- 2:28:07сколько конкретно эти рутины будут
- 2:28:09выполняться. Потому что, несмотря на то,
- 2:28:10что тут таймслип 1 секунда, есть
- 2:28:12всё-таки какое-то дополнительное время,
- 2:28:14которое требуется на то, чтобы выполнить
- 2:28:15другие операции. Там вывести что-нибудь
- 2:28:17на экран, вызвать, в принципе, функцию,
- 2:28:19вернуть её значение. Ну или если
- 2:28:21говорить более абстрактно, шахтёру нужно
- 2:28:22дойти до шахты, там расчехлить все свои
- 2:28:25инструменты, начать работать. Это тоже
- 2:28:26какое-то время занимает помимо самого
- 2:28:28процесса добывания угля. И какое именно,
- 2:28:31мы не можем предугадать, потому что от
- 2:28:32запуска к запуску программы это время
- 2:28:34может быть разное. Где-то у нас там
- 2:28:35какой-нибудь браузер был запущен на
- 2:28:37компьютере, который у нас слишком много
- 2:28:39процессорного времени скушал, у нас
- 2:28:41из-за этого программа чуть-чуть лаганула
- 2:28:43и она дольше, чем одну секунду
- 2:28:44выполнилась. Или, может быть, у нас
- 2:28:46наоборот какой-нибудь суперсвежий запуск
- 2:28:47и у нас слишком быстро программа
- 2:28:49выполнилась, а мы из-за этого слишком
- 2:28:50долго спали. Хотя могли бы раньше
- 2:28:52завершить нашу программу, но мы целую
- 2:28:53секунду спали. То есть мы бы тут могли
- 2:28:55как-нибудь попытаться там 1.200
- 2:28:57мисекунд,
- 2:28:59ну, что является 1.2 секунды,
- 2:29:02да, как-нибудь вот типа 200 мсекунд
- 2:29:04заложить. Но из-за того, что у нас по
- 2:29:06сути программа-то выполняется быстрее,
- 2:29:08мы бы каждый раз вот эти 200 мкунд
- 2:29:10спали. Короче, это вот игра в лотерею.
- 2:29:12Вот пытаться вот так вот таймслипом
- 2:29:14попасть вообще в время завершения всех
- 2:29:16запущенных горутин, это ненадёжно, это
- 2:29:19неопределённое поведение вызывает нашей
- 2:29:20программе. То есть наша программа одного
- 2:29:22запуска к другому запуску может разный
- 2:29:24результат выдавать. Но это вообще не
- 2:29:25круто. Поэтому так вот таймслипом ловить
- 2:29:27- это не выход. То есть таймслипом
- 2:29:29просто вот так вот оттягивать завершение
- 2:29:30программы - это не выход, потому что мы
- 2:29:31либо слишком мало будем ждать, либо
- 2:29:33слишком много. И в итоге у нас программа
- 2:29:35мало того, что будет выполняться либо
- 2:29:37дольше, чем она должна, так если мы
- 2:29:38будем пытаться вот прямо в ровень
- 2:29:39попасть, ну мы ещё и можем случайно
- 2:29:41кого-то не успеть дождаться. Поэтому это
- 2:29:43не выход. Первая проблема. Вторая
- 2:29:44проблема, которая у нас возникает, что
- 2:29:46мы вот этот добытый уголь, мы ничего с
- 2:29:48ним не делаем. Как бы мы его добыли и
- 2:29:50всё. То есть наши рабочие этот уголь
- 2:29:52добыли. Там же, где они его добыли, они
- 2:29:53его там же и оставили и всё. То есть
- 2:29:55зачем добывали? Ну, вообще, если честно,
- 2:29:57непонятно. То есть мы даже, если тут
- 2:29:58дожидаемся их выполнение, там, о'кей,
- 2:30:00много таймслипа ставим, они этот уголь
- 2:30:02добыли и всё. И оставили его прямо в
- 2:30:04шахте. Типа никак нам его не передали.
- 2:30:06Как эту проблему решить? На самом деле,
- 2:30:07для того, чтобы решить проблему первую,
- 2:30:10что мы не можем правильно дождаться
- 2:30:11наших шахтёров, и вторую, что мы никак
- 2:30:13от них не можем получить уголь, эти две
- 2:30:15проблемы сразу же решают каналы. В
- 2:30:19Голенге есть такая сущность, как каналы.
- 2:30:21Есть два типа каналов. Есть каналы не
- 2:30:24буферизированные, есть каналы
- 2:30:25буферизированные. Сейчас мы по очереди
- 2:30:28разберём, в чём отличие и что вообще
- 2:30:30такое канал. Ну давайте сначала разберём
- 2:30:32небуферизированный канал. Опять же, в
- 2:30:34чём отличие буферизированного от
- 2:30:36небуферизированного, мы разберём чуть
- 2:30:37попозже. Для начала вот у нас канал без
- 2:30:40буфера, небуферизированный канал. Что
- 2:30:42это такое в нашем примере? В нашем
- 2:30:44примере это пункт передачи угля. Когда
- 2:30:47какой-то рабочий нафармит уголь, он
- 2:30:49должен будет прийти в этот пункт
- 2:30:51передачи углём со своим углём и ждать,
- 2:30:55пока к нему придёт прораб и заберёт этот
- 2:30:57самый уголь. Потом прораб с этим углём
- 2:31:00придёт обратно в главный поток
- 2:31:01выполнения программы и куда-нибудь этот
- 2:31:03уголь положит. Ну давайте будем класть
- 2:31:05наш уголь в ту же переменную кол.
- 2:31:08Давайте её нарисую. Вот наша переменная
- 2:31:10кол, куда мы будем складывать уголь. Ну
- 2:31:12давайте пойдём заново по порядку.
- 2:31:14Значит, во-первых, почему, когда мы
- 2:31:15запускаем нашу программу, вроде бы мы
- 2:31:17сначала запустили первого, потом
- 2:31:19второго, потом третьего рабочего, почему
- 2:31:20же они в каком-то странном порядке
- 2:31:22всегда начинают идти в шахту? Сначала
- 2:31:24третий, потом первый, потом второй.
- 2:31:25Почему? Потому что когда мы запускаем
- 2:31:28какие-то грутины, порядок их запуска
- 2:31:30фактически нам не гарантирован. То есть
- 2:31:32они могут запуститься как 1 2 3, так и
- 2:31:34321, так и 312. То есть мы вот этой вот
- 2:31:37записью, когда мы запускаем какую-то
- 2:31:39грутину, когда мы запускаем какого-то
- 2:31:41рабочего, мы просто говорим: "Иди
- 2:31:42работай". А так как мы им говорим: "Иди
- 2:31:43работай практически одновременно". 1Д,
- 2:31:45потом сразу же три. Ну, для программы
- 2:31:47это буквально несколько микросекунд,
- 2:31:48если не меньше. Это, ну, моментально
- 2:31:50практически. То есть мы практически
- 2:31:51моментально им сказали: "Иди работай", а
- 2:31:53уже там кто первый раздуплится, кто
- 2:31:55первый как бы подойдёт к шахте, соберёт
- 2:31:57все инструменты, там начнёт работать,
- 2:31:59это мы уже точно не можем сказать. Они
- 2:32:00как бы примерно вот в одно время начнут
- 2:32:02работать, но кто первый, мы не можем
- 2:32:04сказать, потому что мы их запустили, как
- 2:32:06бы сказали: "Идите работайте". А вот
- 2:32:07повторюсь, кто первый там начнёт,
- 2:32:09собственно, работать, мы этого не знаем.
- 2:32:10Мы знаем, что примерно, ну, в одно время
- 2:32:12они там начнут работать, но в каком
- 2:32:13именно порядке нам никто ничего не
- 2:32:15гарантирует. Поэтому мы видим вывод
- 2:32:17такой в неправильном, казалось бы,
- 2:32:18порядке. Хотя на самом деле при
- 2:32:20параллельном выполнении нам этот
- 2:32:21порядок-то сильно не важен. Нам главное,
- 2:32:23чтобы они все сходили в шахту, все
- 2:32:24добыли уголь и вернули нам этот уголь. А
- 2:32:26там, в каком порядке они это сделают,
- 2:32:28ну, не сильно важно. Главное, чтобы
- 2:32:29быстро, главное, чтоб параллельно. А кто
- 2:32:31первый там, кто второй, ну, это не
- 2:32:32сильно нас интересует. Так вот, начинаем
- 2:32:35нашу программу. То есть мы вот запустили
- 2:32:36трёх рабочих и ждём, пока они там что-то
- 2:32:38добудут. Пока мы в главном потоке
- 2:32:40выполнения ждём, пока они что-то
- 2:32:42добудут, мы сразу же приходим на этот
- 2:32:44пункт передачи угля и ждём, пока
- 2:32:46кто-нибудь из наших рабочих этот самый
- 2:32:48уголь добудет. Ну вот, допустим, второй
- 2:32:50рабочий, которого мы запустили, он успел
- 2:32:52первым добыть уголь, и он с этим самым
- 2:32:54углём тоже приходит на пункт передачи
- 2:32:56угля. Он приходит с этим углём, так как
- 2:32:58тут мы уже в главном потоке его ждём, он
- 2:33:01может спокойно нам этот уголь передать,
- 2:33:03и по сути после этого успешно
- 2:33:05завершится. То есть наш рабочий вернул
- 2:33:08уголь, и после этого его работа
- 2:33:10окончена. Он завершается после этого,
- 2:33:12говоря техническим языком, рутина
- 2:33:13завершает свою выполнение. Всё, я её вот
- 2:33:15зачёркиваю. Этот уголь, который мы
- 2:33:17получили в главном потоке, мы будем
- 2:33:19класть в нашу переменную кол. Потом во
- 2:33:21время того, как мы общались с этим
- 2:33:23первым рабочим, пока мы тут этот уголь
- 2:33:24перекладывали, уже какой-то другой
- 2:33:26рабочий, например, вот первый, он мог
- 2:33:28уже успеть тоже добыть уголь и прийти на
- 2:33:31пункт передачи угля. Но так как тут ещё
- 2:33:33никто его уголь не готов забирать, то
- 2:33:36этот рабочий просто будет ждать вот
- 2:33:37здесь вот, ждать, пока кто-нибудь придёт
- 2:33:39забирать этот уголь. После этого,
- 2:33:41наконец-таки, наш главный поток приходит
- 2:33:44опять в пункт передачи угля, и наш
- 2:33:46рабочий может спокойно этот самый уголь
- 2:33:47передать. передаёт уголь, после чего
- 2:33:50завершается,
- 2:33:51и мы в главном потоке выполнения можем
- 2:33:53этот уголёк принятый положить опять в
- 2:33:55нашу переменную кол. Вот у нас уже тут
- 2:33:5620 уголька. Потом опять мы в главном
- 2:33:59потоке положили наш уголёк, пришли в
- 2:34:02пункт передачи, ждём, пока нам
- 2:34:04кто-нибудь уголь принесёт. Ждём, ждём,
- 2:34:06ждём, ожидаем. И наконец-таки вот этот
- 2:34:08вот рабочий тоже добыл свой уголь. Он
- 2:34:09приходит в пункт передачи угля со своим
- 2:34:11углём. И так как его тут уже ожидают, он
- 2:34:14сразу же передаёт этот уголь. И после
- 2:34:17этого спокойненько завершается. Мы в
- 2:34:19главном потоке приняли этот уголь,
- 2:34:21положили в переменную кол и всё. И мы
- 2:34:23можем её спокойно вывести на экран,
- 2:34:25завершить нашу программу. То есть мы
- 2:34:26благодаря каналу, благодаря вот этому
- 2:34:28пункту передачи угля решили сразу две
- 2:34:30проблемы. Во-первых, мы успешно
- 2:34:32дождались всех рабочих. То есть мы не
- 2:34:34завершились раньше, чем каждый из
- 2:34:36рабочих вернул бы нам свой уголь. То
- 2:34:38есть мы успешно решили задачу
- 2:34:39синхронизации. Во-вторых, мы успешно
- 2:34:42этот самый добытый уголь из потоков
- 2:34:44выполнения этих рабочих приняли в нашем
- 2:34:47основном потоке выполнения. То есть мы
- 2:34:48этот уголь смогли между разными
- 2:34:50грутинами передать. Это вторая проблема,
- 2:34:52которую мы решили. То есть каналы, они
- 2:34:54для этого и нужны. Для того, чтобы
- 2:34:55синхронизировать разные потоки
- 2:34:57выполнения и для того, чтобы передавать
- 2:34:59между ними какую-то информацию. Это
- 2:35:00может быть у нас целочисленная
- 2:35:01информация, строковая информация, либо
- 2:35:03какие-то структуры наши. То есть любую
- 2:35:05переменную, которую мы тут можем
- 2:35:06написать, мы можем передавать в канале.
- 2:35:07Ну, с некоторыми оговорками, но пока что
- 2:35:09всё, что мы умеем. Мы можем передавать в
- 2:35:11канале там целые числа какие-то
- 2:35:12структуры, там булин, флот, стринг, что
- 2:35:14угодно вообще. Ну, в данном случае мы
- 2:35:15передаём уголь, который выражен целым
- 2:35:17числом, поэтому у нас как бы канал типа
- 2:35:19integer. Собственно, давайте посмотрим,
- 2:35:21как это вообще у нас будет выглядеть в
- 2:35:22коде. Ну, для того, чтобы это всё
- 2:35:24реализовать в нашем коде, для начала нам
- 2:35:26этот самый пункт передачи угля нужно
- 2:35:28создать. Как это делать? Назвать его
- 2:35:30можем как угодно. Я назову его transfer
- 2:35:34point, то есть пункт передачи transfer
- 2:35:36point. И создам канал. Как создаются
- 2:35:39каналы в Головнке? Через функцию Mak, мы
- 2:35:40помним, через функцию Make у нас можно и
- 2:35:42слайс создать, и мапу
- 2:35:44проинициализировать. Ну и канал тоже
- 2:35:45можно создать через функцию M. Как это
- 2:35:47выглядит? Мы пишем слово ключевой ch,
- 2:35:49что значит channel, то есть канал. И
- 2:35:51после этого тип канала. Ну, в нашем
- 2:35:53случае это целочисленный тип, потому что
- 2:35:54мы целые чиселки гоняем туда-сюда.
- 2:35:56Уголь, то есть целочисленный. Int. Всё,
- 2:35:59мы создали наш трансфер point. То есть
- 2:36:00мы создали наш канал, благодаря которому
- 2:36:02мы будем синхронизировать выполнение
- 2:36:04отдельных рутин и организовывать обмен
- 2:36:06между ними информацией. Хорошо, мы его
- 2:36:08создали. Теперь нам нужно, чтобы каждый
- 2:36:10из этих рабочих знал про наш трансфер
- 2:36:13point, чтобы он мог с ним
- 2:36:13взаимодействовать. Для этого мы будем
- 2:36:15передавать его в аргументах функции.
- 2:36:19Выглядит это следующим образом. Название
- 2:36:21канала и тип канала, что это у нас канал
- 2:36:23типа integer. Получается, нам этот
- 2:36:24transfer point нужно теперь передавать в
- 2:36:26аргументы этих функций. Трансфр point,
- 2:36:28transрансфр point, transрансфер point,
- 2:36:30чтобы каждый параллельно запущенный
- 2:36:32шахтёр, он знал про то, что есть вот
- 2:36:34такой вот трансфер point, про то, что
- 2:36:35есть такой вот пункт передачи угля, и
- 2:36:38чтобы он мог с ним как-то работать.
- 2:36:39Сразу же кто-то может заметить, что мы
- 2:36:41передаём копию этого канала, а не
- 2:36:44указатель на него, да? То есть не
- 2:36:45указатель на канал, а копию. Но так
- 2:36:48устроен, что каналы мы передаём копией,
- 2:36:51и они всё равно хорошо работают, потому
- 2:36:52что под капотом у них там довольно
- 2:36:53сложное устройство. Сейчас мы его
- 2:36:55разбирать не будем. Просто можем
- 2:36:56запомнить, что каналы мы передаём без
- 2:36:58всяких указателей, и они там под капотом
- 2:37:00всё равно будут корректно работать. То
- 2:37:01есть это и это по сути один канал.
- 2:37:03Почему так? Нужно разбирать подкапотку
- 2:37:05устройства каналов. Сейчас мы не будем
- 2:37:07на это тратить время, просто запомним,
- 2:37:08что каналы можно копии передавать вот
- 2:37:09так вот не по указателю, и всё будет
- 2:37:11хорошо работать. Вот мы создали наш
- 2:37:13трансфер pointт, мы его передали в наших
- 2:37:15шахтёров, но теперь у нас вопрос, что мы
- 2:37:18никак его не используем. Как мы этот
- 2:37:19transтрансфер point должны использовать?
- 2:37:21Ну, во-первых, когда какой-то рабочий
- 2:37:23уголь добыл, он должен прийти в этот
- 2:37:24ранфер pointт. и ждать, пока кто-нибудь
- 2:37:27придёт, забрать уголь. Но пока никого не
- 2:37:29пришло, этот рабочий должен прийти в
- 2:37:32этот пункт передачи угля и ждать, пока
- 2:37:34кто-нибудь будет готов забрать уголь.
- 2:37:36Как это выглядит в коде? Выглядит это
- 2:37:37следующим образом. Вот наш трансфер
- 2:37:38pointт, стрелочка в него и количество
- 2:37:41добытого угля. Ну, теперь наша функция
- 2:37:43уголь передаёт не возвращаемым
- 2:37:45значением, а именно через пункт
- 2:37:47передачи, через канал. Поэтому
- 2:37:49возвращаемое значение мы убираем. Что
- 2:37:51тут произошло? Наш рабочий отработал,
- 2:37:53добыл уголь. вот эти наши 10 угля. И он
- 2:37:55пытается эти 10 угля передать в пункт
- 2:37:58передачи угля. Ну, то есть вот эта
- 2:38:00стрелочка означает, что мы значение,
- 2:38:01которое справа, хотим передать в канал,
- 2:38:03в наш пункт передачи. Но так как у нас
- 2:38:06ещё тут не будет никого, кто бы этот
- 2:38:09уголь смог принять, то выполнение вот
- 2:38:11этой функции май, выполнение нашего
- 2:38:13рабочего в отдельной рутине тут будет
- 2:38:14блокироваться. И этот рабочий вот в этой
- 2:38:17восемнадцатой строчке будет просто вот
- 2:38:19так вот с углём стоять и ждать, пока
- 2:38:20кто-нибудь придёт забирать этот уголь.
- 2:38:23Для того, чтобы кто-то пришёл забирать
- 2:38:24этот уголь, мы должны вот в нашем
- 2:38:26главном потоке мы должны нашему этому
- 2:38:28прорабу, главному рабочему, прописать,
- 2:38:30что он должен приходить в этот пункт
- 2:38:32передачи и ждать, пока кто-то придёт,
- 2:38:34отдаст ему уголь. Как это выглядит в
- 2:38:36коде? Вот мы запустили трёх рабочих.
- 2:38:37После этого мы хотим ждать, пока они нам
- 2:38:39этот уголь передадут. Мы берём в
- 2:38:41переменную кол и прибавляем к ней то,
- 2:38:43что мы получим в пункте передачи угля.
- 2:38:46Выглядит это следующим образом. то есть
- 2:38:48мы к переменной хотим прибавить какое-то
- 2:38:50значение, которое находится справа от
- 2:38:52плюс равно. Справа от плюс равно
- 2:38:54находится получение значения из нашего
- 2:38:57канала. То есть таким образом, таким
- 2:38:58синтаксисом, мы говорим, что здесь мы
- 2:39:00будем ждать, пока мы сможем что-то
- 2:39:02получить из нашего канала. Пока в этот
- 2:39:04канал никто не придёт, пока в этот пункт
- 2:39:07передачи угля никто не придёт, мы просто
- 2:39:09будем тут ждать. Соответственно, тут мы
- 2:39:11заблокируем своё выполнение и будем
- 2:39:13ждать, пока кто-нибудь из рабочих
- 2:39:14наконец-таки добудет уголёк и придёт нам
- 2:39:17его передать. Как только какой-нибудь
- 2:39:18рабочий добудет уголёк и придёт нам его
- 2:39:20передавать, мы сразу же сможем его здесь
- 2:39:23принять, а здесь сможем отдать.
- 2:39:25Соответственно, как только здесь
- 2:39:26произойдёт отдача угля, сразу же один из
- 2:39:28запущенных арбочек завершит своё
- 2:39:30выполнение. Мы примем этот уголь вот
- 2:39:32здесь вот, достанем его из этого пункта
- 2:39:34передачи и прибавим в нашу переменную
- 2:39:36кол. Так как у нас запущено три рабочих,
- 2:39:38то прийти за углём мы должны три раза.
- 2:39:42Раз пришли за углём, этот завершился.
- 2:39:44Потом два пришли за углём, подождали,
- 2:39:46пока второй у нас придёт с угольком,
- 2:39:48получили этот уголь, положили сюда. Этот
- 2:39:50завершился.
- 2:39:52Потом третий у нас придёт отдавать
- 2:39:54уголь. Мы должны третий раз прийти за
- 2:39:55этим углём, получить его и положить в
- 2:39:58нашу переменную. После чего наш третий
- 2:39:59рабочий завершится, и мы сможем вывести
- 2:40:01на экран вот эту вот нашу переменную
- 2:40:02укол, в которой мы три раза положили наш
- 2:40:04уголь. То есть сходить в пункт передачи
- 2:40:06из главного нашего потока мы должны три
- 2:40:08раза. Потому что, если мы туда придём
- 2:40:10всего лишь один раз, давайте подумаем,
- 2:40:11что будет, если мы придём туда всего
- 2:40:12лишь один раз, как у нас сейчас
- 2:40:14написано. То есть мы вот запускаем наших
- 2:40:16троих рабочих раздтри. И сразу же
- 2:40:18приходим в пункт передачи. Ждём, пока
- 2:40:20кто-нибудь нам с другой стороны протянет
- 2:40:22руку с углём. О'кей. Вот какой-нибудь
- 2:40:24рабочий, наконец-таки, добывает свой
- 2:40:25уголь, приходит в пункт передачи вот
- 2:40:28здесь вот и сразу же отдаёт нам этот
- 2:40:30уголь, после чего завершает своё
- 2:40:31выполнение.
- 2:40:33Мы положили 10 угля, вернулись. И так
- 2:40:36как мы пришли в пункт передачи углём
- 2:40:37всего один раз, то после этого мы просто
- 2:40:39заснём зачем-то ещё на 1.2 секунды. Ну
- 2:40:43о'кей, мы поспали, мы типа продолжили
- 2:40:45своё выполнение, поспали, и после этого
- 2:40:47всё, мы выведем, сколько угля мы добыли
- 2:40:49и сколько времени прошло. А добыли мы
- 2:40:51сколько? 10 угля всего мы добыли.
- 2:40:52Почему? Потому что мы пришли в пункт
- 2:40:54передачи один раз всего. И даже если
- 2:40:55после этого какие-то ещё рабочие придут
- 2:40:57в этот пункт передачи, чтобы передать
- 2:40:59нам уголь, мы всё равно не придём
- 2:41:00забирать его. И поэтому они так и будут
- 2:41:02тут стоять, эти наши рабочие, ждать,
- 2:41:03пока кто-нибудь этот уголь заберёт. А
- 2:41:05его никто не придёт забирать, потому что
- 2:41:07мы только один раз его забирали. Ну,
- 2:41:09соответственно, мы только один раз
- 2:41:09заберём наш уголь, поспим зачем-то 1-2
- 2:41:12секунды. Можем это даже удалить, это ни
- 2:41:13на что не повлияет, и всё. И выведим,
- 2:41:14что мы добыли 10 угля. Ну, давайте
- 2:41:16проверим.
- 2:41:18Мы как бы три похода в шахту начали.
- 2:41:20Закончились тоже все три похода в шахту,
- 2:41:22но мы всего лишь 10 угля добыли, потому
- 2:41:24что мы только один раз взяли этот уголь.
- 2:41:25Поэтому нужно брать уголь три раза.
- 2:41:27Первый раз взяли уголь, второй раз взяли
- 2:41:29уголь, третий раз взяли уголь. Что
- 2:41:31теперь будет происходить? Теперь будет
- 2:41:33происходить именно тот самый описанный
- 2:41:35алгоритм, который должен происходить в
- 2:41:36идеале. Мы запускаем три рабочих. Вот
- 2:41:38они наши запущенные рабочие. 1 2 3.
- 2:41:40После этого мы сразу же пришли в пункт
- 2:41:43передачи углём и ждём, пока кто-нибудь
- 2:41:45из них закончится. То есть любой из
- 2:41:47трёх, кто первый закончится, первый,
- 2:41:49второй или третий, любой, кто первый
- 2:41:50добудет уголь, тот первый придёт в пункт
- 2:41:53передачи угля, и мы получим его уголь.
- 2:41:55Ну, соответственно, первый, допустим,
- 2:41:56вот этот вот закончил. Вот он пришёл со
- 2:41:57своим углём. Мы его тут уже ждём. Вот мы
- 2:41:59его тут ждём. Он сразу же передаст нам
- 2:42:01свой уголёк. и мы его положим в нашу
- 2:42:03переменную. После этого всё, он возьмёт,
- 2:42:05да, и завершится. Потом мы второй раз
- 2:42:08приходим на пункт передачи угля. И тут
- 2:42:10интересный момент. Пока мы обрабатывали
- 2:42:12уголь от первого рабочего, в этот пункт
- 2:42:14передачи уже могли прийти два других
- 2:42:16рабочих. Они могли уже успеть обработать
- 2:42:18уголь и прийти в этот пункт передачи
- 2:42:20угля. Но так как у нас канал, этот
- 2:42:22канал, он нам и синхронизирует их
- 2:42:24работу, и помогает обмениваться
- 2:42:25информацией, то эти два рабочих будут
- 2:42:27послушно ждать вот здесь вот, пока мы
- 2:42:29придём за их углём. Потом мы второй раз
- 2:42:31придём за угольком, нам какой-нибудь из
- 2:42:34этих двух рабочих отдаст свой уголь, мы
- 2:42:36этот уголёк положим в переменную, и этот
- 2:42:38рабочий у нас завершится. А вот этот вот
- 2:42:39другой, который пришёл вместе с ним, он
- 2:42:41всё ещё будет ждать. После этого мы
- 2:42:43третий раз пойдём в пункт передачи,
- 2:42:46заберём у него, наконец-таки, этот
- 2:42:47уголь, положим в нашу переменную кол,
- 2:42:50а этот рабочий спокойно завершит своё
- 2:42:52выполнение. Таким образом, мы запустили
- 2:42:54трёх рабочих, три раза пришли в пункт
- 2:42:56передачи углём и дождались, пока каждый
- 2:42:58из этих трёх рабочих нам в этот пункт
- 2:43:00передачи доставится уголь. Давайте
- 2:43:01запустим программу, посмотрим, что
- 2:43:03получилось. Все три пошли в шахту, все
- 2:43:05три завершили своё выполнение, мы
- 2:43:08вывели, что мы добыли 30 угля и прошла 1
- 2:43:11секунда. Таким образом, мы наконец-таки
- 2:43:13смогли перейти от вот этой вот модели
- 2:43:16выполнения программы, где у нас всего
- 2:43:17лишь один рабочий, и он последовательно
- 2:43:19ходит в шахту, и в итоге вся программа 3
- 2:43:21секунды занимает. Мы от этой модели
- 2:43:23успешно перешли к модели, когда у нас
- 2:43:25три рабочих работают параллельно, и при
- 2:43:27этом мы благодаря каналу
- 2:43:28синхронизировали их выполнение и смогли
- 2:43:31обменяться ещё информацией. То есть
- 2:43:32каналы нам нужны для того, чтобы
- 2:43:34синхронизировать отдельные потоки
- 2:43:35выполнения, синхронизировать отдельные
- 2:43:37рутины и чтобы эти рутины ещё
- 2:43:39обменивались информацией. Таким образом,
- 2:43:41мы можем запустить до хоть сразу семь
- 2:43:43рабочих.
- 2:43:44Тут 3 4 5 6 7. Просто семь раз можем
- 2:43:48сходить в пункт передачи углём. 1 2 3 4
- 2:43:515 6 7 И мы за ту же одну секунду сможем
- 2:43:54добыть уже 70 угля.
- 2:43:57Как мы видим, прошла всего лишь 1
- 2:43:59секунда, но мы уже добыли целых 70
- 2:44:00углей. Почему? Потому что у нас уже
- 2:44:03параллельно работало не три шахтёра, а
- 2:44:06семь.
- 2:44:08Вот так вот мы их запустили параллельно,
- 2:44:09и они у нас начали работать. Это одно из
- 2:44:12самых больших преимуществ Голенга в том,
- 2:44:14что запуск вот этих дополнительных
- 2:44:16рабочих, он требует очень мало каких-то
- 2:44:18накладных ресурсов. Поэтому мы можем и
- 2:44:201.000 рабочих запустить, и 2.000 рабочих
- 2:44:23запустить параллельно работающих. И у
- 2:44:25нас это сильно не отразится на нашей
- 2:44:27производительности. Мы сможем наоборот
- 2:44:28очень много выиграть времени. То есть в
- 2:44:30худшую сторону это практически никак не
- 2:44:32отразится, зато очень сильно отразится в
- 2:44:34лучшую. Сейчас немножко такой момент
- 2:44:36будет чисто технический. На самом деле
- 2:44:37новичку, наверное, не очень интересно,
- 2:44:39но всё-таки об этом нужно сказать. У нас
- 2:44:41не может именно истинно параллельно
- 2:44:43выполняться больше рабочих, чем у нас
- 2:44:46ядер в процессоре. Если у нас в
- 2:44:48процессоре четыре ядра, а мы запустили
- 2:44:511.000 рабочих, то эти рабочие будут
- 2:44:53выполняться в некотором смысле по
- 2:44:56очереди всё-таки. То есть одновременно
- 2:44:57сможет выполняться только четыре
- 2:44:59рабочих, но Geng очень хорошо умеет
- 2:45:03управлять таким выполнением. И когда
- 2:45:05какой-то из рабочих будет либо засыпать,
- 2:45:08либо останавливаться на каком-то сетевом
- 2:45:10вызове, либо ещё что-нибудь такое
- 2:45:12делать, из-за чего он, по сути, будет
- 2:45:14блокировать своё выполнение и чего-то
- 2:45:15ждать, например, писать в канал и ждать,
- 2:45:17пока кто-нибудь придёт в пункт приёма и
- 2:45:19возьмёт этот уголь. В этот момент Голг
- 2:45:22видит, что какой-то из рабочих у нас
- 2:45:24простаивает, ничего не делает, он его
- 2:45:25вытесняет и на его место кладёт другого
- 2:45:28рабочего. Потом он увидит, что вот этот
- 2:45:30вот другой рабочий тоже что-то там
- 2:45:31завтыкал, он уже заснул, там что-нибудь
- 2:45:34не делает, он обратно туда вытеснит
- 2:45:36рабочего, который уже может работать.
- 2:45:38Потом какой-нибудь из этих рабочих у нас
- 2:45:40засыпает,
- 2:45:41его вытеснет с ядра процессора и на его
- 2:45:44место поставит другого рабочего, который
- 2:45:46готов работать. Потом этот рабочий опять
- 2:45:48не готов работать, его вытесняют,
- 2:45:49кладётся туда рабочий, который готов
- 2:45:51работать. И таким образом Goeng у нас
- 2:45:53создаёт ощущение, что даже если мы
- 2:45:55запустили 1.000 рабочих, а у нас всего
- 2:45:57лишь четыре ядра процессора, то они как
- 2:45:59будто бы всё равно выполняются
- 2:46:00параллельно. И это, повторюсь, одно из
- 2:46:02самых главных преимуществ голнга как
- 2:46:03языка. Почему он так популярен, почему
- 2:46:04его сейчас начинают везде активно
- 2:46:05использовать? Потому что он очень
- 2:46:07эффективно расходует эти ядра
- 2:46:08процессора. Практически никогда он не
- 2:46:10заставляет эти ядра простаивать. Если
- 2:46:12видишь, что какое-то ядро, какой-то
- 2:46:13рабочий занимается какой-то фигнёй и
- 2:46:15просто спит там, ждёт какого-нибудь
- 2:46:16сетевого ответа или просто там слип
- 2:46:18словил или пишет в канал, то есть в этот
- 2:46:20пункт приёма передачи угля, и его никто
- 2:46:22не забирает, он просто берёт отсюда вот
- 2:46:24так хопа и вытесняется, и на его место
- 2:46:26ставится рабочий, который готов
- 2:46:27работать, который готов выполнять
- 2:46:29какую-то полезную нагрузку. Таким
- 2:46:30образом осуществляется запуск рутин и
- 2:46:32назначение этих рутин на ядро
- 2:46:34процессора. Ну, это так немножко
- 2:46:35технической информации для общего
- 2:46:36развития. Это мы с вами разобрали
- 2:46:39небуферизированный канал. То есть
- 2:46:40небуферизированный канал, он не имеет
- 2:46:42какого-то места, где бы он мог хранить
- 2:46:44уголь, который в него приносят рабочие.
- 2:46:47Поэтому рабочие просто ждут, пока
- 2:46:49кто-нибудь придёт этот уголь принять и
- 2:46:51передают его из рук в руки.
- 2:46:54Но помимо этого у нас есть
- 2:46:56буферизированный канал. Давайте
- 2:46:58рассмотрим, как работает
- 2:46:59буферизированный канал. Вот он наш
- 2:47:01буферизированный канал. В чём его
- 2:47:02отличие? в том, что у него, собственно,
- 2:47:05есть буфер. Мы можем разный размер этому
- 2:47:07буферу задавать. Что такое буфер в
- 2:47:09канале? Это место, куда можно положить
- 2:47:12уголь
- 2:47:13и сохранить его там. Не ждать, пока
- 2:47:16придёт человек, который заберёт этот
- 2:47:18уголь из рук в руки, а просто положить
- 2:47:20этот уголёк в тележку условную и
- 2:47:22спокойненько завершится.
- 2:47:25То есть у нас человек, который принимает
- 2:47:27этот уголь, с другой стороны, он может
- 2:47:29вообще там какими-то своими делами
- 2:47:30заниматься. А наши рабочие быстренько,
- 2:47:32хоп-хоп, придут туда, в эту тележку
- 2:47:34уголька закидают и спокойненько
- 2:47:36завершатся.
- 2:47:38Каждый из них может прибежать со своим
- 2:47:40углём, положить его туда, в эту
- 2:47:42тележечку и спокойненько завершится, не
- 2:47:45дожидаясь, пока придёт человек, который
- 2:47:46этот уголь будет забирать. То есть эти
- 2:47:48горутины могут просто в канал накидать
- 2:47:50значений и завершиться спокойно. И в
- 2:47:52этом канале эти значения будут храниться
- 2:47:54благодаря тому, что у него есть буфер
- 2:47:56какой-то размерности. Мы можем туда в
- 2:47:58этот буфер накидать сразу же несколько
- 2:47:59значений, и они там будут сохранены,
- 2:48:01ждать, пока кто-нибудь придёт, эти
- 2:48:03значения заберёт. После этого наш
- 2:48:05главный поток может прийти и сразу же
- 2:48:07раз вычитать оттуда, два вычитать оттуда
- 2:48:10и три вычитать оттуда. В этом и отличие
- 2:48:13буферизированного канала от
- 2:48:14небуферизированного. То есть, когда у
- 2:48:16нас у канала нет буфера, то наш рабочий,
- 2:48:19который принёс уголь, он не сможет уйти.
- 2:48:22освободиться или завершиться, пока не
- 2:48:24придёт другая рутина, другой рабочий,
- 2:48:26другой поток выполнения, чтобы из рук в
- 2:48:28руки этот уголь забрать. А когда у нас
- 2:48:29буферизированный канал, можно сразу же
- 2:48:31весь этот уголь накидать в эту
- 2:48:32тележечку, спокойненько завершится, и
- 2:48:34потом когда-нибудь кто-нибудь этот уголь
- 2:48:37весь заберёт, весь буфер этот прочитает.
- 2:48:38Давайте посмотрим, как это у нас в коде
- 2:48:40выглядит. Ну вот сделаем, что у нас три
- 2:48:42рабочих. И, соответственно, мы хотим
- 2:48:44сделать наш канал с буфером. Для этого
- 2:48:46мы тут положим буфер размерностью три.
- 2:48:48То есть можно будет три раза туда
- 2:48:50положить какое-то значение и
- 2:48:51спокойненько завершится. Ну давайте
- 2:48:53запустим нашу программу.
- 2:48:56Ну, в принципе, мы видим то же самое,
- 2:48:58потому что программа наша выполняется
- 2:49:00довольно быстро, и у нас что ждут наши
- 2:49:03копатели, пока кто-нибудь заберёт этот
- 2:49:05уголь, что не ждут, всё равно происходит
- 2:49:06всё довольно быстро, мы не видим
- 2:49:07разницу. Давайте чуть-чуть
- 2:49:08подкорректируем программу, чтобы эту
- 2:49:10разницу всё-таки увидеть. То есть мы
- 2:49:11каждый раз после того, как передали наш
- 2:49:13уголь, будем ещё писать поход,
- 2:49:16номер такой-то.
- 2:49:20Уголь передал.
- 2:49:22И каждый раз после того, как мы этот
- 2:49:24уголь будем забирать, мы будем ещё
- 2:49:26засыпать тоже на секунду.
- 2:49:29[аплодисменты]
- 2:49:34Для чего это делать? Ну, для того, чтобы
- 2:49:35сымитировать вот этого запоздалого
- 2:49:37главного рабочего, который не сразу
- 2:49:39приходит забирать уголь. Что он после
- 2:49:41каждого раза, когда он этот уголь
- 2:49:42забирает, он ещё целую секунду ждёт и не
- 2:49:44приходит. Для начала давайте рассмотрим
- 2:49:46этот пример на канале без буфера. Вот,
- 2:49:48значит, у нас канал без буфера. У нас
- 2:49:49три рабочих, они запускаются. Ну, и
- 2:49:51примерно одновременно они у нас придут
- 2:49:53этот самый уголь отдавать. Первый
- 2:49:54рабочий придёт уголь отдавать, второй
- 2:49:56рабочий придёт уголь отдавать, и третий
- 2:49:57рабочий придёт уголь отдавать. У нас
- 2:49:59никакого буфера нету, поэтому они не
- 2:50:00могут положить этот уголь в какой-то
- 2:50:02ящичек, в какую-то корзиночку и
- 2:50:04завершится. Нет, они из-за того, что нет
- 2:50:06буфера, будут ждать, пока смогут
- 2:50:08передать этот уголь из рук в руки. Ну
- 2:50:09вот я уберу тут буфер, у канала нету
- 2:50:11буфера. И запустили наших рабочих. Они
- 2:50:13там отработали. Вот приходит наш главный
- 2:50:15самый поток, хочет главный наш рабочий
- 2:50:17хочет вот этот уголь взять из рук в
- 2:50:19руки. Ну вот он хорошо придёт, возьмёт
- 2:50:21этот уголь, положит его в нашу
- 2:50:22переменную. После этого одна из горутин,
- 2:50:25один из рабочих сможет хорошо
- 2:50:27завершиться. Успешно
- 2:50:29завершился. Но два других будут ждать,
- 2:50:31пока этот наш главный поток, главный
- 2:50:33рабочий придёт опять забирать значение,
- 2:50:35а он будет засыпать после этого на целую
- 2:50:37секунду. Соответственно, наши передачи
- 2:50:39углей тоже будут ждать, пока этот чувак
- 2:50:42придёт забирать уголь. Он секунду
- 2:50:44отоспался, опять придёт забирать уголь.
- 2:50:46Заберёт какой-нибудь уголь,
- 2:50:49другая горутина завершится.
- 2:50:51Но третья, она всё ещё будет ждать,
- 2:50:53короче, всё ещё будет ждать, пока,
- 2:50:55наконец-таки, сможет этот уголь
- 2:50:56передать. Потом ещё через секунду,
- 2:50:58наконец-таки, наш главный челик придёт,
- 2:51:00заберёт этот уголь, положит его в
- 2:51:02переменный укол. Ну и, наконец-таки,
- 2:51:04наши все грутины завершатся. То есть
- 2:51:06из-за того, что у нас нету буфера, наши
- 2:51:08все горутины, которые в этот канал
- 2:51:09что-то хотят записать, они будут ждать,
- 2:51:11пока с другой стороны придёт челик,
- 2:51:13чтобы прочитать это значение. И это
- 2:51:15может быть плохо, если после записи в
- 2:51:17канал предполагается какая-то ещё логика
- 2:51:20осмысленная. То есть эта осмысленная
- 2:51:22логика, она не сможет выполняться, пока
- 2:51:24мы не запишем что-то в канал, пока
- 2:51:26кто-то с другой стороны не прочитает из
- 2:51:27этого пункта выдачи наш уголь. Иногда
- 2:51:29это плохо, иногда это хорошо. И тут наша
- 2:51:31задача как программистов понимать, когда
- 2:51:33лучше использовать небуферизированный
- 2:51:34канал, когда буферизированный. Ну
- 2:51:35давайте запустим наш пример с
- 2:51:36небуферизированным каналом, с каналом
- 2:51:38без буфера. Запускаем. Запускаются три
- 2:51:40рабочих. Один уголь передал, второй
- 2:51:43уголь передал, третий уголь передал. То
- 2:51:44есть они передают уголь не
- 2:51:46оддновременно. Они ждут, пока с другой
- 2:51:49стороны заберёт челик уголь. Давайте тут
- 2:51:51поставим таймслипы побольше, примерно 3
- 2:51:53секунды.
- 2:51:54Ну и будем засыпать не после того, как
- 2:51:56мы пришли в пункт выдачи, а перед.
- 2:52:01То есть заснули на 3 секунды, забрали,
- 2:52:03заснули, забрали, заснули, опять
- 2:52:05забрали. Давайте запустим нашу
- 2:52:06программу.
- 2:52:08Они все на, видите, все наши рабочие,
- 2:52:10они уже закончили свой поход в шахту. Но
- 2:52:13уголь они передать смогут далеко не
- 2:52:15сразу. Они будут ждать, пока с другой
- 2:52:17стороны придёт вот этот вот челик,
- 2:52:18забирать уголь. Давайте ещё раз на это
- 2:52:19посмотрим.
- 2:52:21Хоп, все закончили. Все сходили в шахту.
- 2:52:23Но они не могут уголь передать. Они
- 2:52:24ждут, пока с другой стороны чувак начнёт
- 2:52:27забирать уголь. первый уголь передал,
- 2:52:29второй, третий, вот только смог передать
- 2:52:30уголь. То есть они не могут его просто
- 2:52:32туда закинуть этот уголь и уйти на покой
- 2:52:35или уйти дальше выполнять какую-то
- 2:52:36полезную работу. Нет, они все тут ждут,
- 2:52:38пока с другой стороны придёт наш главный
- 2:52:41рабочий и прочитает оттуда значение и
- 2:52:43вытащит из этого пункта передачи.
- 2:52:45Возьмёт из рук в руки этот уголь, и они
- 2:52:47все с протянутой рукой вот тут вот
- 2:52:48просто ждут его. Ну вот ещё раз. Они
- 2:52:51пришли в шахту, всё, добыли уголь и
- 2:52:53ждут. Первый дождался, наконец-таки.
- 2:52:55Второй наконец-таки передал уголь.
- 2:52:56Третий всё ещё ждёт. Третий передал
- 2:52:58уколь. Слава богу. Это бывает не всегда
- 2:53:01эффективно. Опять же, если после записи
- 2:53:02в канал у нас какая-то логика
- 2:53:03осмысленная есть, мы хотим бы уже
- 2:53:05перейти к её выполнению, хотели бы
- 2:53:06закинуть это значение в канал, да, в
- 2:53:08буфер в какой-нибудь закинуть и пойти
- 2:53:10дальше, но мы не можем, мы ждём, потому
- 2:53:11что у нас не буферизированный канал без
- 2:53:13буфера. А если мы этому каналу сделаем
- 2:53:15буфер, ну вот, например, три значения.
- 2:53:16Давайте посмотрим. Вот наш буфер
- 2:53:18размерностью три. Вот я как бы три
- 2:53:19ячеечки для него выделил, что как бы это
- 2:53:21буфер размерностью три. У нас
- 2:53:23запускаются наши три рабочих. Вот буфер
- 2:53:25размерностью три. запускаются наши три
- 2:53:26рабочих. И после этого они как бы за
- 2:53:28одну секунду все успеют уголь добыть и
- 2:53:30будут пытаться его положить в наш канал.
- 2:53:32Наш главный вот этот вот челик, он
- 2:53:34только через 3 секунды придёт забирать
- 2:53:35уголь. Но на самом деле все рабочие уже
- 2:53:38через примерно одну секунду весь этот
- 2:53:39уголь успеют положить в канал. Как это
- 2:53:41будет выглядеть? Вот первый рабочий
- 2:53:43успел добыть уголь и хочет положить его
- 2:53:45в канал. Так как канал у нас
- 2:53:47буферизированный, то когда эти все
- 2:53:49рабочие придут класть уголь, они не
- 2:53:51будут ждать, пока кто-то с другой
- 2:53:52стороны придёт этот уголь забирать. Они
- 2:53:54просто бам накидают его в буфер. Бам
- 2:53:57накидают его в буфер, спокойненько
- 2:53:59завершаться. Этот тоже прибежит, как
- 2:54:02только так сразу, сразу же закинет в
- 2:54:03буфер и тоже завершится. Всё, они сразу
- 2:54:06же накидают в буфер значений и
- 2:54:07спокойненько завершатся. Либо пойдут
- 2:54:09дальше выполнять какую-то осмысленную
- 2:54:10логику. Сразу же мы увидим, что поход
- 2:54:12номер такой-то уголь передал без
- 2:54:14задержек. Просто возьмут и накидают в
- 2:54:15этот буфер угля. И потом уже наш ленивый
- 2:54:18главный вот этот поток, он придёт и за
- 2:54:20три раза вычитает все значения, которые
- 2:54:22в нашем буфере лежат. То есть первый раз
- 2:54:24придёт, десяточку возьмёт, положит в
- 2:54:26кол. Второй раз придёт, десяточку
- 2:54:28возьмёт, положит в кол. И третий раз
- 2:54:29придёт, десяточку возьмёт и положит в
- 2:54:31кол. Всё. А вот эти наши рабочие, они
- 2:54:33будут освобождены от необходимости
- 2:54:36стоять у этого пункта передачи и ждать,
- 2:54:38пока придёт рабочий с другой стороны.
- 2:54:40Повторюсь, бывают ситуации, когда нужно
- 2:54:41использовать небуферизированный канал,
- 2:54:43когда нам всё-таки нужно, чтобы каждый
- 2:54:44рабочий ждал, пока главный придёт и
- 2:54:46заберёт какое-то значение. А бывают
- 2:54:48ситуации, когда нам лучше накидать это
- 2:54:49всё в буфер и потом просто спокойно
- 2:54:51пойти дальше выполняться. зависит от
- 2:54:53поставленной задачи, зависит от того,
- 2:54:54как нам нужно организовать потоки нашего
- 2:54:56выполнения. Поэтому нужно включать
- 2:54:58голову, думать перед тем, как создавать
- 2:54:59буфер для канала или не создавать. Ну
- 2:55:01давайте посмотрим, как вот этот вот наш
- 2:55:02пример с буфером будет работать на
- 2:55:04практике. Запускаем нашу программу.
- 2:55:05Начали, оп, закончили. И как только
- 2:55:08закончили, сразу же передали уголь. То
- 2:55:10есть они только закончили своё
- 2:55:11выполнение, сразу же передали уголь.
- 2:55:13Закончил, сразу же передал, закончил,
- 2:55:15сразу же передал. И всё. И когда они
- 2:55:17передали уголь, они либо завершаются,
- 2:55:19либо дальше выполняют какую-то
- 2:55:20осмысленную логику. То есть теперь на
- 2:55:22вот этом месте мы не блокируемся. Мы не
- 2:55:24ждём, пока кто-то с другой стороны
- 2:55:25придёт прочитать оттуда значение, потому
- 2:55:27что у нас есть буфер, у нас есть куда
- 2:55:29положить наши значения, а потом уже
- 2:55:32сначала первый раз он прочитает, второй
- 2:55:35раз прочитает, третий раз, каждый раз 3
- 2:55:37секунды спит, ну и через 9 секунд,
- 2:55:39наконец-таки, мы выведем добыли
- 2:55:40треуголя. Поэтому в данном случае
- 2:55:42благодаря буферу, мы можем освободить
- 2:55:44наших рабочих гораздо раньше. Они просто
- 2:55:46накидают в буфер значений и не будут
- 2:55:48ждать, пока этот ленивый главный
- 2:55:49прорабёд читать свои значения. А что
- 2:55:52будет, если у нас буфер размером два?
- 2:55:54Как вот это понимать? Давайте сначала на
- 2:55:56картинке разберём. То есть вот у нас
- 2:55:58буфер размером два. О'кей, запустили
- 2:56:00всех рабочих. Они потом пришли отдавать
- 2:56:03свой уголь. Этот наш главный там пока
- 2:56:04ещё чилит. Пришли отдавать свой уголь.
- 2:56:06Оп, первый отдал уголь, завершился.
- 2:56:10Второй пришёл со своим углём, отдал свой
- 2:56:13уголь, завершился.
- 2:56:17Третий пришёл со своим углём,
- 2:56:20а класть его некуда, мест нет. И он
- 2:56:22точно так же, как в случае с
- 2:56:24небуферизированным каналом, будет ждать,
- 2:56:26пока кто-то придёт, прочитает хотя бы
- 2:56:29одно значение,
- 2:56:31и после этого он только сможет положить
- 2:56:33в открывшуюся ячейку какое-то значение и
- 2:56:35тоже завершится.
- 2:56:37Поэтому, если мы пишем в
- 2:56:38буферизированный канал и у нас
- 2:56:39заканчивается буфер, то следующая запись
- 2:56:41в этот канал будет блокировать опять
- 2:56:44выполнение. И опять мы будем вот ждать,
- 2:56:46пока кто-нибудь прочитает какую-нибудь
- 2:56:48ячейку из этого канала. Эта ячейка
- 2:56:50освободится у нас, и после этого мы туда
- 2:56:52сможем записать. Таким образом, мы уже
- 2:56:54плавненько начинаем переходить к тому,
- 2:56:56как каналы работают, когда они
- 2:56:57блокируются, когда не блокируются. Когда
- 2:56:59мы с вами приходим писать
- 2:57:01внебуферизированный канал, то мы всегда
- 2:57:03будем блокироваться до тех пор, пока
- 2:57:05кто-нибудь не придёт читать из этого
- 2:57:07канала. И как только кто-нибудь придёт
- 2:57:09на этот пункт передачи, мы сможем из рук
- 2:57:11в руки передать. Но до тех пор, пока
- 2:57:13никто сюда не пришёл, мы просто будем
- 2:57:15ждать, если мы пишем в этот канал,
- 2:57:17просто будем блокироваться и ждать, пока
- 2:57:19кто-нибудь придёт забирать это значение.
- 2:57:20Точно так же, с другой стороны, если
- 2:57:22кто-нибудь уже пришёл читать из нашего
- 2:57:25канала, а в наш канал никто ещё не
- 2:57:27пишет, этот кто-то, кто хочет прочитать,
- 2:57:29он будет стоять и ждать, пока придёт
- 2:57:31какая-нибудь гарутина, чтобы в этот
- 2:57:33канал какое-то значение записать. Вот он
- 2:57:35будет ждать, ждать, ждать, и потом
- 2:57:37придёт рутина с каким-то значением, и
- 2:57:39сразу же мы сможем это значение
- 2:57:41прочитать. То есть канал нам служит
- 2:57:42местом синхронизации. Мы будем как во
- 2:57:44время записи в канал блокироваться. То
- 2:57:46есть у нас сейчас речь про
- 2:57:47небуферизированный канал. Мы как во
- 2:57:49время записи в канал будем блокироваться
- 2:57:51и ждать, пока кто-нибудь придёт
- 2:57:52прочитать. Так и во время чтения из
- 2:57:54канала мы тоже будем блокироваться и
- 2:57:56ждать, пока кто-нибудь придёт в этот
- 2:57:57канал что-нибудь класть. То есть мы во
- 2:58:00время чтения из канала будем
- 2:58:01блокироваться и ждать, пока кто-нибудь
- 2:58:02другой придёт в этот канал писать. И
- 2:58:04только тогда мы сможем это значение
- 2:58:05прочитать оттудово, положить его
- 2:58:07куда-нибудь, куда нам нужно, и тот, кто
- 2:58:09пишет, пойдёт дальше. Ну или завершится,
- 2:58:11если у нас там дальше нет никакого кода,
- 2:58:12как бы вот мы тут записали, вывели на
- 2:58:14экран, всё, закончили своё выполнение.
- 2:58:16Это не буферизированные каналы, у них
- 2:58:18нет буфера, поэтому нужно, чтобы с двух
- 2:58:20сторон приём и передача были активны, и
- 2:58:22только тогда произойдёт передача
- 2:58:24значения. С другой стороны, у нас есть
- 2:58:26буферизированные каналы. Когда кто-то
- 2:58:27приходит читать из буферизированного
- 2:58:30канала и в канале нет никакого значения,
- 2:58:32то этот кто-то, кто пришёл читать, он
- 2:58:34также заблокируется и будет ждать, пока
- 2:58:36кто-нибудь что-нибудь положит в буфер. С
- 2:58:38другой стороны, если кто-нибудь пришёл
- 2:58:40писать в буферизированный канал и никто
- 2:58:43не читает этот буферизированный канал,
- 2:58:44ну и что? У нас есть буфер, мы в этот
- 2:58:46буфер закидываем значение и спокойненько
- 2:58:49идём дальше. Ну или завершаемся. Потом
- 2:58:51какая-нибудь другая горутинка пришла в
- 2:58:52этот наш буферизированный канал
- 2:58:53что-нибудь писать. Если в буфере ещё
- 2:58:55есть свободное место, оп, спокойненько
- 2:58:56закидываем в этот буфер наши значение и
- 2:58:58завершаем своё выполнение. Ну или там
- 2:58:59идём дальше. Если дальше какой-нибудь
- 2:59:00код у нас тут написан осмысленный. Но
- 2:59:02если мы придём писать в буферизированный
- 2:59:05канал, а буфер будет забит, то мы уже
- 2:59:08заблокируемся на записи. Будем ждать,
- 2:59:10пока кто-нибудь придёт читать из этого
- 2:59:11канала. Если кто-нибудь приходит читать
- 2:59:13из буферизированного канала, а в буфере
- 2:59:15уже лежат какие-то значения, он
- 2:59:17моментально это значение вычитает и
- 2:59:19пойдёт дальше. То есть даже не будет
- 2:59:20блокироваться на чтении, потому что тут
- 2:59:22уже есть какие-то значения. И как только
- 2:59:23в буферизированном канале освободилось
- 2:59:25место, те, кто блокировались на записи в
- 2:59:27буферизированный канал, потому что там
- 2:59:29места не было, спокойно запишут своё
- 2:59:31значение и завершат своё выполнение. И
- 2:59:33после этого, кто бы не приходил читать
- 2:59:35из этого канала, он просто вычитает все
- 2:59:37эти значения по одному значению за
- 2:59:39каждый раз. И всё. Если мы потом ещё раз
- 2:59:41придём читать из буферизированного
- 2:59:43канала, там опять нету никаких значений,
- 2:59:45то мы заблокируемся и будем ждать, когда
- 2:59:47же кто-нибудь туда что-нибудь запишет. И
- 2:59:49тут есть некоторые проблемы, за которыми
- 2:59:51мы, как разработчики, должны очень
- 2:59:53внимательно следить. То есть вы могли
- 2:59:54заметить, что у нас только три рабочих.
- 2:59:57Мы три раза сходили в этот
- 2:59:58буферизированный канал и прочитали
- 3:00:00оттуда три значения. Но что будет, если
- 3:00:02мы туда пойдём четвёртый раз? Мы пошли
- 3:00:04четвёртый раз, запустили мы всего трёх
- 3:00:06рабочих, они отработали. Мы пошли
- 3:00:08четвёртый раз. Мы тут, извините меня,
- 3:00:10останемся навсегда, потому что мы будем
- 3:00:12ждать, пока кто-то запишет значение
- 3:00:14какое-то в канал, а уже некому писать.
- 3:00:16Уже все, кто могли записать, они уже
- 3:00:18туда никогда ничего не запишут, потому
- 3:00:19что они отработали, и мы заблокируемся
- 3:00:21тут навсегда. Такое состояние в
- 3:00:22программе называется deadlock. Ин сам
- 3:00:24отлавливает это состояние. Давайте
- 3:00:26посмотрим, как это выглядит. Уберу вот
- 3:00:28эти вот таймслипы.
- 3:00:29[аплодисменты]
- 3:00:31Неважно, у нас тут будет
- 3:00:32буферизированный канал или не
- 3:00:34буферизированный канал. Даже если у нас
- 3:00:35тут канал небуферизированный, всё равно
- 3:00:37нам вот передали три значения, мы три
- 3:00:40раза туда сходили, все они завершились.
- 3:00:42Потом мы идём четвёртый раз. То же
- 3:00:43самое. Мы будем ждать, пока кто-нибудь с
- 3:00:45той стороны придёт нам передавать уголь.
- 3:00:47Но уже никто не придёт, потому что у нас
- 3:00:48всего три рабочих запущено, а мы
- 3:00:50четвёртый раз пришли. Никто не придёт, и
- 3:00:51мы тоже навсегда останемся ждать, пока
- 3:00:53кто-нибудь с той стороны придёт. Это
- 3:00:55называется deadlock, повторюсь. Давайте
- 3:00:57посмотрим, как он выглядит. Мы трёх
- 3:00:59рабочих запустили, три раза в пункт
- 3:01:01выдачи пришли, а потом придём четвёртый
- 3:01:03раз. Запускаем программу.
- 3:01:07Все горуны заблокировались. Deadlock.
- 3:01:10Что это значит? У нас по сути под конец
- 3:01:12осталась только одна главная грутина.
- 3:01:13Вот этот вот главный поток выполнения,
- 3:01:15это тоже рутина, но она называется меain
- 3:01:18когда aрутина завершается, все остальные
- 3:01:20тоже за ней сразу же завершаются. Наша
- 3:01:22мега рутина три раза сходила в канал,
- 3:01:24вычитала три значения и пошла четвёртый
- 3:01:26раз. Четвёртый раз она пошла, но тут уже
- 3:01:29никто никогда туда ничего не отправит,
- 3:01:31потому что все три запущенных рабочих у
- 3:01:33нас завершились, и мы пришли четвёртый
- 3:01:34раз, и мы уже никогда не дождёмся, чтобы
- 3:01:37кто-то сюда что-то записал. Поэтому мы
- 3:01:40навечно застряли в этом месте. И нам
- 3:01:42Гошка говорит, что ты написал программу,
- 3:01:44которая в дедлоке. То есть мы никогда
- 3:01:46отсюда не выйдем, наша программа
- 3:01:48заблокировалась и уже ничего полезного
- 3:01:50она никогда не сделает. Поэтому deadog -
- 3:01:52это плохо, и нам нужно следить за тем,
- 3:01:54чтобы не происходило ситуаций, когда мы
- 3:01:56пытаемся достать из канала больше, чем в
- 3:01:59него было записано. Есть ещё несколько
- 3:02:01обратная ситуация, когда мы три раза
- 3:02:03читаем, но четыре раза пишем. То есть мы
- 3:02:06вот три раза сходили в наш канал,
- 3:02:08достали три значения, ну и всё, как бы
- 3:02:10мы там дальше выполняем какую-то свою
- 3:02:11логику, а у нас четыре было рабочих, и
- 3:02:14четвёртый рабочий придёт со своим углём
- 3:02:16в этот пункт передачи и также навечно
- 3:02:19тут останется. Но так как наша программа
- 3:02:21дальше выполняется, то то, что вот этот
- 3:02:24вот рабочий тут навечно остался, нам это
- 3:02:25не грозит дедлоком, потому что программа
- 3:02:27дальше выполняется. Есть ещё помимо этой
- 3:02:30горутины другие горутины, которые ещё
- 3:02:32успешно выполняются и ещё не
- 3:02:33заблокированы. Но это приведёт к такому
- 3:02:35эффекту, как утечка грутины. То есть у
- 3:02:37нас вот этот рабочий, он будет запущен.
- 3:02:39Он будет ждать, пока кто-нибудь
- 3:02:41прочитает из нашего канала значения, но
- 3:02:44никто сюда никогда не придёт. Поэтому
- 3:02:46наш рабочий будет утёкшей горутиной. Чем
- 3:02:49это плохо? Да тем, что если у нас этих
- 3:02:51утёкших горутин очень много, то это
- 3:02:53излишне перегружает наш компьютер
- 3:02:56бесполезной работой. Наш компьютер будет
- 3:02:58постоянно помнить об этих гартинах, он
- 3:03:00будет их как-то обрабатывать, хотя они
- 3:03:02по сути заблокировались в каком-то своём
- 3:03:04состоянии, больше из него никогда не
- 3:03:05выйдут. Это называется утечка грутин.
- 3:03:07Утечки рутин не так страшны, как
- 3:03:09дедлоки, но тем не менее их необходимо
- 3:03:11избегать для того, чтобы максимально
- 3:03:13оптимально расходовать ресурсы нашего
- 3:03:15компьютера или нашего удалённого
- 3:03:16сервера, на котором крутится наше
- 3:03:18приложение. Что такое рутины, мы
- 3:03:19разобрали. То есть мы можем написать
- 3:03:21ключевое слово го перед вызовом какой-то
- 3:03:23функции или даже метода. Тем самым мы
- 3:03:25запустим отдельный поток выполнения.
- 3:03:27Просто запустить отдельный поток
- 3:03:28выполнения нам особо ничего не даст,
- 3:03:30потому что мы его можем запустить, и
- 3:03:33сразу же наша главная горутина, наш
- 3:03:35главный поток завершится, и вместе с ним
- 3:03:37завершатся все остальные. Для того,
- 3:03:39чтобы нам синхронизировать их работу и
- 3:03:41обмениваться между рутинами информацией,
- 3:03:43мы используем каналы. Каналы бывают у
- 3:03:45нас буферизированы, бывают
- 3:03:46небуферизированные. Мы разобрали, в чём
- 3:03:48между ними отличие. Ну и давайте
- 3:03:49поговорим о том, для чего, в принципе, у
- 3:03:51нас существует такое понятие, как
- 3:03:52грутины. Кпример, шахтёром он был
- 3:03:54призван в первую очередь показать, как с
- 3:03:55технической точки зрения работают рутины
- 3:03:57и каналы. Но сейчас мы поговорим, как
- 3:04:00конкретно эти рутины и, в принципе,
- 3:04:01параллельная какая-то обработка данных
- 3:04:03используется на практике. Ну вот,
- 3:04:05допустим, мы с вами разрабатываем
- 3:04:06какой-то мессengжер. Мессжер популярный,
- 3:04:08пользуются им миллионы людей, очень
- 3:04:10большой онлайн, и в каждый момент
- 3:04:11времени через наш мессенджер летит очень
- 3:04:13большое количество сообщений. Ну и
- 3:04:15представим, что время обработки одного
- 3:04:16сообщения - это X. какая-то
- 3:04:18фиксированная величина, неважно, 1
- 3:04:19секунда или 1 минута, просто какая-то
- 3:04:21фиксированная величина X. Ну и смотрим,
- 3:04:23как бы выглядела обработка сообщений
- 3:04:25нашим мессенджером, если бы мы не
- 3:04:27пользовались никакими рутинами, если бы
- 3:04:28мы не использовали никакую параллельную
- 3:04:30обработку данных. Просто по старинке в
- 3:04:32один поток последовательно, сверху вниз,
- 3:04:34слева направо написали бы наш код по
- 3:04:36обработке сообщений. Ну, мы помним, что
- 3:04:38обработка сообщений, она занимает X.
- 3:04:40Соответственно, мы в каком-то слайсе бы
- 3:04:41собрали эти все сообщения и через цикл
- 3:04:43начали бы их обрабатывать. Первое
- 3:04:44сообщение обрабатываем, x времени
- 3:04:46потратили. Потом второе сообщение
- 3:04:48обрабатываем, уже 2x времени прошло.
- 3:04:50Третье сообщение обрабатываем, 3x
- 3:04:52времени прошло. Четвёртое сообщение
- 3:04:54обрабатываем, 4x времени прошло. И пятое
- 3:04:56сообщение обрабатываем, прошло 5x
- 3:04:57времени. Вот так вот мы обрабатываем
- 3:04:59сообщение одно за одним, потому что у
- 3:05:01нас с вами нет никаких грутин. И
- 3:05:03получается, пользователь, который
- 3:05:04отправил пятое сообщение, он будет
- 3:05:06ждать, пока наш сервер, пока наше
- 3:05:08приложение обработает все сообщения до
- 3:05:10него. То есть вместо того, чтобы просто
- 3:05:12спокойно взять и отправить свои
- 3:05:13сообщения, пользователь будет ждать,
- 3:05:15пока все сообщения до него один за одним
- 3:05:18по очереди будут обработаны. Сами
- 3:05:19понимаете, что если одновременно будет
- 3:05:21писать не пять человек, а 5 млн, то это
- 3:05:23будет вызывать очень большие проблемы,
- 3:05:24потому что люди просто не смогут
- 3:05:26дождаться, когда же, наконец-таки, их
- 3:05:27сообщение будет обработано, потому что
- 3:05:29они будут с каждым своим новым
- 3:05:30сообщением вставать в условную очередь
- 3:05:33обработки этих сообщений и ждать, пока
- 3:05:35все сообщения до этого будут обработаны
- 3:05:37и сервер наконец-таки дойдёт до
- 3:05:38обработки их сообщения. Это очень
- 3:05:40неэффективно, это очень долго и
- 3:05:41выглядит, на самом деле, очень грустно.
- 3:05:43Ну, а теперь посмотрим, как выглядела бы
- 3:05:44обработка сообщений, когда у нас
- 3:05:46используются рутины и параллельная
- 3:05:48обработка данных. Такая же ситуация.
- 3:05:49Пять пользователей одновременно
- 3:05:51отправляют в наш мессжер своё сообщение,
- 3:05:52а мы обрабатываем эти сообщения в пяти
- 3:05:54разных рутинах. Соответственно, у нас
- 3:05:56одна рутина обрабатывает первое
- 3:05:57сообщение, вторая рутина обрабатывает
- 3:05:58второе сообщение, третье, третье,
- 3:06:00четвёртое, четвёртое, пятое, пятое. И
- 3:06:01так как эти гартины у нас запущены
- 3:06:03параллельно, так как у нас эти сообщения
- 3:06:05обрабатываются параллельно, то все пять
- 3:06:06сообщений у нас уже обрабатываются не за
- 3:06:085x времени, а за 1x, то есть в пять раз
- 3:06:11быстрее. Ну и, соответственно,
- 3:06:12представляем, если у нас там не пять
- 3:06:14сообщений, а миллион, во сколько же раз
- 3:06:16мы быстрее сможем обрабатывать сообщения
- 3:06:18пользователей и насколько же приятнее
- 3:06:20будет мессенджером пользоваться? Вот я
- 3:06:21сопоставил эти два рисунка. И тут
- 3:06:23наглядно видно, что в то время, как у
- 3:06:25нас при параллельной обработке данных мы
- 3:06:27уже успели обработать все входящие
- 3:06:28сообщения, без параллельной обработки
- 3:06:30данных мы бы успели обработать только
- 3:06:32одно сообщение и потом только второе,
- 3:06:34потом только третье. И в итоге наш
- 3:06:36мессенджер без использования рутин
- 3:06:38работал бы очень плохо, очень медленно,
- 3:06:40им бы никто не пользовался. Просто
- 3:06:41потому, что мы все данные обрабатываем
- 3:06:42последовательно, один за одним. А когда
- 3:06:44этих данных становится очень много, то
- 3:06:46обработка данных по одному начинает
- 3:06:48вызывать большие проблемы. Поэтому нам
- 3:06:50на помощь приходят рутины, нам на помощь
- 3:06:52приходит параллельная обработка данных,
- 3:06:53параллельно выполнение нашего кода наших
- 3:06:55программ, которое позволяет кратно
- 3:06:57ускорить выполнение нашего кода,
- 3:06:59соответственно, предоставляя
- 3:07:00пользователям нашей системы, на нашем
- 3:07:02примере, пользователям нашего
- 3:07:03мессенджера гораздо более приятный
- 3:07:05сервис. А в некоторых случаях без
- 3:07:06использования параллельной обработки
- 3:07:08данных в принципе построения системы
- 3:07:10было бы невозможно. Ну и давайте пару
- 3:07:11слов поговорим о том, как вообще стало
- 3:07:13возможно такое явление, как параллельное
- 3:07:15выполнение программ. Если у нас в
- 3:07:16компьютере в конечном счёте всего лишь
- 3:07:18один процессор, как он сразу может
- 3:07:20выполнять несколько рутин? Как он сразу
- 3:07:22может выполнять несколько программ?
- 3:07:23Каждый из программ может тоже
- 3:07:24выполняться параллельно. Как это вообще
- 3:07:26стало возможно? А всё это стало возможно
- 3:07:28в тот момент, когда появились
- 3:07:29многоядерные процессоры. То есть в
- 3:07:31данном случае это процессор у нас
- 3:07:32четырёхъядерный. Раз ядро, два ядро, три
- 3:07:34ядро, четыре ядро. Каждое ядро - это
- 3:07:36очень, грубо говоря, маленький процессор
- 3:07:38внутри процессора. То есть многоядерные
- 3:07:40процессоры - это, по сути, одна какая-то
- 3:07:42схема, внутри которой есть несколько
- 3:07:45подпроцессоров таких. И вот уже каждое
- 3:07:47это ядро, каждый этот мини-процессор
- 3:07:49берёт на себя в один момент времени
- 3:07:51какую-то отдельную задачу. И мы уже, в
- 3:07:52свою очередь, как программисты,
- 3:07:54благодаря многоядерным процессорам,
- 3:07:55научились писать по-настоящему
- 3:07:57параллельные программы, которые
- 3:07:58действительно могут очень сильно
- 3:07:59ускорять обработку каких-то данных. Ну,
- 3:08:01например, те же самые сообщения в
- 3:08:03мессенджере. Поэтому, чем больше ядер у
- 3:08:05нас в процессоре, тем больше мы сможем с
- 3:08:07вами запустить параллельных задач,
- 3:08:08которые действительно ускорят время
- 3:08:10выполнения нашей программы.
- 3:08:13[музыка]
- 3:08:17Для того, чтобы продвинуться дальше, я
- 3:08:19вам покажу некий иной способ запуска
- 3:08:21грутин. Ну, способ тот же самый, просто
- 3:08:23выглядит немножко по-другому. До этого
- 3:08:24мы с вами запускали в гартине какую-то
- 3:08:26именованную функцию. Ну вот, допустим, у
- 3:08:27нас есть функция F. Она там в
- 3:08:29бесконечном цикле что-нибудь вводит на
- 3:08:30экран. Hello. Ну и там после каждого
- 3:08:33вывода будет спать 100 мсекунд,
- 3:08:36чтобы не засорять на вывод. После этого
- 3:08:38мы в мейне запускаем эту функцию фу в
- 3:08:41гарутине и можем подождать просто вот
- 3:08:43так вот по колхозному одну секунду в
- 3:08:47нашей мейнрутине, чтобы рутина фу
- 3:08:49чуть-чуть поработала. И потом из-за
- 3:08:51того, что главная рутина у нас
- 3:08:52завершится, рутина фу тоже сама
- 3:08:54завершится. Ну давайте проверим.
- 3:08:58Hello, hello. Hello, hello. Оп, секунда
- 3:08:59прошла. У нас гогарутина завершилась и
- 3:09:02выводы hello тоже завершились. То есть
- 3:09:04мы запускали рутины через какие-то
- 3:09:06именованные функции. Теперь я хочу
- 3:09:07показать, как можно запускать рутины при
- 3:09:09помощи анонимных функций. Для этого мы
- 3:09:11пишем здесь вместо какой-то конкретной
- 3:09:13функции, пишем go fun.
- 3:09:15[аплодисменты]
- 3:09:16И с помощью такого синтаксиса мы можем
- 3:09:18запустить анонимную функцию.
- 3:09:20Соответственно, в ней мы будем делать то
- 3:09:22же самое, что в функции F, только тут
- 3:09:24будем выводить анон. Запускаем. И как мы
- 3:09:28видим, у нас параллельно работает вывод
- 3:09:30нашей анонимной функции и вывод Hello.
- 3:09:32Они как бы работают параллельно,
- 3:09:33одновременно на двух разных ядрах
- 3:09:35процессора. И потом у нас секунда
- 3:09:37выходит, завершается рутина. Ну и они
- 3:09:39тоже две эти запущенные рутины у нас
- 3:09:41завершаются. То есть мы можем запускать
- 3:09:43рутины через заменованные функции, можем
- 3:09:44запускать через анонимные, но это всё
- 3:09:46равно будет рутина, которая параллельно
- 3:09:48остальным рутинам работает и конкурентно
- 3:09:50выполняется. Но это всё равно будет
- 3:09:52рутина полноценная, которая работает
- 3:09:54параллельно всем остальным рутинам.
- 3:09:55Неважно, запустили мы её через
- 3:09:56именованную функцию либо через
- 3:09:57анонимную. Для чего у нас вообще есть
- 3:09:59такая возможность запускать рутины через
- 3:10:01анонимные функции? Почему мы не можем
- 3:10:02всегда запускать рутины через
- 3:10:04именованные функции? Да, на самом деле,
- 3:10:06можем. То есть мы действительно можем
- 3:10:07всегда запускать рутины через
- 3:10:08именованные функции, но к вам с опытом
- 3:10:11придёт, что иногда действительно проще
- 3:10:13запустить гартинку через анонимную
- 3:10:15функцию, а не описывать целую отдельную
- 3:10:17именованную функцию для того, чтобы
- 3:10:18запустить какой-то небольшой кусок кода,
- 3:10:20который запускается лишь в одном месте и
- 3:10:22больше нигде не переиспользуется.
- 3:10:25[музыка]
- 3:10:30Далее нам необходимо познакомиться с
- 3:10:32такой конструкции, как select. Для чего
- 3:10:34вообще нужен селект? Предположим, у нас
- 3:10:37есть два канала разных. В первом канале
- 3:10:39нам будут приходить какие-то целые
- 3:10:40числа. Я назову его intч и создам канал
- 3:10:44для целых чисел. Во втором канале у нас
- 3:10:46будут приходить строки. Я назову его,
- 3:10:48ну, типа string channel. О'кей. Создали
- 3:10:51канал для интов и канал для строк.
- 3:10:53Теперь одна грутина нам будет в
- 3:10:56бесконечном цикле в этот канал
- 3:10:58что-нибудь писать.
- 3:11:00Ну, допустим, номер своей итерации. Тут
- 3:11:02я создам переменную i, которая равна
- 3:11:03единице. Будем писать I и каждый раз
- 3:11:05прибавлять I. То есть в бесконечном
- 3:11:07цикле мы будем записывать в этот канал
- 3:11:09один, потом два, потом три, ну и так
- 3:11:11далее. И запущу сразу же вторую рутину.
- 3:11:14В этой рутине я в бесконечном цикле буду
- 3:11:16писать одну и ту же строку.
- 3:11:19Я вот в этот канал беру, да, и пишу хай.
- 3:11:22Ну, можно также у нас завести переменную
- 3:11:24i и писать сюда не просто хай, а1,
- 3:11:27high2, high3, ну, и так далее. А для
- 3:11:29того, чтобы нам к этой строке прибавить
- 3:11:31число так, чтобы в итоге получилась
- 3:11:33строка, содержащая хая1 и так далее,
- 3:11:35воспользуемся стандартным пакетом str
- 3:11:37conf. В нём есть функция i toa, то есть
- 3:11:40это integer to as. As - это кодировка
- 3:11:42символов. В общем, просто функция eтуа.
- 3:11:44Туда передаём нашу i. В итоге у нас
- 3:11:46получится high1, high2, high3, ну и так
- 3:11:48далее. И, конечно же, будем прибавлять
- 3:11:49на каждой итерации нашу i. Плюс добавим
- 3:11:51time спскунд
- 3:11:54для того, чтобы у нас какая-то была
- 3:11:56небольшая задержка между записями в
- 3:11:58канал. Теперь мы измейна должны каким-то
- 3:12:00образом читать из этих самых каналов, из
- 3:12:03каналов целых чисел, из канала со
- 3:12:05строками. Как мы это можем сделать, если
- 3:12:08мы хотим вот читать из первого, и
- 3:12:10второго канала? То есть, если мы будем
- 3:12:11делать следующим образом. У нас,
- 3:12:13например, вот числа будут приходить из
- 3:12:16intчаннела и потом будем выводить на
- 3:12:18экран.
- 3:12:22И у нас строки будут приходить из
- 3:12:24строкового канала. Тоже можем вводить на
- 3:12:26экран.
- 3:12:30Ну и хорошо. То есть мы один раз
- 3:12:31прочитаем, второй раз прочитаем и
- 3:12:33завершимся. Но мы хотим как бы читать
- 3:12:35несколько раз, потому что у нас записей
- 3:12:36будет много в эти каналы. Можем всё это
- 3:12:38в цикле читать тоже. Пускай у нас будет
- 3:12:40бесконечный цикл, и наша программа по
- 3:12:42сути будет бесконечно выполняться. У нас
- 3:12:44бесконечно в эти каналы будет что-то
- 3:12:45записываться, а мы бесконечно из этих
- 3:12:47каналов будем что-то читать. Ну, давайте
- 3:12:48запустим программу.
- 3:12:51Ну, и как мы видим, вот рутины у нас
- 3:12:52пишут числа и строки в каналы свои, а мы
- 3:12:56в мейне в бесконечном цикле вычитываем
- 3:12:57эти каналы и выводим их на экран. Это
- 3:12:59всё, конечно, замечательно. Но теперь
- 3:13:01представим ситуацию, что у нас грутина,
- 3:13:03которая пишет в целочисленный канал, она
- 3:13:06после каждой записи будет спать целую
- 3:13:08секунду. Ну, то есть 1.000 мсекунд - это
- 3:13:10то же самое, что 1 секунда.
- 3:13:12Что произойдёт в таком случае? Давайте
- 3:13:13запустим, посмотрим.
- 3:13:15И мы видим, что у нас выполнение сразу
- 3:13:18же замедлилось почему-то. А если у нас
- 3:13:20тут будет 5 секунд, запускаем.
- 3:13:25И что-то как-то 5 секунд мы ждём
- 3:13:29чего-то. Чего же мы ждём? Мы запускаем
- 3:13:31наши рутины, они начинают писать в
- 3:13:33канал. Мы в мейне, в бесконечном цикле
- 3:13:35сначала читаем канал целых чисел. Мы
- 3:13:38прочитали оттуда число, вывели его на
- 3:13:40экран, потом прочитали строку из нашего
- 3:13:43строкового канала, вывели её тоже на
- 3:13:44экран, пошли на вторую итерацию и
- 3:13:47пытаемся прочитать из канала целых
- 3:13:48чисел. А у нас тут перед второй записью
- 3:13:50в этот канал рутина засыпает на 5
- 3:13:53секунд. Соответственно, мы в этом месте
- 3:13:55засыпаем на 5 секунд. И несмотря на то,
- 3:13:57что в строковый канал уже давно была
- 3:13:58записана строка, мы всё равно здесь ждём
- 3:14:01просто, пока в очередной раз наша
- 3:14:04рутина, которая пишет целые числа, у нас
- 3:14:05разблокируется и будет дальше записывать
- 3:14:07в канал. Мы тут просто ждём. Как бы нам
- 3:14:09вот заиметь такой инструмент, чтобы
- 3:14:11можно было как-то параллельно вот
- 3:14:13смотреть на эти два канала и если в один
- 3:14:15из них приходит какое-то значение, то
- 3:14:17выводить его на экран. А в Голенге есть
- 3:14:19такой инструмент, он называется select.
- 3:14:20Выглядит следующим образом. Мы пишем
- 3:14:22select, потом фигурные скобочки, и тут
- 3:14:25разные каналы мы ожидаем в разных, так
- 3:14:27называемых, кейсах. То есть первый кейс
- 3:14:29- это когда мы ждём наш канал с целыми
- 3:14:32числами. Второй кейс - это когда мы ждём
- 3:14:36наш канал со строками. И после двоеточия
- 3:14:39мы можем описать, что мы будем делать,
- 3:14:42если мы получим то или иное значение. То
- 3:14:44есть, если мы получили что-то из
- 3:14:45целочисленного канала, мы это вводим на
- 3:14:47экран и подписываем, что это
- 3:14:48целочисленный канал. Если мы что-то
- 3:14:50получили из строкового канала, то у нас
- 3:14:52выполнится вот этот вот блок кода, и мы
- 3:14:54выведем на экран то, что мы получили из
- 3:14:55строкового канала с соответствующей
- 3:14:57подписью. То есть мы приходим в
- 3:15:00бесконечный цикл, попадаем в самую
- 3:15:01первую его итерацию и здесь сразу же
- 3:15:03встречаем select. То есть, напоминаю,
- 3:15:05select он будет ждать тот кейс, который
- 3:15:08первый разблокируется у нас. Мы
- 3:15:10одновременно ожидаем разблокировку
- 3:15:12возможности прочитать из целочисленного
- 3:15:14канала и разблокировку возможности
- 3:15:16прочитать из строкового канала. Если
- 3:15:18что-то у нас сюда придёт в целочисленный
- 3:15:20канал, мы сразу же прочитаем это в
- 3:15:22переменную number. у нас этот кейс
- 3:15:24разблокируется, и мы выполним вот этот
- 3:15:26вот блок кода. Если нам что-то придёт из
- 3:15:27строкового канала, то мы здесь сразу же
- 3:15:29разблокируемся, прочитаем это значение
- 3:15:31строкового канала в переменную strр, и
- 3:15:32выполнится этот блок кода. то есть у
- 3:15:34нас, даже несмотря на то, что запись в
- 3:15:37целочисленный канал происходит раз в 5
- 3:15:39секунд, мы всё равно будем видеть записи
- 3:15:41в канал строковый и сможем оттуда
- 3:15:43читать, потому что сект он просто
- 3:15:44ожидает, пока кто-нибудь из них двоих
- 3:15:47разблокируется и выполнится, как только
- 3:15:49появится возможность прочитать или
- 3:15:50записать в какой-то из каналов, который
- 3:15:52написан после ключевого слова кейс. Ну,
- 3:15:54в данном случае мы читаем. Давайте
- 3:15:55запустим нашу программу, посмотрим, как
- 3:15:56она работает.
- 3:15:59И как мы видим, у нас чтение из
- 3:16:01строкового канала не дожидается, пока
- 3:16:04будет запись в числовой канал. Просто
- 3:16:06параллельная обработка идёт чтение из
- 3:16:08двух каналов. Какой канал первый у нас
- 3:16:10разблокируется, то есть в какой канал
- 3:16:12первый у нас будет попытка записать
- 3:16:14какое-то значение, такой кейс у нас и
- 3:16:16разблокируется. Вот это преимущество
- 3:16:18конструкции Select. Мы благодаря
- 3:16:19конструкции Select можем параллельно
- 3:16:21вычитывать из двух разных каналов. Или
- 3:16:23мы можем записывать даже в какие-то
- 3:16:25каналы в конструкции Select, но пока мы
- 3:16:26это не рассматриваем. Просто для общего
- 3:16:28развития говорю. Так, в основном
- 3:16:29конструкция Select используется для
- 3:16:30того, чтобы читать из двух разных
- 3:16:32каналов. И какой канал нам первый станет
- 3:16:33доступен для чтения, какой канал первый
- 3:16:35разблокируется, такой блок кода и
- 3:16:36выполнится. Если нам, допустим,
- 3:16:38предположим, в целочисленный канал и в
- 3:16:40строковый канал одновременно,
- 3:16:42единомоментно придёт значение и сюда, и
- 3:16:44сюда, тогда выполнится рандомный кейс.
- 3:16:46Либо первый, либо второй. Уже рандомом
- 3:16:48выберется, если сюда одновременно
- 3:16:49придёт. Но если придёт в целочисленный
- 3:16:51канал, а в строковом ещё ничего нет,
- 3:16:52выполнится целочисленный. И наоборот,
- 3:16:54если придёт в строковый, а в целочислен
- 3:16:56нам ещё ничего нет, выполнится в
- 3:16:57строковый. Ну, мы это и видим. Вот нам в
- 3:16:58строковый раз в 100 мскунд приходит
- 3:17:00значения, мы их обрабатываем. Потом хоп,
- 3:17:02пришло что-то в целочисленный канал. Ну,
- 3:17:04мы обработали целочисленный канал, и мы
- 3:17:06видим, что мы успели обработать уже в
- 3:17:07800-900 наших строковых значений, а
- 3:17:10целочисленный канал только 19, ну, 20
- 3:17:12там и и так далее. Вон 21. Если бы мы по
- 3:17:15старинке с вами это всё использовали,
- 3:17:17ну, я думаю, понятно, мы бы ждали каждый
- 3:17:19раз наш целочисленный канал и только
- 3:17:21после этого давали бы возможность
- 3:17:22обработать строковый канал. И тогда бы у
- 3:17:24нас сейчас было не 1.000 обработано
- 3:17:26строковых значений, а точно так же 21.
- 3:17:28Ну давайте ещё раз мы это посмотрим. Я
- 3:17:31уберу в принципе конструкцию Select,
- 3:17:33ключевые слова кейс, вот эти вот
- 3:17:34двоеточие,
- 3:17:36и получу то, что у нас раньше было, где
- 3:17:38мы просто последовательно сначала
- 3:17:39пытаемся прочитать из целочисленного
- 3:17:41канала, потом из строкового. Запускаем.
- 3:17:43Прочитали из целочисленного, прочитали
- 3:17:45из строкового. В строковом уже есть
- 3:17:47возможность прочитать, потому что туда
- 3:17:48уже горутинка вот эта вот строковая
- 3:17:50пишет, потому что она всего лишь 100
- 3:17:51мкунд поспала. Но мы залочились вот на
- 3:17:53этом чтении и ждём 5 секунд, пока наша
- 3:17:56грутина, которая пишет в целочисленный
- 3:17:57канал, соизволит туда что-нибудь
- 3:17:59написать. Ну и, соответственно, у нас
- 3:18:00вот идёт такая медленная обработка,
- 3:18:02последовательное чтение из каналов. Хотя
- 3:18:04мы бы могли уже сколько этих строковых
- 3:18:06значений обработать, а мы всё вот ждём,
- 3:18:08потому что долго обрабатываем
- 3:18:09целочисленные. Ну, ставим конструкцию
- 3:18:12Select. И теперь мы не будем ждать наш
- 3:18:15целочисленный канал, если что-то уже
- 3:18:16придёт в строковый. Запускаю. Ну и, как
- 3:18:19мы видим, мы можем спокойно обрабатывать
- 3:18:20приходящие строковые значения, не
- 3:18:21дожидаясь, пока разблокируется
- 3:18:23целочисленный канал. Целочисленный канал
- 3:18:25разблокировался, в него кто-то что-то с
- 3:18:26другой стороны готов передать. О'кей,
- 3:18:28читаем это, в переменную набрводим. Вот.
- 3:18:30Ну и потом дальше обрабатываем наш
- 3:18:31строковый канал. Почему это всё в
- 3:18:32бесконечном цикле? Ну, потому что если
- 3:18:34бы мы один раз, э, воспользовались
- 3:18:36селектом, прочитался бы какой-то
- 3:18:38рандомный у нас или первый
- 3:18:40освободившийся канал, мы бы его один раз
- 3:18:42вывели и всё, и селект бы закончился. Ну
- 3:18:43давайте проверим. Ну и как мы видим, нам
- 3:18:45вот первый пришёл строковый, мы его
- 3:18:46вывели и всё. Ну, программа завершилась,
- 3:18:49завершился, всё. Поэтому нам, чтобы
- 3:18:50циклично это всё читать бесконечно, мы
- 3:18:52это всё в бесконечный цикл пихаем. Бам.
- 3:18:54Ну и всё. Теперь мы читаем уже, сколько
- 3:18:56нам надо, просто бесконечное количество
- 3:18:57раз. Также у конструкции Select может
- 3:19:00быть секция default.
- 3:19:03Для чего она нужна? Она выполняется,
- 3:19:06если на момент достижения конструкции
- 3:19:09Select ещё ни один из кейсов не
- 3:19:12разблокировался. В таком случае у нас
- 3:19:14выполнится секция default. Ну, выглядит
- 3:19:16это следующим образом. Я наширутины
- 3:19:18сильно упростил. Они теперь один раз
- 3:19:20только будут что-то писать в канал и
- 3:19:22всё. До этого они спят какое-то время.
- 3:19:24Ну вот пускай наша функция, наша
- 3:19:26грутина, которая пишет в целочисленный
- 3:19:27канал, будет спать 200 мскунд. А наша
- 3:19:31грутина, которая пишет в строковый
- 3:19:32канал, также будет спать 100 мскунд. Ну
- 3:19:34и теперь давайте попробуем перед
- 3:19:36селектом тоже спать какое-то время.
- 3:19:3950 мскунд.
- 3:19:41То есть, что произойдёт. Мы запустим
- 3:19:43разгарутину, запустим два рутину. Первая
- 3:19:45рутина, перед тем, как записать в канал,
- 3:19:47будет спать 200 мсекунд. Вторая грутина
- 3:19:49перед тем, как записать что-то в канал,
- 3:19:51будет спать 100 мкунд. Мы в самом мейне
- 3:19:53после того, как запустили рутины, ещё
- 3:19:55заснули на 50 мскунд. Ну, как бы 50
- 3:19:57мкунд прошло, вторая рутина только через
- 3:20:00ещё 50 что-то начнёт писать в канал.
- 3:20:02Первая вообще через 150 только начнёт
- 3:20:04что-то писать в канал. То есть мы в
- 3:20:05момент, когда попадём на конструкцию
- 3:20:07Select, ещё ни один канал не будет
- 3:20:09затронут, ещё ни в один канал ничего не
- 3:20:11будет записано. Поэтому у нас отработает
- 3:20:13секция дефолт и выведится, что никакой
- 3:20:15канал не готов. Давайте проверим. Оп.
- 3:20:17Никакой канал не готов. Почему? Да
- 3:20:19потому что ещё ни сюда ничего не
- 3:20:20пишется, ни сюда ничего не пишется. Ну,
- 3:20:22в таком случае у нас отработала секция
- 3:20:23de дефолт. Чуть попозже мы разберём
- 3:20:25очень частый пример, когда эта секция
- 3:20:26default используется. Пока нам важно
- 3:20:28понять именно с технической точки
- 3:20:29зрения, как и когда выполняется какая
- 3:20:31секция. Если мы тут будем спать,
- 3:20:33например, 500 мскунд, в таком случае у
- 3:20:34нас уже и первая горутина успеет
- 3:20:36записать, и вторая уже успеет вот здесь
- 3:20:38вот принять попытку, так сказать,
- 3:20:39записать что-то в канал. И поэтому у нас
- 3:20:41в селекте отработает какой-то рандомный
- 3:20:43кейс, либо этот, либо этот, потому что
- 3:20:44уже оба канала будут доступны для
- 3:20:47чтения, будут доступны для
- 3:20:48разблокировки. Ну давайте запустим. Ну и
- 3:20:51мы видим, что вот отработал intann. Если
- 3:20:53чуть-чуть поиграться, то можно заметить,
- 3:20:55что не всегда отрабатывает именно intan.
- 3:20:57Иногда отрабатывает всё-таки строковый
- 3:20:58канал. Ну пока вот int, in int, in int.
- 3:21:02Опа, строковый. Мы прочитали строковый.
- 3:21:04Почему? Потому что, напоминаю, если у
- 3:21:06нас во время попадания в конструкцию
- 3:21:07Select два кейса готовы быть
- 3:21:09разблокированы, а так как у нас идёт
- 3:21:11запись и в целочисленный канал, и в
- 3:21:13строковый канал, то эти два кейса, ну,
- 3:21:15готовы быть разблокированы, не готовы
- 3:21:16сразу же прочитать что-то из канала и
- 3:21:18вывести на экран, то выберется рандомный
- 3:21:20кейс. Ну и так как какой-то кейс
- 3:21:21выбирается, то секция дефолт уже не
- 3:21:23отрабатывает. Теперь представим, что мы
- 3:21:24всё-таки опять наши 50 миссекунд спим, у
- 3:21:27нас всегда будет отрабатываться, что
- 3:21:28никакой канал не готов. Но если секции
- 3:21:30дефолт не будет, то сект будет
- 3:21:32блокироваться и ждать первой
- 3:21:33разблокировки. Какая первая
- 3:21:34разблокировка придёт, то он и выполнит.
- 3:21:36Ну, в нашем случае первым разблокируется
- 3:21:38строковый канал, потому что он после вот
- 3:21:40этого таймслипа ещё через 50 мскунд
- 3:21:42запишет хай, а целочисленный канал там
- 3:21:44только через 150 мскунд. Разница
- 3:21:46довольно большая, поэтому всегда будет
- 3:21:47отрабатывать строковый канал, потому что
- 3:21:49он первый разблокируется. Ну и вот у нас
- 3:21:50сколько не запускаю, будет строковый
- 3:21:52канал. Если строковый канал будет спать
- 3:21:54больше, чем целочисленный, тогда
- 3:21:56постоянно будет отрабатывать
- 3:21:57целочисленный канал, потому что он будет
- 3:21:58первым записываться и у нас всегда будет
- 3:22:00вот эта секция разблокироваться. Ну и,
- 3:22:02соответственно, сект отработает и пойдёт
- 3:22:04выполнение дальше. Оп. И теперь у нас
- 3:22:06постоянно целочисленный канал выводится.
- 3:22:08Таким образом, мы разобрали, как
- 3:22:10работает сект. Что с помощью селекта мы
- 3:22:11можем параллельно ждать прихода значений
- 3:22:14из двух каналов. Ну, можем из трёх, из
- 3:22:16четырёх, там и скольки угодно. Можем
- 3:22:18ждать из одного канала, вот сколько
- 3:22:19хотим. Также можем прописать секцию
- 3:22:21defол, которая будет вызываться, если на
- 3:22:23момент, когда наша программа дошла в
- 3:22:25своём выполнении до конструкции Select,
- 3:22:27не было ещё разблокировано ни одной
- 3:22:29секции. То есть не тут у нас никакого
- 3:22:30значения нету. мы не можем дальше
- 3:22:32выполнять программу. Не тут у нас
- 3:22:33никакого значения нету. Ну тогда
- 3:22:35отработает секция default. Повторюсь,
- 3:22:36для чего вообще вот эта секция default
- 3:22:38нужна, мы чуть попозже рассмотрим.
- 3:22:40Теперь мы знаем, что мы можем запускать
- 3:22:41рутины при помощи анонимных функций и
- 3:22:43познакомились с конструкцией Select,
- 3:22:45которая на самом деле довольно часто
- 3:22:46используется в связке с некоторыми
- 3:22:48другими примитивами для конкурентной
- 3:22:50работы. Сейчас мы постепенно с вами
- 3:22:52познакомимся с большинством основных
- 3:22:54примитивов go для параллельной
- 3:22:56конкурентной работы нашего кода, а потом
- 3:22:58посмотрим на прямо такой жирненький,
- 3:23:00мощный пример, который бы собирал в себе
- 3:23:02все наши пройденные инструменты, и можно
- 3:23:05было бы лицезреть, как они все работают
- 3:23:06сообща очереди разбираем, что такое
- 3:23:09хорошо, что такое плохо. Вот сейчас
- 3:23:11рутинки прошли, анонимные функции,
- 3:23:13узнали, что такое канальчики посмотрели,
- 3:23:15селекты посмотрели, что можно
- 3:23:16одновременно из двух разных каналов
- 3:23:17читать, не блокируюсь при этом, если
- 3:23:19какой-то канал очень долго идёт. Теперь
- 3:23:21идём дальше. Ну давайте рассмотрим такой
- 3:23:23базовый простой пример, когда нам вообще
- 3:23:25может понадобиться одновременно читать
- 3:23:26из двух каналов. Ну, предположим, мы
- 3:23:28пишем свою социальную сеть, опять же,
- 3:23:30какой-нибудь мессенджер, и мы хотим
- 3:23:32одновременно слушать сообщения от двух
- 3:23:34наших друзей. То есть у нас как один
- 3:23:36друг может написать нам что-то в личку,
- 3:23:38так и второй. Писать они эти сообщения
- 3:23:39будут с одной стороны канала, а мы,
- 3:23:41условно говоря, будем принимать эти
- 3:23:42сообщения с другой стороны канала. Ну
- 3:23:44давайте посмотрим на то, о чём я,
- 3:23:46собственно, говорю. Ну, первым делом
- 3:23:47опишем само сообщение. То есть у
- 3:23:49сообщения есть автор,
- 3:23:52его как-то зовут, и сам текст сообщения.
- 3:23:54Теперь нужно создать два канала, по
- 3:23:56которым нам эти самые сообщения от наших
- 3:23:58друзей будут приходить. Message chan 1.
- 3:24:01Я это назову make chanage. И тут мы
- 3:24:05можем с вами обратить внимание на то,
- 3:24:06что каналы у нас могут быть не только
- 3:24:08простых типов, вроде интеджера или
- 3:24:10строки, но также каналы у нас могут быть
- 3:24:12наших составных типов, там разные
- 3:24:14структуры наши, в том числе. Вот. И
- 3:24:16message
- 3:24:182, то же самое. Make chanage. Вот теперь
- 3:24:22мы с вами с имитируем двух этих наших
- 3:24:25друзей, которые как-то независимо от нас
- 3:24:27пишут нам сообщения. Ну о пишем мы этих
- 3:24:29самых друзей в гарутинах, которые я
- 3:24:32запущу через анонимную функцию. И в
- 3:24:34бесконечном цикле нам первый друг будет
- 3:24:36в наш первый канал с сообщениями писать
- 3:24:38следующее сообщение. Значит, мы
- 3:24:41следующим образом в канал можем писать
- 3:24:43структуру, то есть название канала,
- 3:24:45потом стрелочка и сразу же создание
- 3:24:47структуры. И созданная тут структура
- 3:24:49будет записана в канал. Автор у нас -
- 3:24:53это друг один и текст - это привет.
- 3:24:58О'кей. Ну и он будет пускай засыпать на
- 3:25:00условные там 100 мсекунд.
- 3:25:03Ну то же самое для второго друга у нас
- 3:25:05будет. Только тут уже канал 2, потому
- 3:25:08что другой человек пишет в свой канал.
- 3:25:10Друг 2 и другд нам будет писать, как
- 3:25:13дела. Всё точно так же. Пускай он спит
- 3:25:15100 мисекунд. Ну и давайте для начала по
- 3:25:17старинке будем читать из нашего
- 3:25:19бесконечного цикла. Просто по очереди.
- 3:25:21Сначала из первого канала, потом из
- 3:25:22второго. Ну значит, читаем сообщение
- 3:25:25MSG1. Я это назову из первого канала.
- 3:25:28Чан один. Ждём, когда нам сюда
- 3:25:29что-нибудь придёт. И когда нам сюда
- 3:25:30что-то придёт, мы выведем на экран. Я
- 3:25:33получил сообщение от
- 3:25:38В сообщении, переданном нам зашит автор
- 3:25:40этого сообщения.
- 3:25:42И текст сообщения
- 3:25:45также зашит в переданном сообщении.
- 3:25:49О'кей. После этого мы точно также будем
- 3:25:52ждать нашего второго друга, пока он нам
- 3:25:54что-нибудь напишет.
- 3:25:57Я получил сообщение
- 3:26:00от
- 3:26:02[аплодисменты]
- 3:26:03текст сообщения.
- 3:26:05[аплодисменты]
- 3:26:09О'кей, давайте посмотрим, что у нас
- 3:26:10вообще получится, как это будет
- 3:26:12работать. Запустим нашу программу.
- 3:26:16Ну, как мы видим, в принципе, всё
- 3:26:17работает так же, как мы ожидали. Мы
- 3:26:19получаем сообщения от друзей, от
- 3:26:21второго, от первого, от первого, от
- 3:26:22второго. И вот так вот по очереди мы от
- 3:26:23них сообщения читаем. Почему? Да, потому
- 3:26:25что мы просто их читаем. Сначала первое,
- 3:26:27потом второе. Теперь давайте представим,
- 3:26:29в принципе, аналогично предыдущему
- 3:26:31примеру, что вдруг у нас один друг вышел
- 3:26:34из сети, пошёл погулять, и он нам не
- 3:26:37пишет сообщение целых 10 секунд.
- 3:26:40[аплодисменты]
- 3:26:41И теперь запустим мы снова нашу
- 3:26:43программу.
- 3:26:44Мы получили сообщение от первого друга,
- 3:26:46от второго и всё. Хотя по факту мы-то с
- 3:26:50вами знаем, что нам второй друг уже
- 3:26:52давным-давно написал кучу сообщений, но
- 3:26:54мы их не можем получить, потому что мы
- 3:26:56читаем эти самые сообщения из канала
- 3:26:58просто по очереди. Сначала первое, потом
- 3:27:00второе. Таким образом, мы не дополучаем
- 3:27:03вовремя сообщение от второго друга.
- 3:27:06Можно даже это продемонстрировать
- 3:27:07следующим образом. Вот здесь вот я буду
- 3:27:09писать время отправки сообщения вторым
- 3:27:13другом. И вот здесь вот буду time now
- 3:27:16использовать. И я, собственно, выведу
- 3:27:17просто время. отправке этого сообщения.
- 3:27:19А тут я буду писать
- 3:27:22время получения сообщения от второго
- 3:27:26друга.
- 3:27:27Также. И мы посмотрим, насколько эти два
- 3:27:30времени отличаются. Ну, значит, вот
- 3:27:32время отправки сообщения вторым другом.
- 3:27:34Смотрим. 22
- 3:27:3648 минут 58 секунд. Теперь мы видим, что
- 3:27:41получили там сообщения на самом деле
- 3:27:43через 10 секунд, потому что мы ждали
- 3:27:47первого друга. То есть разница между
- 3:27:48отправкой и получением у нас была целых
- 3:27:5010 секунд. А теперь представим, что вот
- 3:27:52этот вот первый друг, он отлучился у нас
- 3:27:54не на 10 секунд, а на целый день. И что?
- 3:27:56И мы тогда просто, получается, сообщение
- 3:27:58от второго друга целый день не сможем
- 3:28:00получить. Ну, потому что мы просто будем
- 3:28:02блокироваться вот на этом вот канале, в
- 3:28:04этом месте. Это совсем не круто. Так
- 3:28:06работать не должно. Если нам один друг
- 3:28:07не пишет, это совершенно не значит, что
- 3:28:09мы не должны получать сообщения от
- 3:28:10второго друга. Ну, на помощь нам
- 3:28:12приходит уже изученный селект. мы будем
- 3:28:15получать уже эти самые сообщения из
- 3:28:16каналов не один за другим, а в селекте
- 3:28:20message 1 message chan 1. И здесь вот
- 3:28:23это вот наше сообщение будет.
- 3:28:26Это можем стереть. Тут, в принципе,
- 3:28:28точно так же будем получать наше второе
- 3:28:30сообщение из уже второго канала и писать
- 3:28:33уже известное нам уведомление о том, что
- 3:28:36мы получили какое-то сообщение. И всё. И
- 3:28:38теперь, несмотря на то, что у нас первый
- 3:28:40друг будет ссылать нам сообщения лишь
- 3:28:41раз в 10 секунд, мы будем стабильно и
- 3:28:44вовремя получать сообщения от нашего
- 3:28:45второго друга. Давайте посмотрим, как
- 3:28:47наша программа будет работать. И как мы
- 3:28:49видим, мы стабильно и вовремя получаем
- 3:28:51сообщение от второго друга. Когда нам
- 3:28:53первый друг что-то пишет, мы от него
- 3:28:54тоже получим сообщение. Сейчас. Опа,
- 3:28:57прошло 10 секунд, и мы получили
- 3:28:58сообщение от первого друга. Но пока нам
- 3:29:00первый друг не пишет, мы спокойно
- 3:29:02получаем сообщение от второго друга.
- 3:29:03Таким образом, благодаря конструкции
- 3:29:05Select, мы смогли единовременно
- 3:29:07параллельно вычитывать сообщение сразу
- 3:29:09из двух каналов и при этом не
- 3:29:10блокироваться на каком-то конкретном
- 3:29:12канале, если из него не приходят
- 3:29:14сообщения, в то время, когда мы, в
- 3:29:15принципе, были бы готовы обработать
- 3:29:17сообщение из второго канала. Ну, таким
- 3:29:20образом нам здесь пригодилась
- 3:29:21конструкция Select. Это такой один из
- 3:29:23возможных кейсов использования
- 3:29:24конструкции Select. Не сильно
- 3:29:26популярный, самый популярный кейс
- 3:29:28использования конструкции Select мы с
- 3:29:29вами рассмотрим далее, когда будем
- 3:29:31изучать, что такое контексты.
- 3:29:34[музыка]
- 3:29:39Далее нам с вами необходимо разобрать
- 3:29:40две очень близкие друг другу темы. Это
- 3:29:42закрытие каналов и аксиомы каналов. Одно
- 3:29:45невозможно понять без другого, поэтому
- 3:29:47разбираем это всё вместе. И я бы хотел
- 3:29:49обратить ваше внимание на значение слова
- 3:29:52аксиома. Аксиома - это положение,
- 3:29:54принимаемое без доказательств. То есть,
- 3:29:57если во время разбора аксим каналов вам
- 3:29:59будет что-то непонятно, вам будет
- 3:30:00непонятно, почему каналы работают именно
- 3:30:02так, как работают они иначе, то
- 3:30:04разработчики Голенга не посчитали нужным
- 3:30:06нам с вами что-то доказывать. Нам с вами
- 3:30:08остаётся только смириться с тем, как
- 3:30:10работают каналы, и попытаться уже за
- 3:30:12разработчиков ГОН как-то додумать,
- 3:30:14почему были приняты именно такие
- 3:30:15решения, а не другие. Ну, со значением
- 3:30:17слова аксиома, я думаю, мы с вами уже
- 3:30:19разобрались. Первым делом мы разберём
- 3:30:21закрытие каналов. Давайте посмотрим, что
- 3:30:23вообще такое закрытие каналов. Мы с вами
- 3:30:25каналы можем не только создавать,
- 3:30:27но ещё и каналы мы с вами можем
- 3:30:29закрывать. Для чего вообще нам может
- 3:30:30потребоваться закрывать канал, мы с вами
- 3:30:32разберём после того, как ознакомимся с
- 3:30:35аксиомами каналов. Вот, кстати, и они на
- 3:30:37правой части экрана, наши с вами аксиомы
- 3:30:39каналов. Давайте вообще посмотрим, что
- 3:30:40тут есть. Тут у нас есть столбцы и есть
- 3:30:44строки. Два столбца. Л канал и closed
- 3:30:47канал. То есть у нас может быть канал, а
- 3:30:50может быть канал закрытый. Закрытый
- 3:30:52канал - это, собственно, то, что
- 3:30:54происходит в шестой строчке нашего
- 3:30:55примера слева. Здесь канал закрывается.
- 3:30:57Что же такое канал? Нил канал - это
- 3:30:59когда мы с вами создаём канал следующим
- 3:31:01образом. Мы просто объявляем, что у нас
- 3:31:04есть переменная типа канал, но мы не
- 3:31:06создаём канал через каналы, как и мапы,
- 3:31:10мы всегда обязаны создавать через й. Но
- 3:31:12если мы вдруг это забыли сделать, тогда
- 3:31:14мы получим с вами нил канал. Вот этот
- 3:31:16вот самый нил канал. Давайте теперь
- 3:31:17разбираться с каждой клеточкой в этом
- 3:31:19самом примере. Но начнём с закрытых
- 3:31:21каналов. Я здесь оставлю комментарии.
- 3:31:23Создаём новый открытый канал. То есть
- 3:31:26канал всегда, когда создаётся по
- 3:31:28умолчанию он открытый. Через Makeйк нам
- 3:31:31всегда возвращается открытый канал.
- 3:31:32Канал станет закрытым только после того,
- 3:31:34как мы передадим его в встроенную
- 3:31:36функцию close. Хорошо, создаём новый
- 3:31:38открытый канал. Дальше тут я добавлю
- 3:31:40комментарий, что мы закрываем канал.
- 3:31:42Получается, начиная с восьмой строчки и
- 3:31:44далее, весь код, который будет как-то
- 3:31:46работать с каналом Ч, будет работать с
- 3:31:48закрытым каналом. А значит, что мы будем
- 3:31:50рассматривать вот этот вот столбец.
- 3:31:52Столбец, который отвечает за closed
- 3:31:53channel. Хорошо. И пойдём разбирать
- 3:31:56самую первую строчку этого столбца.
- 3:31:58Получается, на пересечении столбца и
- 3:31:59строки мы получаем наш результат. Что у
- 3:32:02нас тут? Закрытый канал. Закрыть. Что
- 3:32:04это значит? Это значит, что если мы с
- 3:32:06вами уже один раз закрыли канал и он у
- 3:32:08нас стал закрытым, если мы к этому
- 3:32:10закрытому каналу снова попробуем
- 3:32:12применить операцию закрытия, то мы
- 3:32:14получим панику. Я ещё раз буду закрывать
- 3:32:16наш канал. И тут подпишу, что я
- 3:32:18закрываю. закрытый канал. Давайте
- 3:32:21запустим нашу программу.
- 3:32:23И я получаю панику. Close of closed
- 3:32:25channel, то есть закрытие закрытого
- 3:32:27канала. Это одна из аксиом каналов,
- 3:32:30который мы с вами только что
- 3:32:31рассмотрели, что если мы попытаемся
- 3:32:33закрыть уже закрытый канал, то мы
- 3:32:35получим панику. Всё согласно нашей
- 3:32:37схеме. Закрытый канал, пробуем его
- 3:32:38закрыть, получаем панику. Всё это
- 3:32:40аксиома каналов. Нравится вам это или
- 3:32:42нет, но если вы будете закрывать уже
- 3:32:44закрытый канал, получите панику. Вот так
- 3:32:46вот. Почему? Да, потому что оно так
- 3:32:48работает. Хорошо, перемещаемся к
- 3:32:50следующей строке. Тут у нас опять же
- 3:32:52закрытый канал. И что делаем? read, то
- 3:32:54есть читаем из закрытого канала. Что мы
- 3:32:56должны получить? Значение по умолчанию.
- 3:32:59Ну, давайте посмотрим, как это работает.
- 3:33:00Опять же создаём канал, закрываем его, и
- 3:33:02тут мы будем читать какое-то значение из
- 3:33:05нашего закрытого канала.
- 3:33:08Читаю значение из закрытого канала и
- 3:33:12выведу это значение на экран.
- 3:33:15Ну, давайте запустим программу,
- 3:33:16посмотрим. И мы видим ноль. Ноль - это
- 3:33:19значение по умолчанию для интеджера. То
- 3:33:20есть получается, если канал у нас
- 3:33:22закрыт, то мы всё равно можем из него
- 3:33:24читать. Причём читать не один раз, а
- 3:33:26читать сколько угодно раз. Вот V1,
- 3:33:28например, V2, V3 у нас пускай будет. И
- 3:33:31вот я все эти V хочу вывести. V1, V2,
- 3:33:33V3, то есть value, value 1, value 2,
- 3:33:35value 3, 0, всё по нулям. То есть мы
- 3:33:38можем из закрытого канала читать, нам
- 3:33:40будет приходить значение по умолчанию. И
- 3:33:42тут точно так же, как с мапой. Если мы
- 3:33:45читаем из канала и можем получить
- 3:33:47значение по умолчанию, то мы также можем
- 3:33:49узнать о том, является ли полученное
- 3:33:51значение значением по умолчанию или нет.
- 3:33:54Это переменная ОК, которую мы
- 3:33:55опционально можем получать при чтении
- 3:33:57значений из канала. Тем самым мы узнаем,
- 3:33:59прочитанное значение является ли
- 3:34:01значением по умолчанию или нет. Если ок
- 3:34:03будет true, это значит, что мы получили
- 3:34:06настоящее записанное значение в канал
- 3:34:08где-то из другой горутины. Если ок у нас
- 3:34:10будет фSE, это значит, что полученное
- 3:34:12значение не было каким-то значением,
- 3:34:14записанным с другой рутины. Значит, это
- 3:34:17значение по умолчанию. Ну давайте я
- 3:34:18здесь сделаю ок,
- 3:34:20ок
- 3:34:223. И выведу все эти оки.
- 3:34:25Ок. Ок 1, ок 2, ок 3. Запускаем. И мы
- 3:34:29видим фS, false, фS. То есть все три
- 3:34:31значения, прочитанные были значениями по
- 3:34:33умолчанию. Таким образом, мы с вами
- 3:34:35сейчас должны обратить внимание и
- 3:34:37запомнить, что если у нас закрытый
- 3:34:38канал, то мы можем из него читать, но
- 3:34:41при этом получать мы будем с вами
- 3:34:42значение по умолчанию. И благодаря вот
- 3:34:44этой опциональной переменной ОК мы
- 3:34:46также, как в случае с мапами, можем
- 3:34:48узнавать, является ли полученное
- 3:34:49значение значением по умолчанию. Это
- 3:34:52была наша вторая аксиома каналов,
- 3:34:53которую мы только что прошли. Опять же,
- 3:34:55почему она так работает? Разработчики
- 3:34:57языка программирования Goeng так решили.
- 3:34:59Хорошо, переходим к следующей строке
- 3:35:01нашего закрытого канала. И это запись в
- 3:35:03закрытый канал. Если у нас закрытый
- 3:35:05канал и мы пишем в него, то получаем
- 3:35:07панику. Ну давайте посмотрим, как оно
- 3:35:09вообще работает. Ну, создаём новый
- 3:35:11открытый канал, закрываем его и после
- 3:35:13этого пробуем в закрытый канал записать
- 3:35:15какое-то значение. И тут я напишу, что я
- 3:35:18записываю значение в закрытый канал.
- 3:35:21Запускаем нашу программу и видим паника.
- 3:35:24Нам пишет sendlosed channel, то есть
- 3:35:26попытка записи в закрытый канал. Это
- 3:35:28тоже приводит к панике. Что мы уже
- 3:35:30вычленили из того, что мы разобрали? Что
- 3:35:33если у нас закрытый канал и мы пытаемся
- 3:35:35повторно закрыть этот закрытый канал, то
- 3:35:37у нас будет паника. Получается, канал
- 3:35:39нельзя закрывать больше, чем один раз.
- 3:35:42Канал мы закрываем только один раз.
- 3:35:44Хорошо. Дальше, если у нас закрытый
- 3:35:46канал и мы пробуем оттуда прочитать, мы
- 3:35:48получим значение по умолчанию и
- 3:35:50опционально можем узнать, является ли
- 3:35:52полученное значение значением по
- 3:35:53умолчанию или нет. И если полученное
- 3:35:56значение является значением по
- 3:35:57умолчанию, это значит, что канал у нас
- 3:35:59закрыт. Хорошо. И третье, то, что писать
- 3:36:03в закрытый канал мы не можем, мы получим
- 3:36:05панику. Получается, можно сделать
- 3:36:07выводы, можно немножечко за
- 3:36:08разработчиков языка программирования
- 3:36:10Коeng додумать, что закрытие канала -
- 3:36:12это своего рода сигнал о том, что с
- 3:36:15каналом мы больше работать не будем.
- 3:36:18Канал отработал, мы его закрываем. Два
- 3:36:20раза закрывать канал нельзя, потому что
- 3:36:22это свидетельствует о том, что наша с
- 3:36:24вами программа написана как-то не так.
- 3:36:26Мол, почему это мы два раза
- 3:36:28сигнализируем о том, что с каналом мы
- 3:36:30больше не будем работать? Если мы
- 3:36:32единожды уже просигнализировали о том,
- 3:36:34что с каналом мы не будем работать, то
- 3:36:35второй сигнал о том, что мы с каналом не
- 3:36:37будет работать, означает, что где-то в
- 3:36:39нашей программе ошибка. Это значит, что
- 3:36:40первый сигнал был ложный, потому что был
- 3:36:42второй. А значит, мы всё-таки после
- 3:36:44первого сигнала как-то продолжили
- 3:36:45работать с каналом. Вывод один: каналы
- 3:36:48закрываем лишь один раз. И это
- 3:36:50сигнализирует о том, что работа с этим
- 3:36:51каналом заканчивается. Также нам
- 3:36:53разработчики Голенга предоставляют
- 3:36:54возможность узнать, было ли прочитанное
- 3:36:56значение значением по умолчанию или нет.
- 3:36:58Ну и по тем же причинам, почему нельзя
- 3:37:00два раза закрывать канал, нельзя писать
- 3:37:02в закрытый канал. То есть, если был
- 3:37:04сигнал о том, что канал закрыт, это
- 3:37:06значит, что мы сказали, что больше с
- 3:37:08этим каналом никакой работы
- 3:37:10производиться не будет. Но после этого
- 3:37:11мы начали писать в канал, а значит, мы
- 3:37:13опять дали ложный сигнал о том, что
- 3:37:15канал не будет использоваться. Мы
- 3:37:16закрыли канал, тем самым сказали, что
- 3:37:18этот канал больше не будет
- 3:37:20использоваться. И после этого взяли и
- 3:37:22начали использовать канал. Получается,
- 3:37:23наврали. Получается, опять наше
- 3:37:25приложение написано неверно. Опять мы
- 3:37:27где-то просчитались и начали работать с
- 3:37:29каналом после того, как сказали, что с
- 3:37:31каналом мы больше работать не будем. Для
- 3:37:33чего вообще нам закрывать канал, если
- 3:37:35это может привести к проблемам? Если мы
- 3:37:37случайно два раза закроем канал, будет
- 3:37:39паника. Так, может, я вообще тогда не
- 3:37:40буду канал закрывать? Зачем оно мне?
- 3:37:42Если я закрою канал, я буду читать
- 3:37:43значение по умолчанию. Зачем мне читать
- 3:37:45значение по умолчанию? Я лучше буду
- 3:37:47читать именно то, что мне в этот канал
- 3:37:48передают. Зачем мне как бы читать
- 3:37:49значение по умолчанию? Очень странно. И
- 3:37:51зачем мне закрывать канал, если я вдруг
- 3:37:54случайно могу начать в него писать и
- 3:37:56получить панику? Ну как это так? Канал
- 3:37:57предназначен для того, чтобы в него
- 3:37:58писать, для того, чтобы из него читать.
- 3:38:00А если я закрываю канал, то я и писать в
- 3:38:02него не могу, и читать значения какие-то
- 3:38:04осмысленные я из него не могу. Зачем нам
- 3:38:06вообще нужно это закрытие каналов? Для
- 3:38:08того, чтобы понять, зачем вообще нужно
- 3:38:10это закрытие каналов, я вам предлагаю
- 3:38:12разобрать следующий пример. Допустим, у
- 3:38:14нас в Горутине через анонимную функцию
- 3:38:17запускается наш шахтёр. Шахтёр будет
- 3:38:20добывать уголь и передавать этот уголь в
- 3:38:22пункт передачи угля. В то время мы из
- 3:38:24главного потока, то есть из мейнга
- 3:38:26рутины, то есть из нашего прораба, будем
- 3:38:28приходить в этот пункт передачи угля и
- 3:38:30забирать переданный шахтёрам уголь. Ну
- 3:38:32хорошо. То есть у нас должен быть
- 3:38:33какой-то трансфер point, какой-то канал
- 3:38:37целочисленный, да, потому что мы с вами
- 3:38:38будем передавать уголь именно в целом
- 3:38:41числе. Майнер, то есть наш шахтёр, будет
- 3:38:43записывать в этот трансфер pointт
- 3:38:44добытый уголь, а мы с точки зрения
- 3:38:46нашего прораба будем этот самый уголь из
- 3:38:48канала, то есть из пункта передачи угля,
- 3:38:50вычитывать. После этого можно выводить
- 3:38:52на экран значение нашей переменной. Вот
- 3:38:54кол. И тут наша переменная кол. Давайте
- 3:38:56запустим программу. И в целом всё
- 3:38:57отрабатывает как надо. То есть мы в
- 3:38:59шахтёре добыли 10 угля и передали в
- 3:39:01пункт передачи угля. А со стороны нашего
- 3:39:03прораба, со стороны мейнга рутины, мы
- 3:39:05этот уголь из пункта передачи угля
- 3:39:06получили, после чего вывели на экран. И
- 3:39:08вот наша десяточка. Казалось бы, всё
- 3:39:09хорошо. А теперь представим, что наш
- 3:39:11шахтёр, когда будет ходить в шахту, он
- 3:39:14заранее не будет знать, сколько угля на
- 3:39:16самом деле находится в этой шахте. То
- 3:39:18есть как будто бы мы играем в Minecraft,
- 3:39:20условно говоря, и когда мы идём в
- 3:39:22пещеру, мы заранее не знаем, сколько там
- 3:39:23будет блоков угля. Три блока угля или 10
- 3:39:26блоков угля или 50 блоков угля, мы
- 3:39:28заранее не знаем. Но у нас задача
- 3:39:30сходить в пещеру и добыть весь уголь,
- 3:39:32который мы с вами там сможем найти. Для
- 3:39:34того, чтобы в нашем майнере сымитировать
- 3:39:36вот эту случайную величину количества
- 3:39:38добытого угля, я воспользуюсь следующим,
- 3:39:40так сказать, фокусом. Сейчас я напишу, а
- 3:39:41потом объясню, что я написал. У меня
- 3:39:43будет переменная iterations, и она у
- 3:39:46меня будет равна 3 + rand n. И тут я
- 3:39:50четыре передам. Что это значит? Я
- 3:39:52сказал, что в переменной iterations на
- 3:39:54каждом запуске программы будет какое-то
- 3:39:56случайное число. Промежуток всех
- 3:39:58возможных случайных чисел, которые могут
- 3:39:59попасть в iterations, находится от тёх
- 3:40:01до шести включительно. То есть на каждом
- 3:40:03запуске программы тут может быть либо
- 3:40:05три, либо четыре, либо пять, либо шесть.
- 3:40:07Почему так? Вспоминаем, что делает
- 3:40:09функция int n из пакета rand. int n она
- 3:40:11принимает себе какое-то число в
- 3:40:13аргументы и возвращает рандомное число
- 3:40:16от нуля включительно до n не
- 3:40:18включительно. То есть, если я вызываю
- 3:40:19round int n и передаю туда 4ты, то эта
- 3:40:22функция мне может вернуть случайное
- 3:40:23число либо ноль, либо один, либо два,
- 3:40:26либо три. Получается, на каждом запуске
- 3:40:28программы здесь будет какое-то рандомное
- 3:40:29число, либо 0, либо один, либо 2, либо
- 3:40:31три и плюс три. Получается, на каждом
- 3:40:33запуске программы будет случайное число
- 3:40:36в iterations. Либо 3 + 0, либо 3 + 1,
- 3:40:38либо 3 + 2, либо 3 + 3. Получается, у
- 3:40:41нас в итоге может быть либо три, либо
- 3:40:42четыре, либо пять, либо шесть. О'кей, с
- 3:40:44этим разобрались. Я благодаря вот этому
- 3:40:45фокусу я буду имитировать случайно
- 3:40:47количество угля. которая может оказаться
- 3:40:49в нашей шахте. Хорошо. И в цикле от
- 3:40:52одного дотеerations
- 3:40:55я буду записывать добытый уголь в наш
- 3:40:58пункт передачи угля. Ну и,
- 3:40:59соответственно, каждый раз я ещё буду
- 3:41:00засыпать там на какое-то время, пускай
- 3:41:02на 300 мскунд,
- 3:41:04чтобы добывать весь этот уголь не
- 3:41:06моментально, а с некоторой
- 3:41:07периодичностью. О'кей. Теперь у нас
- 3:41:10встаёт вопрос. Вот есть наш майнер. Он
- 3:41:12добывает уголь, причём какое-то
- 3:41:14случайное его количество. И мы заранее
- 3:41:16не можем узнать, сколько именно угля
- 3:41:19добудет наш майнер. Получается, мы со
- 3:41:21стороны нашего прораба заранее не знаем,
- 3:41:24сколько раз нам нужно прийти в этот
- 3:41:26пункт передачи угля. Допустим, я вот
- 3:41:28приду один раз, да, давайте я вот буду
- 3:41:30выводить на экран значение переменной
- 3:41:31iterations
- 3:41:32[аплодисменты]
- 3:41:34и потом выводить здесь наш добытый уголь
- 3:41:36суммарно. Запускаю iterations у нас
- 3:41:39четыре, то есть мы четыре раза с точки
- 3:41:41зрения нашего шахтёра были готовы прийти
- 3:41:43и передать добытый уголь. Но в сумме у
- 3:41:45нас добыто только 10 угля. Получается,
- 3:41:47ещё 30 угля где-то потерялось. Хорошо, я
- 3:41:49могу поступить следующим образом. Я буду
- 3:41:51ходить с точки зрения нашего прораба в
- 3:41:53пункт передачи угля несколько раз. То
- 3:41:55есть вычитывать из этого канала
- 3:41:56несколько раз. Но я вычитаю здесь три
- 3:41:58раза. Но итеerations там может быть и
- 3:42:00четыре, и пять, и шесть. То есть мы в
- 3:42:02любом случае с некоторой вероятностью
- 3:42:04можем не досчитаться нашего угля. Вот
- 3:42:07сейчас iterations 3, call 3. Да,
- 3:42:09действительно, сколько раз шахтёр добыл
- 3:42:10уголь, столько мы угля и взяли. Всё
- 3:42:12круто. Но тут у нас уже iteration 6
- 3:42:14может быть на очередном запуске
- 3:42:15программы, а мы всего лишь три раза
- 3:42:17сходили. Хорошо, мы можем ходить много
- 3:42:19раз в наш Transfи, но тогда это чревато
- 3:42:22тем, что у нас записей в канал будет
- 3:42:26меньше, чем попыток прочитать из этого
- 3:42:28канала.
- 3:42:30А это приведёт к дедлоку. Почему
- 3:42:32deadlock? Потому что у нас iterations на
- 3:42:35очередном запуске программы был четыре.
- 3:42:36Получается, записи в канал было всего
- 3:42:38лишь четыре. Мы прочитали эти четыре
- 3:42:40раза и потом начали пытаться прочитать
- 3:42:43пятый раз. Но так как в этот канал уже
- 3:42:45никто ничего больше не запишет, то мы
- 3:42:47никогда не сможем отсюда ничего
- 3:42:49прочитать. И мы тут блокируемся
- 3:42:50навсегда, получая тот самый deadlog. То
- 3:42:52есть deadlog, переводя на русский язык -
- 3:42:54это что-то вроде блокировка намертво. То
- 3:42:56есть наша программа намертво
- 3:42:58заблокировалась без возможности
- 3:42:59разблокировки. А значит, мы написали наш
- 3:43:01код опять неверно. Получается, мы не
- 3:43:03можем здесь просто вот так вот взять и
- 3:43:04пытаться как-то угадать, сколько же раз
- 3:43:07там будет это iterations. Мы не знаем.
- 3:43:09Эта переменна она вообще внутри шахтёра.
- 3:43:11У нас к ней нету доступа из нашего
- 3:43:12прораба. Мы не знаем, сколько раз шахтёр
- 3:43:15будет приносить нам уголь, потому что мы
- 3:43:16заранее не знаем, сколько в шахте угля.
- 3:43:18О'кей, мы могли бы попробовать в
- 3:43:20бесконечном цикле читать этот сам уголь.
- 3:43:22То есть я вот создам переменну кол. И в
- 3:43:24бесконечном цикле я буду получать
- 3:43:26значение, которое нам будет передавать
- 3:43:28шахтёр, и прибавлять его к переменной
- 3:43:30кол.
- 3:43:32Хорошо, казалось бы, всё круто. Давайте
- 3:43:33даже на каждой итерации буду выводить
- 3:43:35значение переменной кол наше очередное.
- 3:43:37О'кей, получается, мы точно получим
- 3:43:39столько угля, сколько нам было передано.
- 3:43:42Но тут тоже есть проблема.
- 3:43:45Мы прочитали весь уголь. Как вы видели,
- 3:43:47у нас итеations 5. Значит, пять блоков
- 3:43:48угля нашёл наш шахтёр. 1 2 3 4 5. То
- 3:43:52есть все пять раз по 10 угля шахтёр нам
- 3:43:55передал. Мы всё это приняли. Но опять
- 3:43:57дедлок. Почему? Да потому что когда
- 3:43:59шахтёр последний раз передал уголь, мы
- 3:44:00этот уголь получили, прибавили, вывели.
- 3:44:02Но так как у нас бесконечный цикл, мы
- 3:44:04пошли на следующую трацию, опять начали
- 3:44:06ждать тут, пока нам кто-нибудь передаст
- 3:44:07этот уголь. А уголь нам никто не
- 3:44:09передаст, потому что ШахтёР уже на этот
- 3:44:11момент завершил своё выполнение. И мы
- 3:44:13опять попали в дедлок. Что же делать?
- 3:44:15Казалось бы, мы с вами тут в какой-то
- 3:44:16безвыходной ситуации. Мы заранее не
- 3:44:18знаем вообще, когда нам нужно прекращать
- 3:44:20чтение и как сделать так, чтобы не
- 3:44:21ловить эти дедлоки, при этом всегда
- 3:44:23вычитывать столько угля, сколько нам
- 3:44:25было передано. Тут нам на помощь
- 3:44:27приходят закрытые каналы. Давайте
- 3:44:29попробуем закрыть наш канал. Напоминаю,
- 3:44:31канал мы должны закрывать тогда, когда
- 3:44:33предполагается, что работа с этим
- 3:44:35каналом у нас завершается. Хорошо
- 3:44:37получается, мы с точки зрения нашего
- 3:44:39прораба, мы не знаем, когда будет
- 3:44:41завершение работы с каналом, потому что
- 3:44:42мы просто ждём, когда нам шахтёр
- 3:44:44принесёт уголь, и мы не знаем, сколько
- 3:44:45там у него угля ещё осталось. Зато со
- 3:44:48стороны шахтёра мы знаем, когда работа с
- 3:44:50каналом должна прекратиться. А должна
- 3:44:52прекратиться она тогда, когда мы донесём
- 3:44:54весь свой уголь в пункт передачи угля.
- 3:44:56Когда мы донесли весь уголь в пункт
- 3:44:58передачи угля, когда этот уголь весь
- 3:45:00вычитал наш прораб, то канал больше не
- 3:45:02будет использоваться, и мы можем его
- 3:45:03закрыть таким образом. Хорошо, закрыли
- 3:45:06канал. И теперь на каждой итерации,
- 3:45:08когда мы будем вычитывать значение из
- 3:45:10этого канала, если в очередной раз мы
- 3:45:13случайно вычитаем значение по умолчанию,
- 3:45:15а мы узнаем об этом благодаря вот этой
- 3:45:17переменной ОК, это значит, что канал
- 3:45:19закрыт. А если канал закрыт, это значит
- 3:45:21работа с этим каналом у нас завершается.
- 3:45:23Получается, если не ок. А если не ок,
- 3:45:26это значит, что мы прочитали значение по
- 3:45:27умолчанию, а это значит, что канал
- 3:45:29закрыт. А если канал закрыт, значит, в
- 3:45:31него никто уже ничего не напишет. О'кей,
- 3:45:33получается, если не ок, это значит, что
- 3:45:36шахтёр обработал всю шахту.
- 3:45:40И мы можем завершить наш бесконечный
- 3:45:43цикл благодаря ключевому слову брейк. Ну
- 3:45:45и тут в конце уже я выведу суммарно
- 3:45:48добытый уголь. Вот он наш кол. Запускаем
- 3:45:52программу. Ieration 6. Получается,
- 3:45:54шахтёр шесть раз будет нам добывать и
- 3:45:56передавать уголь. И мы все шесть раз
- 3:45:58корректно этот самый уголь получили. И
- 3:46:01потом, когда Шахтёр передал весь свой
- 3:46:03уголь, он закрыл канал, а мы уже со
- 3:46:05стороны нашего прораба увидели, что
- 3:46:07канал закрыт, и сказали, что шахтёр
- 3:46:09обработал всю шахту, и завершили наш
- 3:46:11бесконечный цикл. Именно для этого нам и
- 3:46:14нужно закрывать каналы. Закрытие канала,
- 3:46:16оно сигнализирует всем, кто работает с
- 3:46:19этим каналом, о том, что, собственно,
- 3:46:21работа с каналом завершилась. И больше
- 3:46:23нет смысла пытаться что-то из этого
- 3:46:25канала прочитать. Больше нет смысла
- 3:46:27ходить в этот пункт передачи угля: "Всё,
- 3:46:29канал закрыт". Значит, работа с ним
- 3:46:30завершена. А значит, мы однозначно можем
- 3:46:32узнать, что канал действительно закрыт,
- 3:46:34и перестать ходить бесконечно в этот
- 3:46:35пункт передачи угля. Получается,
- 3:46:37закрытие канала - это неотъемлемая часть
- 3:46:39работы с этими самыми каналами, и оно
- 3:46:41просто необходимо для того, чтобы
- 3:46:42корректно организовывать нашу
- 3:46:44параллельную работу в приложении. Потому
- 3:46:46что просто убрав это самое закрытие
- 3:46:48канала, прораб уже никогда не узнает,
- 3:46:50что шахтёр больше никогда не принесёт
- 3:46:52уголь. А значит, прораб будет бесконечно
- 3:46:54пытаться что-то из пункта передачи угля
- 3:46:57принять. А это у нас приведёт к дедлоку,
- 3:47:00потому что, повторюсь, если мы пытаемся
- 3:47:02прочитать что-то из канала, в который
- 3:47:04уже никто ничего не запишет, это значит,
- 3:47:06мы навечно тут заблокируемся, и наша
- 3:47:07программа больше ничего полезного уже не
- 3:47:09сделает. Это и называется deadlock.
- 3:47:12Также хотел бы вам показать, как ещё
- 3:47:14возможно читать какое-то значение из
- 3:47:16канала более красиво. В Гошке есть
- 3:47:18следующая конструкция: цикл forф,
- 3:47:20значение какое-то value, range и тут
- 3:47:23название канала. Что тут будет
- 3:47:25происходить? Каждый раз, когда в канал
- 3:47:26Transfer Point будет записано какое-то
- 3:47:28значение, у нас будет запускаться
- 3:47:29очередная итерация цикла, и в мы получим
- 3:47:32именно значение, которое было записано в
- 3:47:34канал. И как только трансфер point будет
- 3:47:36закрыт, тогда автоматически этот цикл
- 3:47:38завершится. И нам уже не придётся
- 3:47:39получать вот это самое ОК и проверять
- 3:47:41его. Нет, как только канал закроется,
- 3:47:43этот циклфор сразу же завершится. Ну,
- 3:47:45получается, мы тут будем вычитывать это
- 3:47:46V из канала и прибавлять к нашей
- 3:47:48переменной call и точно также выводить
- 3:47:50на экран. Вот это я закомментирую,
- 3:47:54запущу программу.
- 3:47:57Ну и как мы видим, наша программа
- 3:47:58отрабатывает точно так же. Получается
- 3:48:00вот это это короткая запись вот этого
- 3:48:02цикла. Просто для удобства, для того,
- 3:48:04чтобы нам не приходилось руками
- 3:48:05проверять, закрыт канал или нет. Просто
- 3:48:07если канал закрывается, то у нас этот
- 3:48:09цикл завершается. И точно также этот
- 3:48:11цикл, он будет блокироваться навечно.
- 3:48:13Если канал с другой стороны никто не
- 3:48:14будет закрывать. Закрытие канала
- 3:48:16отсутствует, а значит, мы будем
- 3:48:17бесконечно в этом цикле ждать какого-то
- 3:48:19значения из канала. Оно уже никогда к
- 3:48:21нам не придёт. Поэтому ловим deadlock.
- 3:48:23Мы с вами наглядно рассмотрели, для чего
- 3:48:25каналы нужно закрывать. Потому что у нас
- 3:48:27могут быть какие-то коллеги, которые
- 3:48:29используют наш пакет и какие-нибудь
- 3:48:31каналы из нашего пакета. Мы им
- 3:48:33предоставляем возможность из этого
- 3:48:34канала что-то читать, и они через
- 3:48:36циклфор из нашего канала какие-то
- 3:48:38значения читают. Наши коллеги заранее не
- 3:48:40знают, сколько раз мы в этот канал будем
- 3:48:41писать. Нет, они просто читают все
- 3:48:43значения, которые придут из этого
- 3:48:44канала. И если мы канал со своей стороны
- 3:48:47не закроем, то наши коллеги в один
- 3:48:49момент обнаружат, что они ловят в своих
- 3:48:50программах дедлоки, потому что мы не
- 3:48:52закрываем канал, а значит, мы никак не
- 3:48:55сигнализируем о том, что работа с этим
- 3:48:57каналом завершается, а значит, наши
- 3:48:59коллеги никогда и не узнают, когда нужно
- 3:49:01переставать читать из канала. Но стоит
- 3:49:03нам просто начать закрывать канал тогда,
- 3:49:05когда действительно работа с ним
- 3:49:06завершается, и наши коллеги смогут
- 3:49:08корректно вычитывать все значения из
- 3:49:09канала, не ловить дедлоки и продолжать
- 3:49:12выполнение своей программы. Таким
- 3:49:13образом, мы с вами посмотрели, для чего
- 3:49:15нам нужно закрывать канал и как себя
- 3:49:17ведёт Goeng при различных операциях с
- 3:49:19закрытым каналом. Попытка закрыть уже
- 3:49:21закрытый канал - это паника. Попытка
- 3:49:24прочитать что-то из закрытого канала -
- 3:49:26это значение по умолчанию с возможностью
- 3:49:27дополнительно прописать переменную ОК,
- 3:49:29которая бы сигнализировала о том,
- 3:49:31является ли полученное значение
- 3:49:32значением по умолчанию или нет. И запись
- 3:49:34в закрытый канал приведёт к панике.
- 3:49:36Почему? Ну, потому что, опять же,
- 3:49:38разработчики языка программирования так
- 3:49:40решили. Это аксиомы. Просто разработчики
- 3:49:43Гонга решили, что оно будет так, поэтому
- 3:49:45оно так и работает. Но мы можем
- 3:49:46попробовать как-то додумать за
- 3:49:48разработчиков, почему они решили именно
- 3:49:50такое поведение встроить вн. Да, скорее
- 3:49:52всего, потому что если мы канал
- 3:49:54закрываем, а после него пишем в канал,
- 3:49:56это значит, что где-то мы с вами
- 3:49:57просигнализировали о том, что работа с
- 3:49:59каналом у нас прекращается. Те, кто
- 3:50:01работали с этим каналом, что-то из него
- 3:50:02читали, они прекращают читать из этого
- 3:50:05канала и забывают про этот канал. А
- 3:50:07после этого мы какое-то значение ещё в
- 3:50:09канал записали. Прочитает ли кто-то это
- 3:50:11значение? Очевидно, нет. Если мы здесь
- 3:50:13вот после закрытия канала что-то в этот
- 3:50:14канал ещё захотим записать, то мы,
- 3:50:16конечно же, словим панику. Отправка
- 3:50:18значения в закрытый канал. Но почему?
- 3:50:20Да, всё правильно. На самом деле так и
- 3:50:21должно быть. Потому что когда мы с вами
- 3:50:23просигнализировали о том, что канал
- 3:50:25закрыт, это значит, что мы сказали
- 3:50:26нашему прорабу, что весь уголь добыт, я
- 3:50:28больше ничего не буду передавать.
- 3:50:30Хорошо, наш прораб перестал приходить в
- 3:50:32этот пункт передачи угля и завершился. А
- 3:50:35мы после этого ой-ой-ой, подожди, вот
- 3:50:37ещё вот ещё 10 угля. Ну, это
- 3:50:39действительно очень большая проблема,
- 3:50:40что мы какие-то значения, которые пишем
- 3:50:41в канал, мы их не дочитаем. Допустим, у
- 3:50:43нас этот канал служит каналом для
- 3:50:45передачи сообщений в нашей социальной
- 3:50:47сети, да, мы разрабатываем соцсеть, и
- 3:50:48нам вот пишут какие-то сообщения наши
- 3:50:50друзья, а мы эти сообщения через циклфор
- 3:50:52вычитываем. И тут в один момент нам
- 3:50:54закрывают канал, то есть говорят, что
- 3:50:55больше сообщений не будет, и всё, и мы
- 3:50:57не читаем входящее сообщение. А тут
- 3:50:59после этого кто-то ещё записал сообщение
- 3:51:01в канал. Так мы его не прочитаем,
- 3:51:03сообщение потеряется, пользователи будут
- 3:51:04недовольны со словами: "А почему в вашей
- 3:51:06соцсети не приходят сообщения?" Я же
- 3:51:08отправил его. Почему никто не прочитал?
- 3:51:10Да потому что канал уже был закрыт. А
- 3:51:11это значит, что больше не планировалось
- 3:51:13никакой работы с этим каналом. Так что в
- 3:51:15целом, если подумать, то все эти аксиомы
- 3:51:17довольно логичны. Просто с опытом к вам
- 3:51:19придёт понимание, почему было сделано
- 3:51:21именно так, а не иначе. Но разработчики
- 3:51:23Голенга всё равно не посчитали нужным
- 3:51:24нам с вами объяснять, почему было
- 3:51:26сделано именно так, а не иначе. Поэтому
- 3:51:28это просто называется аксиомы каналов.
- 3:51:30Хорошо, мы с вами разобрали, для чего
- 3:51:32канал закрывать. Мы с вами разобрали,
- 3:51:33что произойдёт, если мы будем
- 3:51:34производить те или иные операции
- 3:51:35закрытым каналом. остался нил канал.
- 3:51:38Давайте вот этот примерчик я пока сотру.
- 3:51:41Вот у нас есть самый обычный канал,
- 3:51:44пускай будет строковый, и мы его
- 3:51:45создаём. Но если мы создаём канал
- 3:51:47следующим образом, то это уже нел канал.
- 3:51:49Нил канал он у нас должен быть не
- 3:51:52проинициализирован. Вот это это л канал.
- 3:51:54Ну давайте попробуем закрыть нашл канал.
- 3:51:56Запущу программу. Закрытие нил канала.
- 3:51:59Паника. Хорошо. Давайте попробуем что-то
- 3:52:01прочитать и что-то записать в нашл
- 3:52:03канал. Ну, я запущу гарутину через
- 3:52:05анонимную функцию,
- 3:52:07и эта рутина будет в наш канал писать
- 3:52:09значение hello. В мейгарутине я буду
- 3:52:12читать значение из этого канала, после
- 3:52:14чего попытаюсь вывести на экран.
- 3:52:18Ну, давайте запустим нашу программу и
- 3:52:20увидим deadlock. Почему deadlock? Потому
- 3:52:22что одна рутина у нас заблокировалась
- 3:52:23навечно при попытке внил канал что-то
- 3:52:25записать. Это вот этот вот случай. А
- 3:52:27вторая грутина заблокировалась навечно
- 3:52:29при попытке из Нил канала что-то
- 3:52:30прочитать. Это вот этот вот случай.
- 3:52:32Получается, у нас работа с NI каналом -
- 3:52:35это, в принципе, с самого начала
- 3:52:36какая-то нездоровая затея. Мы не можем в
- 3:52:38Nл канал писать, мы не можем из Нил
- 3:52:40канала читать, мы не можем его
- 3:52:42закрывать, мы ничего снил каналом делать
- 3:52:44не можем. Что это значит? Это значит,
- 3:52:45что каналы всегда нужно инициализировать
- 3:52:48через й. Сейчас же мы запустим. Всё
- 3:52:50хорошо. Просто благодаря тому, что мы
- 3:52:52нашл канал проинициализировали, это уже
- 3:52:55нел канал, и мы смогли спокойно из одной
- 3:52:58рутины передать значение, а из другой
- 3:52:59прочитать и вывести его на экран. Ну,
- 3:53:01можно сделать вывод, что с нил каналами
- 3:53:03лучше не работать и каналы всегда хорошо
- 3:53:05бы инициализировать. И это будет
- 3:53:06правильный вывод. Ни разу я в своей
- 3:53:08карьере, ни разу за весь мой опыт я не
- 3:53:10встречал какой-то необходимости
- 3:53:12осознанно держать Нил канал. Канал
- 3:53:14всегда надо инициализировать. И никаналы
- 3:53:16- это уже, скорее всего, какая-то
- 3:53:18большая ошибка в нашей программе. Таким
- 3:53:19образом, мы с вами разобрали две темы,
- 3:53:21которые очень важны для понимания того,
- 3:53:23как вообще работать с каналами. Первое -
- 3:53:25это закрытие канала. Мы разобрали, что
- 3:53:26это такое, для чего нужно. И второе -
- 3:53:28это аксиомы каналов, то есть то, как
- 3:53:30наши каналы себя ведут в зависимости от
- 3:53:32разных условий. Самое главное, что тут
- 3:53:34нужно запомнить - это то, как
- 3:53:35производится работа с закрытым каналом.
- 3:53:38Мы канал можем закрывать, но уже
- 3:53:39закрытый канал ещё раз закрывать нельзя,
- 3:53:42это будет паника. Из закрытого канала мы
- 3:53:44с вами можем читать, тем самым будем
- 3:53:45получать значение по умолчанию и
- 3:53:47опционально ещё переменную ОК, которая
- 3:53:49бы нам сигнализировала о том, является
- 3:53:50ли полученное значение значением по
- 3:53:52умолчанию или нет, благодаря чему мы
- 3:53:53сможем узнать, закрыт ли канал или нет.
- 3:53:56А если канал закрыт, это значит, попытки
- 3:53:58из него что-то прочитать можно
- 3:53:59прекращать. Уже ничего полезного мы
- 3:54:01оттуда не вычитаем. Ну и, конечно же, в
- 3:54:03закрытый канал, если мы попробуем что-то
- 3:54:05записать, это тоже будет паника. Мол,
- 3:54:07если ты канал закрыл, значит ты уже
- 3:54:09сказал, что работа с каналом прекращена,
- 3:54:11а значит, если ты после этого что-то
- 3:54:12попытаешься в канал записать, значит, ты
- 3:54:14где-то просчитался. И в твоей программе
- 3:54:16ошибка.
- 3:54:18[музыка]
- 3:54:23Следующая тема, которую нам необходимо
- 3:54:24разобрать - это контексты. Что такое
- 3:54:27контексты вообще, для чего они
- 3:54:28используются, как работают, сейчас мы с
- 3:54:30вами разберём. Мы до этого, когда
- 3:54:31запускали сразу несколько грутин, мы вот
- 3:54:33их могли запустить, и они рано или
- 3:54:35поздно заканчивали своё выполнение. Но у
- 3:54:37нас могут возникнуть в программе
- 3:54:39ситуации, когда мы захотим завершить
- 3:54:42часть наших рутин или завершить сразу же
- 3:54:44все горутины по какой-то команде. Эту
- 3:54:46возможность нам предоставляет контекст.
- 3:54:49То есть мы можем несколько наших грутин
- 3:54:52объединить в какой-то контекст, в
- 3:54:54какой-то логический набор, в какое-то
- 3:54:55множество их объединить, ну или, говоря
- 3:54:57языком гошников, запихнуть несколько
- 3:54:59грутин в один контекст. И потом мы при
- 3:55:02помощи этого контекста, в который мы
- 3:55:03объединили какие-то грутины, мы сможем
- 3:55:06эти самые грутины взять, да, и прибить,
- 3:55:07то есть взять и завершить их выполнение.
- 3:55:09Нам контексты позволяют разделять наше
- 3:55:12приложение на какие-то логические
- 3:55:13модули. То есть у нас может быть, к
- 3:55:15примеру, всё наше приложение целиком и
- 3:55:18отдельно модуль там для работы с базой
- 3:55:20данных, например. Вот это вот функции
- 3:55:22бу. Это грутинки бу, которые были
- 3:55:24запущены от функции бу. Мы это всё
- 3:55:26разберём сейчас в коде, но пока вот так
- 3:55:27на словах разбираем. Ну а вот это
- 3:55:28какие-то другие функции фу, пока
- 3:55:30неважно, что они делают, вот эти
- 3:55:31горутинки. Вот у нас есть рутинки бу,
- 3:55:33которые ходят в базу данных. И мы в один
- 3:55:35момент в нашем приложении захотели взять
- 3:55:37и эти горутинки остановить. Мы поняли,
- 3:55:39что у нас больше не будет работы никакой
- 3:55:41с базой данных. Просто берём и эти
- 3:55:42горутинки все хотим остановить. Хорошо,
- 3:55:44если мы до этого завязали все вот эти
- 3:55:46воттинки на какой-то контекст, то
- 3:55:49отменив этот контекст, то есть отмена
- 3:55:51контекста - это и есть та самая просьба,
- 3:55:53когда мы просим контекст завершить
- 3:55:56завязанные на него рутины. То есть мы
- 3:55:58можем контекст отменить и тем самым мы
- 3:56:00отменим выполнение всех горутин, которые
- 3:56:02были завязаны на этот контекст. Почему у
- 3:56:05меня тут два каких-то контекста? Parent
- 3:56:07contex и child контекст, ещё какой-то
- 3:56:08бэкграунд. Так вот, в Голенге самым
- 3:56:10основным контекстом такой царьконтекст -
- 3:56:13это бэкграунд контекст. Его нельзя
- 3:56:15отменить. Он есть всегда. Он доступен из
- 3:56:18всех мест приложения. Нужен он лишь для
- 3:56:20того, чтобы от него унаследовать
- 3:56:22какой-то другой контекст и уже завязать
- 3:56:24какие-то грутины на этот унаследованный
- 3:56:26контекст. Просто контекст бэкграунд -
- 3:56:28это царь-контекст, от которого мы
- 3:56:30наследуем все наши остальные контексты,
- 3:56:32которые мы уже можем отменять, тем самым
- 3:56:34отменяя завязанные на этот контекст
- 3:56:36рутины. Соответственно, у нас есть
- 3:56:38parent контекст. То есть основной
- 3:56:39контекст, который мы унаследовали от
- 3:56:41бэкэкграунд контекста. Под этот parent
- 3:56:43контекст мы завязали вот эти вот три
- 3:56:46горутинки. Потом от parent контекста мы
- 3:56:49унаследовали child контекст. И на этот
- 3:56:51Child con контекст мы завязали вот эти
- 3:56:53вот грутинки. Как будут завершаться
- 3:56:56рутины при отмене того или иного
- 3:56:58контекста? Ну, если мы с вами отменим
- 3:57:01дочерний контекст, то завершатся только
- 3:57:03рутины, которые завязаны на этот
- 3:57:05дочерний контекст. Если мы отменим
- 3:57:08родительский контекст, то отменятся,
- 3:57:10завершатся все горутины, которые
- 3:57:11завязаны на этот родительский контекст,
- 3:57:14а также завершиться и отмениться каждый
- 3:57:17дочерний контекст. То есть отменяя
- 3:57:18родительский контекст, мы тем самым
- 3:57:20автоматически отменим и все дочерние
- 3:57:22контексты. То есть отменяя родительский
- 3:57:24контекст, мы завершим все гарутины,
- 3:57:27которые завязаны на этом родительском
- 3:57:28контексте. Также автоматически отменится
- 3:57:31дочерний контекст и завершатся все
- 3:57:33горутины, которые завязаны на этот
- 3:57:34дочерний контекст. Почему так? Это
- 3:57:37позволяет нам очень удобно расслаивать
- 3:57:39наше приложение на несколько уровней и
- 3:57:42точечно контролировать выполнение того
- 3:57:44или иного уровня. Допустим, мы поняли,
- 3:57:47что мы хотим завершить полностью нашу
- 3:57:49программу. Мы можем отменить вот этот
- 3:57:51вот родительский контекст, и у нас
- 3:57:53отменится всё, что у нас есть в
- 3:57:55программе. Все гарутины завершатся. Но
- 3:57:57если мы не хотим завершать все рутины,
- 3:57:59мы хотим только точечно отключить модуль
- 3:58:02для работы с базой данных, мы можем
- 3:58:04отменить только дочерний контекст, и
- 3:58:06тогда отменятся только грутинки, которые
- 3:58:08зависят от этого контекста, которые на
- 3:58:10этом контексте завязаны. Всё остальное
- 3:58:12приложение у нас продолжит корректно
- 3:58:13работать, потому что оно завязано лишь
- 3:58:16на родительский контекст. Отменится
- 3:58:18только то, что завязано на дочерний. Ну
- 3:58:20и повторюсь, если мы отменяем
- 3:58:21родительский контекст, тогда
- 3:58:22автоматически отменяются все дочерние
- 3:58:23контексты. у нас как бы всё приложение
- 3:58:24сразу берёт, да, и ложится как бы, да,
- 3:58:25все гарутины, которые завязаны на этих
- 3:58:27контекстах. Хорошо, теперь мы это знаем.
- 3:58:29Давайте посмотрим вообще на то, как это
- 3:58:31в коде у нас работает. Ну, у нас будет,
- 3:58:33как в примере, функция фу и будет
- 3:58:36функция бу.
- 3:58:38Пускай функция фу у нас в бесконечном
- 3:58:41цикле будет выводить что-то на экран. Ну
- 3:58:44вот будет вводить фу. И как бы на каждой
- 3:58:45итерации будет засыпать на 100 мскунд
- 3:58:48для того, чтобы не превращать наш вывод
- 3:58:49вмесиво. То есть разница вот 100 мскунд
- 3:58:51даст хотя бы возможность нам вот в
- 3:58:53выводе в терминале увидеть вообще, что
- 3:58:55происходит, потому что если этого
- 3:58:56таймслипа не будет, итерации слишком
- 3:58:58сильно, слишком быстро будут
- 3:58:59отрабатывать и будет слишком непонятная
- 3:59:01каша. Поэтому вот 100 мскунд просто
- 3:59:03ставлю задержечку на каждом выводе. То
- 3:59:05же самое функции бу, только будет
- 3:59:07вводить она бу. Ну и так как мы хотим
- 3:59:09это использовать как грутины, то давайте
- 3:59:11запустим, собственно, их как грутины.
- 3:59:13Запускаю функцию фу и запускаю функцию
- 3:59:15бу. Ну и после этого приколхожу просто
- 3:59:18таймслип на 3 секунды для того, чтобы
- 3:59:21просто 3 секундочки дать поработать этим
- 3:59:22горутинам. Они как бы вроде в
- 3:59:24бесконечном цикле, то есть должны
- 3:59:25никогда не закончиться, но через 3
- 3:59:27секунды у нас завершится наша программа,
- 3:59:29и эти горутины тоже у нас закончатся.
- 3:59:31Давайте запустим нашу программу и видим:
- 3:59:33фу-бу-бу, фу-фу-фу, вот оно всё
- 3:59:35выводится параллельно. Оп, всё,
- 3:59:36программка завершилась. Через 3 секунды,
- 3:59:38ну, можем ещё раз. Вот 1 2 3. И вот
- 3:59:42через 3 секундочки наша программа
- 3:59:43завершается. О'кей. Всё работает вроде
- 3:59:45как. Ээ, понятно? Теперь давайте,
- 3:59:47наконец-таки, перейдём к контекстам. То
- 3:59:49есть мы сейчас сможем завершать наши
- 3:59:51горутины не просто тем, что мы полностью
- 3:59:54завершаем выполнение всего приложения, а
- 3:59:56точечно при помощи контекстов сможем
- 3:59:57завершать те или иные рутины. Ну, помню,
- 3:59:59что у нас как бы функция фу будет
- 4:00:01завязана на родительский контекст, а
- 4:00:05функция бу на дочерний контекст. Давайте
- 4:00:09посмотрим, как вообще контексты
- 4:00:10создаются у нас в коде. Ну, для начала
- 4:00:11мы должны создать этот самый
- 4:00:13родительский контекст. Он у нас
- 4:00:15создаётся из бэкграунд контекста. Как
- 4:00:16это выглядит? Мы берём и создаём
- 4:00:19родительский контекст. Назову его parent
- 4:00:20context. Запятая parent cancel. Сейчас
- 4:00:23объясню, что это такое. И пишем
- 4:00:25контекст. То есть это пакет контекст
- 4:00:27стандартный. И в нём есть функция visel.
- 4:00:30В неё передаём контекст background. Что
- 4:00:32произошло? Я взял и из контекста
- 4:00:34бэкграунд, из вот этого самого
- 4:00:35контекста, из этого царьконтекста создал
- 4:00:38родительский контекст. Вот он
- 4:00:40родительский контекст. его создал с
- 4:00:42возможностью ручной отмены, то есть с
- 4:00:44возможностью в любой момент этот
- 4:00:46родительский контекст отменить и
- 4:00:47завершить все горутины, которые на этот
- 4:00:49контекст будут завязаны. Именно поэтому
- 4:00:51я вызвал из пакета контекст функцию with
- 4:00:53cancel, потому что она из передаваемого
- 4:00:56туда контекста наследует другой
- 4:00:59контекст, возвращает нам его и
- 4:01:00возвращает ещё функцию, которая нам
- 4:01:02позволит руками отменить возвращённый
- 4:01:04контекст. Теперь нужно создать дочерний
- 4:01:06контекст. Мы создали из бэкграунда
- 4:01:09родительский контекст с возможностью его
- 4:01:11отмены. То есть вот из бэкграунда
- 4:01:12создали родительский контекст с
- 4:01:14возможностью его отменить в любой
- 4:01:15момент. Теперь нам из этого
- 4:01:16родительского контекста нужно создать
- 4:01:18дочерний. Как это выглядит? Да, точно
- 4:01:20так же, как мы создавали родительский,
- 4:01:21только создаём дочерний. Родительский у
- 4:01:23нас исходил из царь контекста, а
- 4:01:25дочерний будет исходить из
- 4:01:26родительского. Соответственно, childк,
- 4:01:30вот тут у меня X нужно поставить и child
- 4:01:33cancel.
- 4:01:35У нас отдельно есть возможность прибить
- 4:01:37родительский контекст и отдельно
- 4:01:38возможность прибить дочерний контекст.
- 4:01:40Точно также контекст councel, только
- 4:01:42сюда уже передаём не контекст бэкграунд,
- 4:01:43потому что мы наш дочерний контекст
- 4:01:45хотим унаследовать не от царь контекста,
- 4:01:47а именно от родительского. Поэтому мы
- 4:01:49передаём сюда родительский контекст.
- 4:01:51О'кей, мы создали родительский контекст,
- 4:01:53создали дочерний контекст, получили
- 4:01:55возможность их руками отменять. Теперь
- 4:01:57нам нужно каким-то образом завязать наши
- 4:01:59запущенные горутины, наши функции, из
- 4:02:01которых мы запускаем эти горутины. Нужно
- 4:02:03их завязать на контекст. То есть сейчас
- 4:02:04они просто в свободном плавании, мы их
- 4:02:06как бы запускаем, да, но они на контекст
- 4:02:08на наш вообще никак не завязаны. То есть
- 4:02:10мы можем контекст отменить, но наши
- 4:02:12грутины продолжат своё выполнение,
- 4:02:13потому что мы их не завязали на наш
- 4:02:15контекст. Для того, чтобы их на этот
- 4:02:16самый контекст завязать, нужно,
- 4:02:18во-первых, эти самые контексты внутрь
- 4:02:20функции передавать. О'кей? То есть нам,
- 4:02:22чтобы принять контекст, пишем название
- 4:02:23просто cx, потому что изнутри какой-то
- 4:02:26функции нам не должно быть важно,
- 4:02:28какой-то контекст родительский,
- 4:02:30неродительский. Мы вот это вот
- 4:02:31разделение должны выполнять именно на
- 4:02:33уровне функции, которая работает с
- 4:02:35горутинами. Внутри самих горутин. Нам
- 4:02:37неважно, что это за контекст. Пускай
- 4:02:38тот, кто вызывает эти гартины, думает о
- 4:02:41том, что за контекст туда передать.
- 4:02:42Поэтому для нас это просто контекст.
- 4:02:44Принято вот так вот писать c x,
- 4:02:45сокращённы этот контекст. Ну и принимать
- 4:02:47мы будем интерфейс контекст из пакета
- 4:02:50контекст. Почему интерфейс? Потому что у
- 4:02:52нас могут быть разные контексты. Могут
- 4:02:53быть контекст vis with value. Это мы
- 4:02:56вообще какое-то значение можем внутри
- 4:02:57этого контекста передавать. У нас могут
- 4:02:59быть контекст с тайм-аутом, то есть
- 4:03:01автоматически контекст через какой-то
- 4:03:02тайм-аут отменится. Может быть контекст
- 4:03:05with deadдadлайн, то есть контекст
- 4:03:06отменится автоматически в какое-то
- 4:03:07определённое назначенное время. У нас
- 4:03:09могут быть разные виды контекстов,
- 4:03:11работают они по-разному. Но так как
- 4:03:13основная суть всех контекстов - это
- 4:03:15просто завязаться на них и завершиться,
- 4:03:18если они будут отменены, то изнутри
- 4:03:20наших грутин нам вообще неважно, какой
- 4:03:22это тип контекста. Мы в любом случае на
- 4:03:24него сможем завязаться и в любом случае
- 4:03:26сможем завершить рутину, если нам это
- 4:03:28потребуется. Поэтому просто принимаем
- 4:03:30интерфейс контекста. Как выглядит вообще
- 4:03:32у нас завязка на сам контекст? Выглядит
- 4:03:34она следующим образом. Мы используем
- 4:03:36нашу ту самую конструкцию Select.
- 4:03:39Select. Первый кейс выглядит следующим
- 4:03:42образом.
- 4:03:44И дефолт секция. Что тут случилось? В
- 4:03:47дефолтсекции будет обрабатываться наш
- 4:03:49код рутины, который выполняется у нас
- 4:03:51циклично. То есть у нас запускается
- 4:03:53грутина и запускается цикл бесконечный.
- 4:03:56На каждой итерации этого цикла у нас
- 4:03:59будет вызываться селект смотреться, что
- 4:04:01происходит. Если у нас контекст будет
- 4:04:03отменён, тогда отработает вот этот вот
- 4:04:06кейс. То есть внутри контекста, по сути,
- 4:04:08у нас скрывается канал. И когда мы этот
- 4:04:11канал закроем при помощи отмены
- 4:04:13контекста, то у нас будет автоматически
- 4:04:15читаться из канала, потому что,
- 4:04:17напоминаю, аксиомы каналов. Это помимо
- 4:04:19прочего, если у нас канал закрыт, тогда
- 4:04:23мы будем оттуда читать, не блокируясь
- 4:04:25сразу же значение по умолчанию.
- 4:04:27Получается, если мы отменим наш
- 4:04:29контекст, вот, допустим, родительский
- 4:04:31контекст, мы его отменяем, тогда у нас
- 4:04:33изнутри этого контекста, изнутри
- 4:04:36канальчика, на котором этот контекст
- 4:04:37строится, автоматически будет
- 4:04:39происходить чтение. Соответственно, эта
- 4:04:41секция будет разблокироваться, и мы
- 4:04:43попадём в return. То есть return у нас
- 4:04:45завершит нашу функцию. Но это если
- 4:04:47залезать прямо в кишки контекста. Можно
- 4:04:50просто запомнить, что вот такая вот
- 4:04:52конструкция контекст дан в селекте она
- 4:04:54разблокируется. Вот выполнится вот эта
- 4:04:57вот секция только если где-то отменится
- 4:04:58контекст, который мы сюда передадим.
- 4:05:00Соответственно, мы в нашем бесконечном
- 4:05:02цикле каждый раз будем попадать на этот
- 4:05:04селектрять,
- 4:05:06отменён ли наш контекст. Если контекст
- 4:05:09отменён, тогда вот здесь вот
- 4:05:11автоматически прочитается какое-то
- 4:05:12значение. Неважно какое. Это подкапотка
- 4:05:15контекста. Если контекст отменён, тогда
- 4:05:17здесь у нас не будет блокировки на этой
- 4:05:19секции, и автоматически выполнится код,
- 4:05:22который предназначен для этого кейса.
- 4:05:23Ну, мы тут можем вывести
- 4:05:26фу, завершилось.
- 4:05:28И вот такой вот грустный смайлик
- 4:05:29поставить. Фу, завершилось. И после
- 4:05:31этого retтюрн, то есть возврат из
- 4:05:32функции. Завершаем сразу же целую
- 4:05:34функцию, целую рутину. Если же у нас
- 4:05:36контекст будет не отменён, тогда здесь
- 4:05:39мы ничего не сможем прочитать. Тут будет
- 4:05:41у нас блокировка. А мы помним, что в
- 4:05:43конструкции Select, если у нас
- 4:05:45блокируются все доступные кейсы, то
- 4:05:48вызывается секция default. А в секции
- 4:05:50дефолт у нас выполнение нашего основного
- 4:05:52кода нашей рутины. Тут может написать фу
- 4:05:56продолжает
- 4:05:58своё выполнение. Ещё раз целиком сверху
- 4:06:00вниз. Вот, допустим, мы в рутину фу
- 4:06:03передадим наш parent контекст. Вот мы
- 4:06:06передали parent contex. Хорошо,
- 4:06:08запускается. Запускается бесконечный
- 4:06:10цикл. На каждой итерации этого цикла мы
- 4:06:12сразу же попадаем в конструкцию Select и
- 4:06:15смотрим, у нас контекст отменён. Если
- 4:06:17контекст отменён, тогда вызовет вот этот
- 4:06:19вот кейс. Мы выведем: "Фу, завершилось".
- 4:06:21И завершим нашу функцию. Соответственно,
- 4:06:24завершим рутину. Если контекст не
- 4:06:26отменён, отработает секция default, и мы
- 4:06:29будем выводить фу, продолжать своё
- 4:06:30выполнение, после этого засыпать на 100
- 4:06:32мсекунд и отправляться на новую
- 4:06:34итерацию. Итак, каждый раз перед тем,
- 4:06:36как выполнять секцию default, мы будем
- 4:06:38проверять, отменён ли контекст. Если
- 4:06:40контекст у нас не отменён, тогда
- 4:06:42выполнится секция default, и мы выведем
- 4:06:45то, что тут вводится. Если контекст
- 4:06:47всё-таки где-то отменили, тогда
- 4:06:49вызовется вот этот вот кейс. Мы выведем
- 4:06:51фу завершилось и закончим нашу рутину.
- 4:06:52Давайте скопируем вот это всё в функцию
- 4:06:54бу. И тут будем выводить бу завершилось.
- 4:06:58Бу продолжает своё выполнение. Ну и,
- 4:06:59соответственно, она тоже должна
- 4:07:00принимать какой-то контекст, чтобы на
- 4:07:02него завязаться. Теперь мы в функцию фу
- 4:07:06передаём родительский контекст, а
- 4:07:08функцию бу дочерний. Теперь смотрим, что
- 4:07:10будет, если мы через одну секунду
- 4:07:12отменим дочерний контекст. Как вот
- 4:07:15отменить контекст при помощи вот этой
- 4:07:17вот возвращённой переменной? На самом
- 4:07:18деле в этой переменной лежит целая
- 4:07:20функция. Чтобы отменить контекст, нужно
- 4:07:22эту функцию вызвать. Child cancel
- 4:07:24скобочки. Всё. Благодаря вот такому
- 4:07:26вызову этой функции мы отменим дочерний
- 4:07:28контекст. Потом ещё через одну секунду
- 4:07:32мы отменим родительский контекст. Ну и в
- 4:07:35конце можно дополнительно не засыпать, в
- 4:07:37принципе, как бы без разницы, что у нас
- 4:07:39тут будет происходить. Мы создали
- 4:07:41родительский контекст и дочерний
- 4:07:42контекст. Собственно, мы создали
- 4:07:44родительский контекст и дочерний
- 4:07:46контекст. После этого мы завязали
- 4:07:48функцию фу на наш родительский контекст.
- 4:07:51То есть мы как бы завязались функции фу
- 4:07:53на в принципе любой контекст, который
- 4:07:55нам будет передан. Но так как мы туда
- 4:07:56передали именно родительский, то
- 4:07:57завязались мы тут на родительском
- 4:07:59контексте. О'кей. Мы функцию фу, как и
- 4:08:01хотели, завязали на родительском
- 4:08:04контексте. Ну, пока что у нас одна
- 4:08:06функция фу запущена, но нам ничто не
- 4:08:07мешает запустить их потом несколько.
- 4:08:09Пока смотрим на одну функцию. У нас вот
- 4:08:11одна вот эта вот горутинка фу. Мы её
- 4:08:13завязали успешно на наш родительский
- 4:08:15контекст. Хорошо. Теперь идём дальше.
- 4:08:17Потом от родительского контекста мы с
- 4:08:19вами унаследовали дочерний контекст. Вот
- 4:08:21он дочерний контекст, который
- 4:08:22унаследован от родительского. И мы
- 4:08:24функцию бу уже завязали также на любой
- 4:08:26контекст, который мы туда передаём. Но
- 4:08:28так как мы передаём туда дочерний
- 4:08:29контекст, то мы функцию бу завязали на
- 4:08:31дочернем контексте. Вот она, наша
- 4:08:33функция бу. Опять же, у нас пока что
- 4:08:35одна рутина запущена, поэтому на первой
- 4:08:37итерации нашего примерчика вот
- 4:08:39рассматриваем, что у нас одна грутина
- 4:08:41фу, одна грутина бу. Рутина фу завязана
- 4:08:43на родительский контекст, рутина бу
- 4:08:45завязана на дочерний контекст. После
- 4:08:47этого мы их как бы запускаем и ждём одну
- 4:08:49секунду. Пока мы ждём одну секунду, у
- 4:08:51нас уже успеет совершиться несколько
- 4:08:53выводов фу и несколько выводов бу,
- 4:08:55потому что ни один из этих контекстов
- 4:08:57ещё не отменён. И у нас цикл на каждой
- 4:08:59итерации там и там будет проверять,
- 4:09:01отменён ли переданный в функцию
- 4:09:03контекст. Мы будем узнавать, что он не
- 4:09:04отменён, и будет выполняться секция de
- 4:09:06дефолт и выводиться фу продолжается у
- 4:09:08выполнение и бу продолжается у
- 4:09:09выполнение. Потом мы через одну секунду
- 4:09:12отменим дочерний контекст. А что
- 4:09:15произойдёт, если мы отменим дочерний
- 4:09:16контекст? Отменится функция бу. Мы
- 4:09:19отменяем дочерний контекст, на котором
- 4:09:21завязана функция бу. На очередной
- 4:09:22итерации функции будём в select,
- 4:09:25проверим, отменён ли контекст, и узнаем,
- 4:09:28наконец, что действительно контекст
- 4:09:30отменён. Тогда выполнится вот эта вот
- 4:09:31секция. Мы выведем: БУ, завершилась и
- 4:09:33завершим выполнение нашей грутины. Потом
- 4:09:36целую секунду у нас будет работать
- 4:09:38только лишь функция фу, потому что мы
- 4:09:40отменили функцию бу отменой дочернего
- 4:09:43контекста и потом опять на одну секунду
- 4:09:45заснули. Эту одну секунду функция фу
- 4:09:46будет делать как бы то, что она делает,
- 4:09:48а именно выводить на экран свой текст.
- 4:09:50Потом ещё через одну секунду мы отменим
- 4:09:52наш родительский контекст, и у нас
- 4:09:54отменится рутина фу. У нас на очередной
- 4:09:57итерации мы попадём в кейс select,
- 4:09:58проверим, отменён ли у нас контекст, и
- 4:10:00обнаружим, что да, отменён. Отработает
- 4:10:02вот этот вот кейс, мы выведем: фу,
- 4:10:03завершилось, и завершим выполнение нашей
- 4:10:05функции. Давайте дополнительно я после
- 4:10:08всех этих отмен контекстов ещё главную
- 4:10:10нашу рутину заставлю поспать 3 секунды.
- 4:10:13Просто чтобы мы видели, что наши вот эти
- 4:10:15горутины, они завершились не потому, что
- 4:10:16у нас мей завершился, а потому, что у
- 4:10:18нас отменился контекст. Соответственно,
- 4:10:20вот отменя родительский контекст, мы
- 4:10:22берём его, отменяем, и у нас через
- 4:10:24секунду после отмены бу отменяется
- 4:10:26функция фу. Ну, здесь дополнительный
- 4:10:27вывод, что-то типа main завершился.
- 4:10:31Запускаем нашу программу
- 4:10:34и видим фубу, фубу, фубу, но в один
- 4:10:37момент фубу фубу фубу, оп, у нас только
- 4:10:40фуки выводов бу. Почему так случилось?
- 4:10:44Потому что мы, когда только нашу
- 4:10:46программу запустили, у нас было фубу,
- 4:10:48фубу фубу. Они как бы одновременно
- 4:10:49работали. Одновременно выполнялась и эта
- 4:10:51рутина, и вот эта рутина. Они
- 4:10:53выполнялись, выполнялись, выполнялись.
- 4:10:54Потом мы отменили дочерний контекст.
- 4:10:57Отменили дочерний контекст. Тем самым
- 4:10:59завершили рутину, которая на этот
- 4:11:00дочерний контекст была завязана.
- 4:11:02Отменили дочерний контекст через
- 4:11:03секунду, а мы его как бы передали в бу.
- 4:11:06Соответственно, мы на очередную итерацию
- 4:11:07узнали, что мы отменились, и вывелась бу
- 4:11:09завершилось. Мы завершили нашу функцию
- 4:11:11бу, нашу грутину. После этого у нас
- 4:11:13осталась только грутина фу, потому что
- 4:11:15мы её только лишь через ещё секунду
- 4:11:17отменим. Ну и вот она выполнялась,
- 4:11:19выполнялась, выполнялась. Как бы
- 4:11:20контекст функции фу не был отменён.
- 4:11:22Постоянно, когда мы на каждой итерации
- 4:11:24попадали в сект и проверяли, отменён ли
- 4:11:26наш контекст, мы узнавали, что он не
- 4:11:28отменён, и выполняли секцию default.
- 4:11:30Потом в один момент мы через ещё секунду
- 4:11:33отменили уже родительский контекст, тем
- 4:11:36самым отменив функцию фу, отменив рутину
- 4:11:38фу. И на очередной итерации она увидела,
- 4:11:40что контекст таки отменён, и поэтому
- 4:11:43вывелась фу завершилась и ретюрн. После
- 4:11:45этого мы ещё целых 3 секунды подождали и
- 4:11:48потом вывели мей завершился. То есть,
- 4:11:50как мы видим, у нас фу завершилось и
- 4:11:52целых 3 секунды прошло до того, как мей
- 4:11:54завершился. Давайте ещё раз запустим
- 4:11:55нашу программу и увидим: фу-бу фу-бу
- 4:11:57фу-бу. Оп, бу завершилось. После этого
- 4:12:00фу. После этого вот. Фу завершилось. И
- 4:12:02только через какое-то время у нас сам
- 4:12:03мей завершился. Оп, фу завершилось. 1 2
- 4:12:063 Оп, мей завершился. Хорошо. Теперь
- 4:12:09давайте посмотрим, что будет, если мы
- 4:12:10отменим сначала родительский контекст, а
- 4:12:12потом только дочерний. Соответственно, я
- 4:12:14сначала отменяю родительский контекст, а
- 4:12:16потом только отменю дочерний. Запускаю
- 4:12:18наши две рутины. Сначала отменю
- 4:12:20родительский, потом дочерний. Посмотрим,
- 4:12:21что будет. Запускаем программу. Фубу,
- 4:12:24фубу, фубу. Обе завершились сразу
- 4:12:27моментом. Почему обе завершились сразу?
- 4:12:29Почему обе завершились? Хотя мы вроде бы
- 4:12:31по очереди отменяли контексты. Потому
- 4:12:33что мы, когда отменили родительский
- 4:12:35контекст, у нас отменилась функция фу,
- 4:12:38но сразу же за родительским контекстом у
- 4:12:40нас автоматически отменился и дочерний.
- 4:12:43А вместе с зачерним отменилась функция
- 4:12:44бу. Соответственно, эти две функции у
- 4:12:46нас завершились и всё. И после этого
- 4:12:48завершился main. Получается, отменой
- 4:12:50родительского контекста мы автоматически
- 4:12:52отменяем все дочерние. Для чего это
- 4:12:53нужно? Повторюсь, с помощью контекста мы
- 4:12:55можем очень удобно контролировать
- 4:12:56выполнение различных узлов в нашей
- 4:12:58программе. И если мы хотим завершить
- 4:12:59только узел работы с базой данных, мы
- 4:13:02можем отменить дочерний контекст.
- 4:13:03Отменится этот узел, завершится, но
- 4:13:05остальной узел нашей программы продолжит
- 4:13:07успешно работать. Но если мы захотим всю
- 4:13:09программу сразу завершить, все в
- 4:13:10негарутины, мы просто один контекст
- 4:13:12отменяем, и все его дочерние контекст и
- 4:13:15дочерние контексты этого дочернего
- 4:13:16контекста, они тоже будут отменены. Это
- 4:13:19приведёт к тому, что вся программа у нас
- 4:13:20завершится. Таким образом, распределяя в
- 4:13:23разные логические узлы наши группы
- 4:13:25рутин, мы можем контролировать их
- 4:13:27выполнение и в любой момент при желании
- 4:13:29брать, да, и завершать их. Плюс мы можем
- 4:13:31вешать тайм-ауты на наш контекст, чтобы
- 4:13:32контекст сам отменился через какой-то
- 4:13:34тайм-аут и завершил целую группу рутин.
- 4:13:36Но это уже более продвинутая тема. Если
- 4:13:38кому-то интересно, очень советую с этим
- 4:13:40поиграться. Давайте теперь рассмотрим
- 4:13:42тот же примерчик, только запустим
- 4:13:43побольше горутин каждой группы, как мы
- 4:13:45изначально и планировали. Тут три и тут
- 4:13:47три. Ну для этого я просто три раза
- 4:13:48напишу фу и три раза напишу гобу.
- 4:13:53И ещё для того, чтобы мы могли каждую
- 4:13:55запущенную отдельно рутину одной группы
- 4:13:56разделять на выводе, я буду им ещё
- 4:13:58передавать чиселку.
- 4:14:00Ну какой-то их номер, и будет
- 4:14:02выводиться, что фу завершилось. И после
- 4:14:04этого ещё её номер. Ну и также фу
- 4:14:06продолжает своё выполнение. И номер. То
- 4:14:07же самое здесь.
- 4:14:10Тут номер и тут номер.
- 4:14:12И, соответственно, сюда я передам 1 2 3.
- 4:14:16И сюда тоже 1
- 4:14:192 3. Запустим нашу программу. Посмотрим,
- 4:14:21как работает отмена контекста, когда у
- 4:14:23нас на один контекст завязано сразу
- 4:14:25несколько грутин. Запускаем. И как мы
- 4:14:29видим, фу, бу, фу. И все разные у нас
- 4:14:31выполняются. у нас выполняется сразу же
- 4:14:33три бу разных и три разных фу. У нас
- 4:14:36выполняется он 3 2 1 3 2 1 3 21. Потом
- 4:14:38мы их все завершили одним разом. Почему?
- 4:14:41Потому что мы сначала отменили
- 4:14:42родительский контекст. Мы взяли,
- 4:14:43отменили родительский контекст и сразу
- 4:14:44всю приложуху положили, короче, отменой
- 4:14:46одного контекста. Давайте сначала
- 4:14:48всё-таки дочерний отменим, а потом
- 4:14:49только родительский. Сохраняем,
- 4:14:52запускаем. Вот все три фу, все трибу у
- 4:14:54нас отлично работают. И как только мы
- 4:14:57отменяем наш дочерний контекст, у нас
- 4:14:59завершаются наши все бугарутины. Он
- 4:15:03завершилась третья бу, первая бу, вторая
- 4:15:05бу тоже завершилась. Как и запуск наших
- 4:15:07рутин не гарантирует последовательность
- 4:15:09их запуска. Они могут немножко в разном
- 4:15:11порядке запуститься. Так и завершение
- 4:15:13наших рутин тоже не гарантирует, что они
- 4:15:14завершатся в определённом порядке. Мы
- 4:15:16просто знаем, что они все завершатся, а
- 4:15:18в каком порядке нас уже не сильно
- 4:15:19интересует. Вызвано это особенностями
- 4:15:21работы планировщика Рутин Goldeng. Более
- 4:15:23подробно мы это будем изучать, когда
- 4:15:25будем готовиться к собеседованию. Пока
- 4:15:26что для нас это не сильно важно. В
- 4:15:28каком-то порядке, неважно в каком, наши
- 4:15:30грутины завершатся. Соответственно, было
- 4:15:31фубу, фубу, всякие разные. Отменили
- 4:15:34дочерний контекст, и у нас отменились
- 4:15:36все функции бу. То есть мы отменили
- 4:15:39дочерний наш контекст, и все функции бу
- 4:15:40разом взяли, да, и отменились. Потом у
- 4:15:42нас продолжилось выполнение только
- 4:15:43функции фу. Как мы видим, только фу у
- 4:15:45нас выполняется. Вот это вот всё
- 4:15:46выполняется, выполняется, выполняется.
- 4:15:48Потом бам, мы отменили родительский
- 4:15:50контекст. И как мы видим, у нас фу
- 4:15:51завершилась. Вторая фу, третья фу тоже
- 4:15:53завершилась. Мы, отменив после дочернего
- 4:15:56родительский контекст, добили оставшиеся
- 4:15:58грутины и всё. У нас как бы не было ни
- 4:16:00одной рутины больше в программе
- 4:16:01запущеной, кроме главной мейн рутины. Ну
- 4:16:03и вот наша мейнрутина после
- 4:16:04трёхсекундного слипа завершилась.
- 4:16:06Давайте ещё раз мы это всё запустим.
- 4:16:08Фу-бу, фу-буфу-бу. Оп, бу, завершилось.
- 4:16:11Фу, завершилось. Подождали. Бам, мей
- 4:16:13завершился. Вот таким образом такой
- 4:16:15конструкции, повторюсь, она
- 4:16:16довольно-таки популярна. Вот это вот
- 4:16:17сочетание бесконечного цикла и в нём
- 4:16:19селекта, который проверяет на каждой
- 4:16:21итерации, отменён ли контекст с
- 4:16:22дефолтсекцией отдельной. Это довольно
- 4:16:24популярная история, которая нам для вот
- 4:16:26таких вот каких-то фоновых обработчиков
- 4:16:28часто может быть полезна. Познакомились
- 4:16:30мы с вами, наконец-таки, с контекстом.
- 4:16:32Уже такая довольно серьёзная тема. И
- 4:16:33повторюсь, что при помощи контекста мы
- 4:16:35можем отдельно контролировать выполнение
- 4:16:37каких-то узлов нашей программы. Захотели
- 4:16:39отключить работу с базой данных, все
- 4:16:41рутины, которые с базой данных работают,
- 4:16:42хоп, взяли, отменили дочерний контекст,
- 4:16:44отключили их. захотели всю приложуху
- 4:16:46положить одним разом, родительский
- 4:16:48контекст отменили, и у нас вся приложуха
- 4:16:49взяла и завершилась. Тут ещё может
- 4:16:52встать вопрос, зачем нам вообще отменять
- 4:16:54родительский контекст, дожидаться, пока
- 4:16:55все рутины у нас завершатся, если мы
- 4:16:57просто можем прибить наше приложение
- 4:16:59через диспетчер задач какой-нибудь. На
- 4:17:00самом деле, да, мы можем прибить наше
- 4:17:02приложение через диспетчер задач, но это
- 4:17:04чревато некоторыми неприятными
- 4:17:05последствиями. Представим следующую
- 4:17:07ситуацию. Вот этот вот прямоугольник -
- 4:17:09это у нас обработка какого-то
- 4:17:11пользовательского запроса. пользователь
- 4:17:13делает свой запрос, и пока мы с вами не
- 4:17:15дойдём до нижнего кружка, мы не
- 4:17:17обработаем до конца запрос. То есть тут
- 4:17:19множество разных действий какое-нибудь,
- 4:17:20к примеру, взаимодействие с базой
- 4:17:22данных, взаимодействие с личным
- 4:17:24аккаунтом пользователя. Это всё требует
- 4:17:26какого-то времени. И, допустим,
- 4:17:27пользователь захотел там отправить
- 4:17:28кому-то сообщение или загрузить какую-то
- 4:17:30фотографию себе в профиль. Он начал это
- 4:17:32делать. Но тут мы пришли и прибили
- 4:17:34приложение, захотели завершить наше
- 4:17:36приложение. Мы просто взяли и на
- 4:17:37середине выполнения этого запроса
- 4:17:39прервали действие, которое обрабатывало
- 4:17:41запрос пользователя. Тем самым
- 4:17:42пользователь получит ошибку и такой
- 4:17:44типа: "А почему моё действие не было
- 4:17:46завершено?" Это называется жёсткое
- 4:17:47завершение приложения. И это не всегда
- 4:17:49хорошо. Это быстро, мы можем быстро
- 4:17:51прибить наше приложение, но таким
- 4:17:53образом мы можем случайно прервать
- 4:17:55некоторые операции, которые, вообще-то,
- 4:17:56хорошо бы завершить до конца для того,
- 4:17:58чтобы пользователи остались довольны.
- 4:17:59Именно для этого мы можем не прибивать
- 4:18:01наше приложение сразу с диспетчера
- 4:18:03задач, а можем завершать контекст. можем
- 4:18:05вот этот наш самый родительский контекст
- 4:18:07завершать. Что будет происходить?
- 4:18:08Пользователь делает запрос, его запрос
- 4:18:10обрабатывается. В тот момент, когда его
- 4:18:12запрос обрабатывается, нам пришли и
- 4:18:14завершили контекст. Но мы всё равно
- 4:18:16спокойненько до конца доработаем наш
- 4:18:17запрос. И только в конце мы проверим,
- 4:18:19завершился ли контекст. И когда узнаем,
- 4:18:21что он завершится, то спокойненько уже
- 4:18:23завершим нашу программу. Таким образом,
- 4:18:25мы сможем и корректно программу
- 4:18:26завершить, и до конца доработать все
- 4:18:28пользовательские запросы, которые мы
- 4:18:30начали выполнять. Тем самым пользователь
- 4:18:32избежит каких-то ошибок на своём экране
- 4:18:33и останется доволен. По большому счёту,
- 4:18:35этот принцип называется graceful
- 4:18:37Shutdown. Переводя с английского языка,
- 4:18:39это что-то типа красивое завершение. Как
- 4:18:41раз-таки Graceful Shutdown в Гошке
- 4:18:43реализуется за счёт контекстов, что мы
- 4:18:45всё наше приложение вешаем на контексты
- 4:18:47и потом не просто берём и прибиваем наше
- 4:18:48приложение из диспетчера задач, а
- 4:18:50завершаем контекст, даём всем начатым
- 4:18:52процедурам аккуратненько завершиться до
- 4:18:54конца. В конце они проверят, завершён ли
- 4:18:56контекст, и узнают, что да, и
- 4:18:57аккуратненько завершаться. Это
- 4:18:59называется graceful Shdдау. контексты,
- 4:19:01помимо того, что помогают нам
- 4:19:03контролировать время выполнения каких-то
- 4:19:04отдельно взятых рутин, они ещё и нам
- 4:19:06помогают реализовывать этот самый
- 4:19:08грейсful shдаdown.
- 4:19:10[музыка]
- 4:19:15Следующее, о чём нам стоит поговорить -
- 4:19:17это инструмент синхронизации
- 4:19:18weightгруoup. Что такое вообще вейт
- 4:19:20группа? Для чего она нужна? Вейтгруппа
- 4:19:22нам позволяет дожидаться выполнения
- 4:19:24какого-то количества рутин. И только
- 4:19:26когда эти рутины завершатся, мы можем
- 4:19:29продолжить своё выполнение в мейне, ну
- 4:19:31или в любой другой рутине, если мы из
- 4:19:32этой другой рутины ещё какие-то рутины
- 4:19:34запускаем. Но пока для простоты мы
- 4:19:36всегда другие рутины запускаем только из
- 4:19:39йгарутин. Хотя нам ничто не мешает из
- 4:19:40рутины запускать ещё рутины, но там уже
- 4:19:43совсем сложно становится для понимания,
- 4:19:45поэтому новые рутины будем запускать из
- 4:19:46мейна. Соответственно, мы из мейна
- 4:19:48запустили несколько рутин и хотим
- 4:19:49дождаться, пока они все завершат своё
- 4:19:51выполнение и только после этого
- 4:19:52продолжить выполнение нашего мейна.
- 4:19:54Казалось бы, мы с вами уже прошли
- 4:19:55каналы. То есть через каналы мы можем
- 4:19:57синхронизировать выполнение наших грутин
- 4:20:00и мейна. Мы можем из этих каналов
- 4:20:02какой-то значение читать. Мы будем тем
- 4:20:04самым и получать значения, и
- 4:20:05синхронизировать наше выполнение. Но
- 4:20:07бывают случаи, когда наши горутины
- 4:20:10ничего не возвращают. То есть мы, по
- 4:20:12сути, не хотим от этих горутин получать
- 4:20:14никакое значение. Мы просто хотим их
- 4:20:16запустить, чтобы они выполнили какую-то
- 4:20:18свою работу. И потом, когда они все свою
- 4:20:21работу выполнят, мы смогли бы идти
- 4:20:23дальше. При этом эти горутины не
- 4:20:25возвращают никакого значения. То есть у
- 4:20:26нас не шахтёры, которые добыли какой-то
- 4:20:29уголь, мы у них хотим этот уголь
- 4:20:31получить. Нет, у нас просто почтальоны.
- 4:20:33Мы отправляем почтальонов, они разносят
- 4:20:35какие-то письма, и когда они завершают
- 4:20:37свою работу, когда они все письма
- 4:20:38разнесли, мы хотим продолжить своё
- 4:20:40выполнение в мейне. То есть нам от этих
- 4:20:42почтальонов получать ничего не надо. Мы
- 4:20:43никакое значение от них не ждём. Мы их
- 4:20:46просто запускаем, хотим, чтобы они
- 4:20:47поработали. И как только они завершат
- 4:20:49свою работу, только потом мы хотим
- 4:20:51продолжиться в мейне. Поэтому тут, если
- 4:20:53как-то и прикручивать каналы для того,
- 4:20:55чтобы синхронизировать наш мей с
- 4:20:57запущенными рутинами, с этими нашими
- 4:20:59почтальонами, нам какие-то костыли
- 4:21:01пришлось бы прикручивать, возвращать в
- 4:21:03канале какое-то неиспользуемое значение
- 4:21:05непонятное, чтобы как-то
- 4:21:06синхронизироваться, в общем, было бы
- 4:21:08совсем некрасиво. Вот для таких случаев,
- 4:21:09когда нам нужно просто запустить
- 4:21:11несколько грутин и подождать, пока они
- 4:21:13завершатся, для этого существует вейт
- 4:21:15группа. Давайте посмотрим, как эта вейт
- 4:21:17гругруппа вообще в коде у нас выглядит.
- 4:21:19Ну, для начала давайте опишем нашу
- 4:21:20функцию, которая будет для нас этим
- 4:21:22самым почтальоном. То есть у нас есть
- 4:21:23функция, назову её Postman, с
- 4:21:26английского почтальон. Всё, что делает
- 4:21:28почтальон - это разносит письма. Мы даём
- 4:21:30почтальону какую-то стопку газет.
- 4:21:32Содержание этих газет мы будем
- 4:21:33передавать в строковые переменной, да?
- 4:21:34Вот там типа текст газеты, это какая-то
- 4:21:36строка. И задача почтальона - это в три
- 4:21:39разные места отнести вот эти вот газеты.
- 4:21:42Соответственно, в цикле от одного до
- 4:21:45трёх мы будем разносить эти газеты. То
- 4:21:47есть всё будет довольно просто. Выводим
- 4:21:49на экран. Я почтальон. Я отнёс газету.
- 4:21:53После этого мы будем выводить, что
- 4:21:55именно за газету он отнёс. В такой-то
- 4:21:59раз. То есть у нас будет я почтальон, я
- 4:22:01отнёс газету такую-то в первый раз, во
- 4:22:03второй раз в третий раз. И после этого
- 4:22:05почтальон завершает своё выполнение. Ну
- 4:22:07давайте разделим это таймслипами опять
- 4:22:09же, а то наш почтальон сейчас как бы всё
- 4:22:11разнесёт за наносекунду. Мы даже ничего
- 4:22:12не успеем понять.
- 4:22:15Вот 250 мскунд. четверть секунды мы
- 4:22:18будем на каждой итерации засыпать. Ну
- 4:22:19давайте сначала просто запустим нашего
- 4:22:21почтальона, как бы одного.
- 4:22:24Ну и пускай он разносит газету Новости.
- 4:22:27Запустим нашу программу.
- 4:22:30Ну видим, я почтальон. Я отнёс газету
- 4:22:32Новости в первый раз, во второй раз, в
- 4:22:34третий раз. О'кей. Теперь мы захотим
- 4:22:36второго почтальона, который бы относил у
- 4:22:38нас игровой журнал.
- 4:22:41Запускаем. Ну и видим, что у нас сначала
- 4:22:43относятся новости, потом игровой журнал.
- 4:22:45Почему? Это потому, что у нас нету
- 4:22:46никаких грутин. Если мы хотим, чтобы у
- 4:22:48нас это всё делал не один почтальон, а
- 4:22:51несколько, то нам нужно запускать
- 4:22:52грутины, чтобы распараллеливать
- 4:22:54выполнение нашего кода. Поэтому пишем
- 4:22:56здесь го и го. Но я думаю, уже многие
- 4:22:58поняли, что сейчас произойдёт, а именно
- 4:23:00ничего. Почему? Потому что мы запустили
- 4:23:02первого почтальона, второго почтальона и
- 4:23:05завершили своё выполнение. Завершил своё
- 4:23:07выполнение и все остальные рутины сразу
- 4:23:09же автоматически тоже завершились. То
- 4:23:10есть нам бы тут по-хорошему как-то
- 4:23:12дожидаться вот выполнения вот этого вот
- 4:23:14почтальона. дождаться, пока он отнесёт
- 4:23:16все газеты и только после этого
- 4:23:17завершаться. То есть у нас, допустим,
- 4:23:19будет вот три почтальона, третий бы
- 4:23:20относил автомобильная хроника
- 4:23:22какая-нибудь, там журнал про автомобили,
- 4:23:24там про разные их устройства и так
- 4:23:26далее. Вот у нас как бы три почтальона,
- 4:23:28мы их запустили 1 2три, но мы их никак
- 4:23:31не дожидаемся нигде. Давайте на схемке,
- 4:23:32чтобы не путаться, я оставлю только три
- 4:23:33почтальона. Вот трёх почтальончиков
- 4:23:35запустили, но main сразу же после этого
- 4:23:37завершил своё выполнение, как только мы
- 4:23:38их запустили, и всё, программа
- 4:23:40завершилась. Нам нужно сюда добавить
- 4:23:42вейтгруппу, благодаря которой мы сможем
- 4:23:45дожидаться, пока все почтальоны завершат
- 4:23:47свою работу, пока каждый из почтальонов
- 4:23:49у нас отнесёт свою газету сколько надо
- 4:23:51раз, и только после этого мы завершим
- 4:23:53наш мей. То есть, если сейчас мы в мене
- 4:23:55что-нибудь выведем,
- 4:24:00ain завершился, то мы как бы увидим
- 4:24:01только мей завершился, потому что мы
- 4:24:03выполнили одну эту команду в главной
- 4:24:05горутине, другие горутины даже
- 4:24:06запуститься не успели. Хорошо, добавляем
- 4:24:08вейт группу. Выглядит это следующим
- 4:24:10образом. принято вейгруппу называть VG.
- 4:24:13Ну и создам вейтгруппу. Вейгруппа - это
- 4:24:16примитив синхронизации, который
- 4:24:17находится в пакете Syn. У нас в пакете
- 4:24:20Syn есть вейтгруппа, та самая, благодаря
- 4:24:22которой мы можем дожидаться несколько
- 4:24:24грутин, пока они завершатся. Повторюсь,
- 4:24:26почему мы тут не используем, как раньше
- 4:24:27каналы? Потому что когда у нас был
- 4:24:29шахтёр, он нам возвращал добытый уголь,
- 4:24:31и мы этот добытый уголь получали в
- 4:24:32мейне. А почтальон он просто разносит
- 4:24:34почту. Мы от него ничего не ждём. Мы
- 4:24:36просто хотим, чтобы он выполнил свою
- 4:24:38работу и дождаться этого момента. Но мы
- 4:24:40от почтальона ничего не получаем,
- 4:24:41поэтому нам тут каналы как бы особо
- 4:24:43красиво не прикрутишь. Поэтому
- 4:24:45используем вей груруппу. Вейтгруппа -
- 4:24:46это структура. У неё есть какие-то поля.
- 4:24:48И так как мы хотим, чтобы эта вейт
- 4:24:50гругруппа использовалась во всех
- 4:24:51рутинах, а не её копия. Если мы
- 4:24:53передадим копиют группы, у нас каждая
- 4:24:55грутина будет работать с этой копией, и
- 4:24:57у нас ничего не заведётся. Нам нужно,
- 4:24:59чтобы каждая грутина работала с
- 4:25:01конкретным экземпляром одной
- 4:25:03единственной вейт гругруппы, чтобы они
- 4:25:05все через эту вейт гругруппу общались.
- 4:25:07Поэтому нам нужно ветг группу передавать
- 4:25:08по указателю. И тут сразу же может
- 4:25:10возникнуть вопрос, почему, когда мы с
- 4:25:12вами работали с каналами, то у нас не
- 4:25:14было никакой необходимости задумываться
- 4:25:15о том, передаём мы этот канал копии или
- 4:25:18передаём мы его по указателю. Мы просто
- 4:25:20писали название переменной, потом тип
- 4:25:21чан и название типа, который через этот
- 4:25:24канал будет передаваться. Ну, в данном
- 4:25:25случае это канал целых чисел. Мы просто
- 4:25:27писали следующую запись и были уверены,
- 4:25:30что этот самый канал будет отрабатывать
- 4:25:32корректно. Но вдруг в случае с вейт
- 4:25:34групппой, что по сути тоже является
- 4:25:36примитивом синхронизации, как и канал,
- 4:25:38мы уже должны задумываться о том, будем
- 4:25:40мы использовать копию или будем мы
- 4:25:42передавать эту вег-группу по указателю.
- 4:25:44Почему так? Для получения ответа на этот
- 4:25:45вопрос нужно обратиться к тому, как
- 4:25:47устроена вейт гругруппа и как устроены
- 4:25:49каналы под капотом. Сразу говорю, мы
- 4:25:51глубоко погружаться не будем,
- 4:25:52максимально поверхностно поговорим.
- 4:25:54Сейчас перед вашими глазами один из
- 4:25:55множества файлов исходного кода языка
- 4:25:57программирования Goeng. То есть, как бы
- 4:25:59это ни было странно, некоторые части
- 4:26:01языка программирования Goeng написаны
- 4:26:03тоже на. Если вы сейчас не можете
- 4:26:05осознать, как это вообще так, то
- 4:26:06предлагаю пока, ну, вообще не
- 4:26:07задумываться об этом и не париться. Для
- 4:26:09понимания нашего текущего вопроса это не
- 4:26:11сильно важно. Так вот, это структура
- 4:26:13канала, это то, как канал выглядит на
- 4:26:15самом деле под капотом, а это
- 4:26:17конструктор канала, который через
- 4:26:19несколько слоёв абстракции вызывается,
- 4:26:21когда мы с вами пишем уже привычный нам
- 4:26:24make и создаём через этот мейк наш
- 4:26:27канал. Когда мы пишем следующую
- 4:26:29конструкцию, то там внутри за
- 4:26:30несколькими слоями абстракции на самом
- 4:26:32деле вызывается вот этот вот
- 4:26:33конструктор. И что мы видим? Он
- 4:26:35возвращает указатель на структуру
- 4:26:38канала. Вот структура канала Hчan. И нам
- 4:26:41возвращается указатель на неё. Что это
- 4:26:44может значить? А это значит лишь одно,
- 4:26:46что на самом деле, когда мы создаём
- 4:26:47канал через make, то мы всегда из этой
- 4:26:50функции получаем указатель на канал. И
- 4:26:53это значит, что всегда, когда мы в наших
- 4:26:54функциях, в аргументах пишем следующим
- 4:26:56образом, на самом деле мы тут принимаем
- 4:26:58указательно структуру канала. Просто
- 4:27:01некоторые слои абстракции были
- 4:27:02разработчикам языка программирования
- 4:27:04Golding добавлены, что нам теперь тут не
- 4:27:06нужно писать вот эту звёздочку, а мы
- 4:27:08можем без неё писать название канала и
- 4:27:10принимать его, и он автоматически будет
- 4:27:13тут передаваться по указателю. Но в
- 4:27:14случае с вейтгруппой это не так. В
- 4:27:16случае с вейтгруппой у неё даже нету
- 4:27:18конструктора. Вейтгруппа - это просто
- 4:27:20структура, которая лежит в пакете S. И
- 4:27:22нам, чтобы этой структурой
- 4:27:23воспользоваться, нужно обратиться к
- 4:27:24пакету S, вызвать эту структуру и
- 4:27:26создать её. После чего положить какую-то
- 4:27:28переменную. И мы уже тут сами вальны
- 4:27:29решать, создаём мы копию этой структуры
- 4:27:31или будем создавать сразу указатель на
- 4:27:33неё. Почему так? Почему ветг группы и
- 4:27:36каналы не подведены под какой-то общий
- 4:27:38знаменатель? Почему в случае с каналами
- 4:27:40у нас есть конструктор и у нас есть
- 4:27:42некий синтаксический сахар, который
- 4:27:43скрывает под собой подкапотную
- 4:27:45реализацию? Что у нас тут на самом деле
- 4:27:46указатель? А Woup - это просто голая
- 4:27:48структура без конструктора, которую мы
- 4:27:50сами должны создавать, сами должны брать
- 4:27:51на неё указатель и сами должны этот
- 4:27:53указатель передавать внутрь функции.
- 4:27:55Если честно, я не знаю. Это просто
- 4:27:56данность. Так решили разработчики языка
- 4:27:58программирования GLNG. Нам, как
- 4:28:00пользователям языка программирования
- 4:28:01GLН, как разработчикам на этом языке,
- 4:28:04остаётся с этим просто смириться. И мы
- 4:28:05должны с вами запомнить, что когда мы
- 4:28:07создаём канал через Makeй, на самом деле
- 4:28:09под капотом у нас возвращается указатель
- 4:28:11на созданную структуру. Поэтому, когда
- 4:28:13мы следующим образом внутрь функции
- 4:28:14передаём канал, на самом деле мы его
- 4:28:16передаём по указателю. И поэтому в
- 4:28:18разных функциях, в разных рутинах
- 4:28:20взаимодействие с этим каналом происходит
- 4:28:21корректно. И у нас тут не происходит
- 4:28:23того, что мы в одной функции работаем с
- 4:28:25копией канала, в другой функции с ещё
- 4:28:27одной копией канала. В итоге эти две
- 4:28:28копии друг про друга ничего не знают и
- 4:28:30программа ломается. Нет, мы на самом
- 4:28:32деле работаем с одним и тем же каналом.
- 4:28:34А вот в случае с группой мы должны брать
- 4:28:36на неё всегда сами этот указатель и
- 4:28:38внутри функции всегда принимать груг
- 4:28:40групппу по указателю. Потому что иначе,
- 4:28:42если мы будем принимать груг группу
- 4:28:43копией, то изменение этой группы, вызов
- 4:28:46методов этой группы внутри одной функции
- 4:28:48никак не повлияет на копию этой группы
- 4:28:50из другой функции. И в итоге мы будем
- 4:28:52зачем-то вообще вызывать методы у копии,
- 4:28:54и никак не получится у нас
- 4:28:56синхронизировать наши разные рутины.
- 4:28:58Даже редактор кода нам жёлтым
- 4:29:00подчёркивает, как бы намекая на то, что
- 4:29:01ты точно тут хочешь копию уйдт группы
- 4:29:03принимать. Нет, я тут хочу принимать по
- 4:29:05указателю. Повторюсь, почему так? Потому
- 4:29:07что разработчики языка программирования
- 4:29:08так решили. Нам с этим остаётся только
- 4:29:10жить и запоминать, как мы работаем с
- 4:29:12каналами, а как мы работаем с
- 4:29:13вейтгруппами.
- 4:29:19Ну, передаю по указателю вейт группу в
- 4:29:22нашего почтальона. Пускай давайте он её
- 4:29:24принимает.
- 4:29:26красным подсвечивается, потому что тут у
- 4:29:28нас структура, а постман наш принимает
- 4:29:30указатель на эту структуру. Ну давайте
- 4:29:32возьмём этот указатель. Раз 2 3 Или мы
- 4:29:35сразу же при создании могли взять
- 4:29:37указатель на группу и тогда передавать
- 4:29:39сразу же эту переменную. Оп, и всё. Тут
- 4:29:43создали йг- группу, сразу же взяли на
- 4:29:44неё указатель. Потом этот указатель
- 4:29:46передаём в нашу функцию. Как вед группа
- 4:29:47работает? Перед каждым запуском рутины
- 4:29:50нам нужно вгруппу добавить единичку.
- 4:29:58Для чего мы говорим вгруппе, что я
- 4:30:00сейчас запущу одну грутину? И перед
- 4:30:03каждой рутиной, перед каждым запуском
- 4:30:04рутины мы сообщаем в Т-группе, что
- 4:30:06сейчас будет запущена грутина.
- 4:30:07Собственно, таким образом вейт группа
- 4:30:09будет знать, что запущено 1 2три рутины.
- 4:30:12Мы бы могли, в принципе, один раз
- 4:30:13сказать вдгруппе, что мы запустим
- 4:30:15тригрутины вот таким вот образом, а
- 4:30:16потом эти самые тригрутин запустить. Но
- 4:30:18считается хорошей практикой добавлять в
- 4:30:20вейтгруппу какое-то значение, говорить в
- 4:30:23вейтгруппе, что мы сейчас запустим
- 4:30:24рутину прямо перед запуском этой рутины,
- 4:30:26чтобы случайно где-нибудь не запутаться.
- 4:30:28Будем придерживаться этой хорошей
- 4:30:29практики и сообщать вгруппе, что мы
- 4:30:31сейчас запустим горутину прямо перед
- 4:30:33запуском. 1 2 и три. Хорошо. Тут мы
- 4:30:37сказали опять же нашей тгруппе, что мы
- 4:30:39запустили 1 2три горутины. После этого
- 4:30:42мы можем запустить нашу программу,
- 4:30:44увидеть, что ничего не изменилось. У нас
- 4:30:45там какой-то почтальон что-то там успел
- 4:30:46сделать, даже, может быть, несколько
- 4:30:48почтальонов успело что-то сделать. Но по
- 4:30:50большой части иногда даже при запуске
- 4:30:52нашей программы всё равно, ну, ничего не
- 4:30:53успевает произойти, потому что то, что
- 4:30:54мы сделали, пока что, ну, ни на что не
- 4:30:56влияет. Мы просто создали вейт группу и
- 4:30:57сказали ей, что мы сейчас запустим 1
- 4:30:582три рутины. И что? А то, что мы, когда
- 4:31:01сказали вйтгруппе, что мы запустим
- 4:31:04rрутин сколько-то там, после этого мы
- 4:31:06можем написать vg. И тем самым мы в этом
- 4:31:10месте заблокируемся в нашем мейне до тех
- 4:31:13пор, пока количество запущенных рутин у
- 4:31:16нас не станет равно нулю. Как нам в
- 4:31:18эгруппе сообщить, что какая-то грутина
- 4:31:20завершилась? То есть тут мы сказали, что
- 4:31:22раз горутина запустилась, два гору
- 4:31:24запустилась, три горутины запустилась. А
- 4:31:26как их завершать-то? Как нам наши группе
- 4:31:28сообщать, что рутина завершилась? Всё
- 4:31:30просто. Мы в конце запущенной рутины
- 4:31:32обращаемся к вейт групппе и говорим. что
- 4:31:36с английского переводится как готово.
- 4:31:38Вот этим вызовом мы сказали нашей
- 4:31:40ветгруппе, что горутинка одна у нас
- 4:31:42завершилась. Соответственно, внутри
- 4:31:44йгруппы у нас есть некий счётчик. Когда
- 4:31:46мы добавляем туда единичку, этот счётчик
- 4:31:48увеличивается на единичку. Тут у нас
- 4:31:50счётчик ноль. Тут один, тут стал два,
- 4:31:52тут стал три. И когда мы пишем VG, мы
- 4:31:55блокируемся на этом вызове и не будем
- 4:31:57продолжаться дальше, пока счётчик не
- 4:32:00станет равен нулю. А каждый раз, когда
- 4:32:02мы вызываем VG дан, мы уменьшаем на
- 4:32:04счётчик на один. Соответственно, 1 2 3
- 4:32:07добавили, счётчик стал три. Здесь ждём.
- 4:32:10Раз. Вгддан отработал, счётчик стал два.
- 4:32:13Два вгд отработал, счётчик стал один.
- 4:32:15Третий раз ВГдан отработает, третья
- 4:32:17горутина дойдёт досюда, счётчик станет
- 4:32:19нулю равен. И мы продолжим своё
- 4:32:21выполнение и корректно дождёмся, пока
- 4:32:23все горутины у нас завершатся, пока все
- 4:32:25гарутины вызовут ВГдан, и только после
- 4:32:27этого мы завершим наш мейн. Очень
- 4:32:29хорошей практикой считается не вручную в
- 4:32:32конце рутины писать ВГдан, а в самом
- 4:32:34начале запускаемой рутины через def
- 4:32:37вызывать этот VГдан. Почему? Да потому
- 4:32:40что мы можем где-то случайно забыть его
- 4:32:41вызвать, если у нас какое-то условное
- 4:32:43ветвление или ещё что-нибудь там. Мы
- 4:32:45через def, когда вызываем vdan, мы
- 4:32:47гарантируем, что всегда, когда наша
- 4:32:50рутина, когда наша функция, запущенная
- 4:32:52вгарутине, завершится, всегда отработает
- 4:32:54vдан. Мы всегда уменьшим наш счётчик на
- 4:32:57один. Ну, давайте запустим нашу
- 4:32:58программу. Сохраняем. Запускаем.
- 4:33:01Как мы видим, я почтальон. Я отнёс
- 4:33:03газету Автомобильная хроника. Новости.
- 4:33:05Игровой журнал. Вот наши три почтальона
- 4:33:08отнесли свои журналы в первый раз. Вот
- 4:33:10наши три почтальона отнесли свои журналы
- 4:33:12во второй раз. И вот наши три почтальона
- 4:33:14отнесли свои журналы в третий раз. Все
- 4:33:16три почтальона отнесли свои журналы в
- 4:33:18каждое из трёх мест, которое им было
- 4:33:20предназначено. Мы благодаря вейтгруппе в
- 4:33:23Мейне, так сказать, покорно, пока все
- 4:33:26наши рутины отработают, пока все их
- 4:33:28функции завершатся, вызовется в Гдан.
- 4:33:30Ждали, пока счётчик в вейт гругруппе
- 4:33:32станет равен нулю. И как только
- 4:33:34последний вот этот вот VГдан отработал,
- 4:33:36тогда счётчик стал равен нулю внутри
- 4:33:38вейт группы. И вот этот вот вызов VG
- 4:33:40weight разблокировался. Мы смогли идти
- 4:33:43дальше. Таким образом, благодаря вейт
- 4:33:44группе, мы ждали, пока завершатся все
- 4:33:47наши горутины. Это очень удобно.
- 4:33:49Использование вейт группы довольно
- 4:33:50частое, потому что довольно часто бывают
- 4:33:52ситуации, когда мы хотим запустить
- 4:33:54какую-то грурутину, она нам ничего не
- 4:33:56возвращает, просто мы хотим, чтобы она
- 4:33:58отработала. И только когда несколько
- 4:34:00этих горутин отработают, в нашем случае
- 4:34:02три штуки, только тогда продолжит своё
- 4:34:04выполнение. То есть мы благодаря
- 4:34:05ветгруппе дожидаемся группу горутин.
- 4:34:07Ну-группу, в принципе, если с
- 4:34:08английского перевести, это ожидание
- 4:34:10группы, то есть ожидание какой-то группы
- 4:34:12грудин. В нашем случае это группа из
- 4:34:15трёх почтальонов.
- 4:34:17[музыка]
- 4:34:21Теперь нам необходимо познакомиться с
- 4:34:24таким явлением, как состояние гонки. На
- 4:34:27самом деле явление это встречается, к
- 4:34:29сожалению, довольно часто, и оно очень
- 4:34:32неприятное. Оно является багом в нашей
- 4:34:34программе. Если у нас где-то состояние
- 4:34:36гонки, значит, мы написали наш код
- 4:34:38неверно. Но тем не менее из-за
- 4:34:40отсутствия понимания некоторых базовых
- 4:34:42принципов новички очень часто случайно
- 4:34:45попадают в это состояние гонки. То есть
- 4:34:47это гонка данных. По-английски это
- 4:34:49называется race condition, но по-русски
- 4:34:51это состояние гонки. То есть рейс - это
- 4:34:53гонка, кондиition - состояние. Состояние
- 4:34:55гонки. Что такое состояние гонки? Для
- 4:34:57начала, чтобы понять, что такое
- 4:34:58состояние гонки, нам нужно понять, как
- 4:35:00вообще на уровне компьютера, на уровне
- 4:35:02процессора выглядит увеличение
- 4:35:04переменной на единицу. Процессор для
- 4:35:06того, чтобы увеличить переменную,
- 4:35:07которая лежит где-то в памяти на
- 4:35:09единицу, он первым делом должен
- 4:35:11прочитать значение переменной из памяти.
- 4:35:13То есть он читает это значение и
- 4:35:15смотрит, что там лежит. Ну, к примеру, у
- 4:35:16нас вот переменная number, она равна
- 4:35:18пяти. То есть процессору, чтобы вот эту
- 4:35:20вот переменную увеличить на один, он
- 4:35:22должен прочитать её значение из памяти.
- 4:35:25он берёт и вот эту вот пятёрочку, вот
- 4:35:26это значение пять, он читает и сохраняет
- 4:35:29себе в регистр. Вторым шагом процессор
- 4:35:32берёт и увеличивает эту пятёрку на
- 4:35:34шестёрку. И третьим шагом ему необходимо
- 4:35:37эту шестёрку сохранить обратно в
- 4:35:39переменную number. Берёт и сохраняет
- 4:35:41обратно в переменную number. То есть,
- 4:35:43казалось бы, такая простая операция, как
- 4:35:45увеличить переменную на один, она
- 4:35:47многосоставная. Для начала нужно себе в
- 4:35:49регистры из оперативной памяти прочитать
- 4:35:51значение, которое лежит в переменной
- 4:35:53Number, потом внутри своих регистров
- 4:35:55увеличить это значение на один, то есть
- 4:35:57посчитать, воспользоваться там своими
- 4:35:58внутренними модулями для калькуляции,
- 4:36:01для арифметических операций, получить
- 4:36:03новое значение и третьим шагом сохранить
- 4:36:05обратно в память. То есть простые такие
- 4:36:08операции: как увеличить переменную, как
- 4:36:10уменьшить, умножить, поделить, там не
- 4:36:12обязательно на один вообще на любое
- 4:36:13число. Просто изменить значение
- 4:36:14переменной - это всегда три этапа.
- 4:36:16прочитать её старое значение, увеличить,
- 4:36:19ну, в нашем случае на один, получить
- 4:36:20новое значение и уже третьим шагом
- 4:36:22сохранить обратно в память. Казалось бы,
- 4:36:24ну, хорошо, сложная операция,
- 4:36:27многосоставная. И что с того? А то, что
- 4:36:29когда у нас запущено несколько грутин,
- 4:36:32которые одновременно работают с одной
- 4:36:34переменной, у нас возникает состояние
- 4:36:36гонки. Что это значит? Представим, что
- 4:36:38несколько грутин одновременно работают с
- 4:36:40этой переменной намр, и они одновременно
- 4:36:42хотят её увеличить на один. Первая
- 4:36:44горутина берёт и читает из памяти
- 4:36:46значение этой переменной. Прочитала.
- 4:36:49Вторая грутина берёт и в этот же момент
- 4:36:51тоже читает значение этой переменной.
- 4:36:53Хорошо. Следующий шаг. Первая грутина
- 4:36:56увеличивает прочитанное значение на
- 4:36:57один. И вторая рутина тоже увеличивает
- 4:37:00прочитанное значение на один. Потом они
- 4:37:02сохраняют посчитанное значение обратно в
- 4:37:04память. Первая горутина сохраняет
- 4:37:06обратно в память посчитанное значение. И
- 4:37:09вторая рутина тоже сохраняет обратно в
- 4:37:10память посчитанное значение. Что мы в
- 4:37:13итоге получили? Две горутины, которые
- 4:37:15работали параллельно. Каждая из них
- 4:37:17увеличила переменную намр на один. То
- 4:37:19есть у нас в сумме переменная намр
- 4:37:20должна была увеличиться на два. То есть
- 4:37:22было восемь. Одна горутина увеличила,
- 4:37:23вторая рутина увеличила. Должно было
- 4:37:25стать 10, но стало девять. Почему мы
- 4:37:27только что посмотрели? Потому что
- 4:37:28увеличение переменной - это
- 4:37:30многосоставная операция. Вот ещё раз. У
- 4:37:32нас numberр равно единице. У нас две
- 4:37:34горутины работают параллельно, и они
- 4:37:37хотят взять значение этой переменной
- 4:37:39numberр и увеличить его на один.
- 4:37:41Соответственно, как мы себе думаем,
- 4:37:43первая горутина возьмёт переменную
- 4:37:44набор, увеличит на один, станет равно 2.
- 4:37:46Вторая рутина возьмёт значение
- 4:37:48переменный намбор, увеличит на один,
- 4:37:49станет равно три. Как оно на самом деле?
- 4:37:51Иногда действительно будет получаться
- 4:37:54так, как мы думаем, потому что это
- 4:37:55состояние гонки, оно довольно
- 4:37:57неустойчивое и по большей части
- 4:37:59случайное. Поэтому иногда всё-таки эти
- 4:38:01горутины действительно будут выполняться
- 4:38:03так и будут работать с переменной набор
- 4:38:05так, как мы думаем. Но состояние гонки,
- 4:38:08оно коварное, оно возникает не всегда. и
- 4:38:10часто в самый неподходящий момент ломая
- 4:38:13нам всю программу. Сейчас ещё раз мы
- 4:38:15повторно рассмотрим, как это состояние
- 4:38:16гонки вообще возникает. У нас увеличение
- 4:38:18переменной - это многосоставная
- 4:38:20операция. Соответственно, первая грутина
- 4:38:22одновременно со второй, так уж вышло,
- 4:38:24они одновременно решили увеличить эту
- 4:38:26переменную numberр на один. Они читают
- 4:38:28значение переменной numberр обе. Первая
- 4:38:29рутина прочитала значение переменной
- 4:38:31numberр и запомнила его. И вторая рутина
- 4:38:34прочитала значение numberр и запомнила
- 4:38:36его. Первый шаг пройден. Потом второй
- 4:38:38шаг. увеличение прочитанного значения на
- 4:38:40один. Первая рутина увеличивает
- 4:38:43прочитанное значение на один, получает
- 4:38:45двойку. И вторая грутина тоже
- 4:38:46увеличивает прочитанное значение на
- 4:38:48один, получает двойку. Потом третий шаг
- 4:38:50- сохранить посчитанное значение обратно
- 4:38:52в память. Первая грутина возвращает
- 4:38:54обратно в память посчитанное значение. И
- 4:38:57вторая рутина тоже кладёт обратно в
- 4:38:58память посчитанное значение. В итоге мы
- 4:39:01переменную Number увеличили два раза, но
- 4:39:03она увеличилась на один только лишь один
- 4:39:05раз из-за этого самого состояния гонки.
- 4:39:08Потому что у нас параллельный доступ к
- 4:39:11одной переменной был нерегулирован. Мы
- 4:39:13никак не синхронизировали этот
- 4:39:15конкурентный доступ. В итоге у нас
- 4:39:17возникло состояние гонки. Это очень
- 4:39:19плохо, потому что наша программа
- 4:39:21начинает работать неопределённым
- 4:39:22образом. Какие-то запуски нашей
- 4:39:24программы будут увеличивать переменную
- 4:39:26number два раза. Какие-то запуски нашей
- 4:39:28программы будут увеличивать переменную
- 4:39:29numberр только лишь один раз. И мы
- 4:39:31заранее не можем сказать, как именно
- 4:39:33наша программа себя поведёт. Это
- 4:39:35называется неопределённое поведение.
- 4:39:36Неопределённое поведение - это одно из
- 4:39:39самых ужасных, что мы, как программисты,
- 4:39:42можем допустить. Поэтому нужно что-то
- 4:39:43делать с этим. Что с этим можно сделать?
- 4:39:45Есть два варианта, что можно сделать
- 4:39:48конкретно с целочисленной переменной,
- 4:39:49когда мы её хотим конкурентно,
- 4:39:51параллельно увеличивать, там, изменять,
- 4:39:53вычитать и так далее. Ну, давайте для
- 4:39:54начала само это состояние гонки мы
- 4:39:56рассмотрим. Для этого я воспользуюсь
- 4:39:57глобальной переменной. У меня будет
- 4:39:58глобальная переменная number,
- 4:40:01типа integer. Ну, пускай в ней вот нолик
- 4:40:03лежит изначально. И у меня будет
- 4:40:05функция, назову её increase, то есть
- 4:40:07увеличить, как бы она будет 1.000 раз
- 4:40:14увеличивать переменную number на один,
- 4:40:16то есть number плюс плюс. О'кей. Я
- 4:40:18запущу в нескольких разных грутинах эту
- 4:40:20функцию. То есть эта функция будет
- 4:40:22параллельно в нескольких разных грутинах
- 4:40:24тысячу раз увеличивать переменную
- 4:40:25number. Ну давайте это сделаем. Я запущу
- 4:40:27раз increк, два increase, три
- 4:40:31increк,четыре инкриз. Ну вот, чтобы уж
- 4:40:33наверняка пять и ещё раз пять, то есть у
- 4:40:36меня 1 2 3 4 5 1 2 3 4 5 10 параллельных
- 4:40:40горутин будут увеличивать 1.000 раз
- 4:40:42переменную набр на один. Что это значит?
- 4:40:44Что в итоге значение, которое я планирую
- 4:40:46получить здесь должно быть 10.000,
- 4:40:49потому что 10 горутин, каждый из них по
- 4:40:511.000 раз увеличивать набор на один.
- 4:40:5210.000 должно в итоге получиться. Ну,
- 4:40:54давайте тут мы уже владеем вейгруппой и
- 4:40:57будем дожидаться, пока все наши горутины
- 4:40:59завершатся на вейт гругруппе уже. Не
- 4:41:00будем сюда колхозить time сleep в конце,
- 4:41:02будем натг группе завязываться. Создадим
- 4:41:05вейт группу
- 4:41:07и будем её по указателю передавать в
- 4:41:11наши рутины. В каждой рутине через def
- 4:41:14будем говорить, что она завершилась,
- 4:41:15когда она действительно завершится. В
- 4:41:17начале сразу же добавлю сюда циферку 10.
- 4:41:19Хотя, повторюсь, лучше перед каждым
- 4:41:21запуском писать один. Но у нас учебный
- 4:41:24пример. Тут уж точно мы не напортачим,
- 4:41:26да, точно 10 раз запустится у нас
- 4:41:28рутина, поэтому я сразу же здесь напишу
- 4:41:3010. Везде передам группу,
- 4:41:35[аплодисменты]
- 4:41:37дождусь, пока наши горутинки завершатся,
- 4:41:41и потом выведу на экран значение
- 4:41:43переменной number.
- 4:41:45Ну, поехали. Запускаю программу.
- 4:41:49Number 6.587, 587.
- 4:41:52Хотя должно быть 10.000, вообще-то.
- 4:41:54Ну-ка ещё раз. Вообще 5.000. То есть у
- 4:41:56нас практически половина увеличений, она
- 4:41:59куда-то потерялась. Ну куда именно она
- 4:42:01потерялась, понятно. Она потерялась вот
- 4:42:03из-за того, что в половине случаев
- 4:42:04практически у нас произошло описанное
- 4:42:06состояние гонки, когда несколько грутин
- 4:42:08одновременно прочитала из памяти
- 4:42:10какое-то значение, к этому значению,
- 4:42:12прибавила единицу и сохранила его. Так
- 4:42:15как это произошло параллельно, то
- 4:42:17некоторые увеличения у нас просто
- 4:42:18потерялись. Ну, ещё раз запустим. Вот
- 4:42:20тут уже получше ситуация, но всё равно
- 4:42:22мы как бы 10.000 раз эту переменну
- 4:42:24увеличили, но в итоге мы получили только
- 4:42:267.000 раз. 72. Это никуда не годится. Мы
- 4:42:29очень много вообще взаимодействия с этой
- 4:42:31переменной потеряли просто. Из-за этого
- 4:42:32наша программа может сломаться. Вы
- 4:42:34представьте, если мы разрабатываем
- 4:42:36какую-нибудь социальную сеть и у нас
- 4:42:37одновременно там миллионы пользователей
- 4:42:39ставят лайк на пост, и у нас из-за вот
- 4:42:41этого состояния гонки мы можем кучу
- 4:42:43лайков потерять. Нам потом авторы
- 4:42:45придут, скажут: "Что за вообще фигня
- 4:42:47ваша социальная сеть? Блин, лайки
- 4:42:48куда-то исчезают. А мы такие: "Пык-мык,
- 4:42:50не знаю, я в катун". А вот состояние
- 4:42:52гонки надо отслеживать, нужно знать, что
- 4:42:54оно может возникнуть. И конкурентный
- 4:42:56доступ к переменным нужно регулировать.
- 4:42:58В данном случае явный конкурентный
- 4:43:00доступ у нас переменная numberр в
- 4:43:02глобальной области видимости, то есть
- 4:43:03она доступна всем запускаемым рутинам.
- 4:43:05Все эти запускаемые рутины без всякой
- 4:43:08синхронизации увеличивают переменную
- 4:43:09набр, что приводит к состоянию гонки. У
- 4:43:11нас много теряется наших увеличений. Что
- 4:43:13с этим можно сделать? Первое, что можно
- 4:43:15сделать, если у нас именно число, мы
- 4:43:18можем воспользоваться так называемыми
- 4:43:20атомиками. Что такое атомики? Атомики -
- 4:43:23это переменные, работа с которыми
- 4:43:26отдельно вынесена в процессоре в
- 4:43:28отдельные модули, благодаря чему у нас
- 4:43:30увеличение переменной на какое-то число
- 4:43:32является немногосоставной операцией, а
- 4:43:34односоставной. То есть увеличить
- 4:43:36переменную на один происходит за одну
- 4:43:37операцию, благодаря чему у нас
- 4:43:39становится невозможным состояние гонки.
- 4:43:41Ну давайте посмотрим, как это у нас
- 4:43:43выглядит в коде. То есть вот это вот я
- 4:43:44закомментирую и создам number уже не
- 4:43:46типа int, а типа атомиic. Вот atтоomic у
- 4:43:49нас есть в пакете Sc. И тут у нас можно
- 4:43:52выбрать, что мы хотим. Integer, там
- 4:43:53buan. Ну я хочу integer. И на 64 возьму.
- 4:43:56В принципе, это не особо важно. По
- 4:43:57умолчанию этот атомик
- 4:43:58проинициализируется значением ноль. Мы
- 4:44:00создали наш атомик, он изначально
- 4:44:02значение ноль имеет. И теперь мы хотим с
- 4:44:04ним работать. Работа с атомиком немножко
- 4:44:06отличается от работы с обычнымтом. Мы
- 4:44:08должны вызывать у атомика, так как это
- 4:44:10структура, мы должны вызывать у неё
- 4:44:11методы. Вот пишем number, точка. И тут
- 4:44:13есть addд. Это мы можем добавить
- 4:44:15какое-то число. Мы можем загрузить
- 4:44:18лежащее там число. Ну давайте добавим
- 4:44:19туда вот единичку. Вот дельта можем
- 4:44:21сколько угодно добавить. Можем добавить
- 4:44:23отрицательное число, тогда мы вычтем. Но
- 4:44:25мы вот добавляем просто единичку. И в
- 4:44:27выводе мы хотим вывести значение,
- 4:44:29которое лежит внутри атомика. Давайте
- 4:44:31запустим. Запускаем.
- 4:44:33Опа, 10.000.
- 4:44:35Ещё раз 10.000. И сколько бы раз мы не
- 4:44:37запускали, у нас всегда будет 10.000.
- 4:44:40Почему? Потому что сложение вычитания в
- 4:44:42атомике, оно реализовано в одну слитную
- 4:44:45операцию. Просто на аппаратном уровне,
- 4:44:47где-то там в арифметическо-логическом
- 4:44:49устройстве процессора, у нас есть
- 4:44:51возможность пользоваться атомиками и
- 4:44:53складывать, вычитать за одну непрерывную
- 4:44:55операцию, благодаря чему состояние гонки
- 4:44:57становится невозможным при работе с
- 4:44:58атомиками. Такие монолитные непрерывные
- 4:45:01операции, которые либо выполняются
- 4:45:02полностью, либо не выполняются вовсе,
- 4:45:05называются атомарными. Ну, просто такая
- 4:45:06терминология на будущее. У нас здесь
- 4:45:08теперь прибавление единички к нашей
- 4:45:10переменной numberр стало атомарным, то
- 4:45:13есть единая цельно нерушимая операция, в
- 4:45:15середину которой какая-то другая
- 4:45:16операция не может встроиться. Вот наша
- 4:45:18атомарная операция. До этого, когда мы
- 4:45:20обычными переменными пользовались,
- 4:45:22обычный integer, у нас эта операция была
- 4:45:24многосоставная. Когда мы начали
- 4:45:26использовать атомики, у нас эта операция
- 4:45:28стала атомарная. То есть за один
- 4:45:29непрерывный шаг выполняется наше
- 4:45:31сложение. Казалось бы, почему теперь
- 4:45:33везде не напихать атомиков? Да, потому
- 4:45:35что они занимают гораздо больше
- 4:45:36процессорного времени, чем работа с
- 4:45:38обычными переменными. То есть прибавить
- 4:45:40единичку в атомиic - это сильно дольше,
- 4:45:42чем прибавить единичку в обычный
- 4:45:44integer. Поэтому нужно понимать, когда
- 4:45:45нам нужно именно атомик использовать, а
- 4:45:47когда обычный integer. Хорошо, с
- 4:45:48атомиками разобрались. А теперь
- 4:45:50представим, что мы вместо того, чтобы
- 4:45:51прибавлять единичку в наших горутинах,
- 4:45:53будем менять слайс целых чисел. У нас
- 4:45:56есть какой-то слайс
- 4:45:58интеджеров. Ну вот это вот я
- 4:45:59закомментирую пока намбор. И мы в каждой
- 4:46:01итерации, каждой нашей запущенной рутине
- 4:46:04будем вот в сумме, во всём цикле 1.000
- 4:46:06раз добавлять в наш слайс новый элемент.
- 4:46:08Ну будем туда класть вот это вот наше I.
- 4:46:10А казалось бы, что может пойти не так,
- 4:46:12да? Вот в конце я буду выводить длину
- 4:46:13нашего слайса. То есть слайс лен и тут
- 4:46:15просто лен
- 4:46:17нашего слайса. Ну казалось бы, у нас вот
- 4:46:19слайс в глобальной области видимости, мы
- 4:46:21в него добавляем наши значения. Ну и как
- 4:46:24бы у нас 10 горутин запущено. Каждая
- 4:46:26грутина в слайс добавляет 1.000
- 4:46:28элементов. Соответственно, 10 раз по
- 4:46:301.000 должно быть 10.000 элементов в
- 4:46:32слайсе. И в конце должно вывести, что у
- 4:46:34нас длина слайса 10.000. Ну, запускаем.
- 4:46:38[аплодисменты]
- 4:46:404.000
- 4:46:423.000 четыре. То есть у нас вот здесь
- 4:46:44вот даже не половина. У нас большая
- 4:46:46часть всех операций куда-то потерялась.
- 4:46:48Куда она потерялась? Туда же, куда и
- 4:46:50потерялись прибавления единички в
- 4:46:52обычный переменный numberр, типа
- 4:46:54integer. Добавление нового элемента в
- 4:46:56слайс - это ещё более сложная операция,
- 4:46:59чем просто увеличить переменную на один.
- 4:47:01И мы сталкиваемся с теми же самыми
- 4:47:02проблемами. Так как у нас добавить
- 4:47:04элемент в слайс - это гораздо более
- 4:47:06сложная операция, чем просто увеличить
- 4:47:08переменную, то состояние гонки мало
- 4:47:10того, что встречается, оно встречается
- 4:47:12ещё чаще. Теперь у нас возникает
- 4:47:14проблема, как нам работать со слайсом
- 4:47:15параллельно из нескольких разных грутин,
- 4:47:17если он такой вот у нас плохой и
- 4:47:20конкурентно небезопасный, то есть мы не
- 4:47:22можем с ним безопасно работать в
- 4:47:24параллель, потому что он вот вообще
- 4:47:26пропускает кучу записей в него. Из-за
- 4:47:27состояния гонки у нас куча записей
- 4:47:29слайку, ну куда-то вообще потерялась.
- 4:47:30Для того чтобы такие критические секции,
- 4:47:33которые конкурентно небезопасны, делать
- 4:47:36конкурентно безопасными, существуют
- 4:47:38мюксы. Что такое? Ну, давайте я тоже
- 4:47:41создам его в глобальной области
- 4:47:42видимости, потому что у меня есть два
- 4:47:44варианта. Либо создать КС в глобальной
- 4:47:46области видимости, чтобы все запущенные
- 4:47:48рутины имели к нему доступ, либо создать
- 4:47:49его в мейне и передать через указатель,
- 4:47:51как мы это делаем своей группой. Можно,
- 4:47:53в принципе, и так, и так. Ну, давайте я
- 4:47:55просто в глобальной области видимости
- 4:47:56это сделаю. У нас есть MEX, он лежит в
- 4:47:58пакете Syn. Вот Mutex назван MTX. Ну,
- 4:48:03просто принято так Mutx называть. Так
- 4:48:04же, как контекст TX, только Mutex MTX.
- 4:48:07Вот наша критическая секция. Она у нас
- 4:48:09состоит вот пока из одной строчки.
- 4:48:10Критическая секция. Почему? Потому что
- 4:48:12тут происходит состояние гонки. Нам
- 4:48:14нужно, чтобы, когда какая-то очередная
- 4:48:16грутина наступала на эту критическую
- 4:48:18секцию, чтобы все остальные рутины
- 4:48:21завершили свою работу с этой секцией,
- 4:48:23если они какую-то работу производили,
- 4:48:25чтобы у нас не было такого, что
- 4:48:27одновременно параллельно производится
- 4:48:29операция добавления слайс, чтобы она
- 4:48:31выполнялась по очереди. С помощью
- 4:48:33Мьютекса это выглядит следующим образом.
- 4:48:35Мы блокируем к дочала критической секции
- 4:48:39и разблокируем его в конце этой
- 4:48:41критической секции. Что происходит? У
- 4:48:43нас тут сразу же много рутин запускаются
- 4:48:46и в каком-то случайном порядке попадают
- 4:48:48в начало выполнения функции Increas. Ну,
- 4:48:50в дефёре, в отложном вызове Vдан,
- 4:48:52понятно, запускается цикл. Первая
- 4:48:54грутина из всех этих десяти, которая
- 4:48:57попадёт в девятнадцатую строчку, она
- 4:48:59возьмёт на себя блокировку этого
- 4:49:01Мьютекса и продолжит выполнение
- 4:49:03критической секции. Все остальные
- 4:49:05рутины, которые попадут в девятнадцатую
- 4:49:07строчку после первой, они тоже
- 4:49:09попытаются взять блокировку на Meex, но
- 4:49:11сделать этого не смогут, потому что
- 4:49:13блокировка уже взята, и они будут
- 4:49:16покорно, пока эта блокировка
- 4:49:18освободится, чтобы её взять. Первая
- 4:49:20грутина попадает, берёт блокировку.
- 4:49:22Блокировку ещё никто не взял, потому что
- 4:49:23эта рутина первая. Взяла блокировку.
- 4:49:25Ага, смогла. Значит, идёт дальше.
- 4:49:27Добавляет в слайс один элементик.
- 4:49:29Спокойно себе добавляет. Никто ей не
- 4:49:31мешает. После того, как критическая
- 4:49:32секция закончилась, эта грутина
- 4:49:34разблокирует Мьютекс. Следующая рутина
- 4:49:37какая-нибудь случайная, которая ждала
- 4:49:39возможности взять блокировку после
- 4:49:41разблокировки другой рутины, этой самой
- 4:49:43первой, сможет взять эту блокировку.
- 4:49:46Возьмёт блокировку вторая рутина
- 4:49:47какая-нибудь случайная, и закинет в наш
- 4:49:49слайс очередной элемент. После этого
- 4:49:51также освободит блокировку и даст
- 4:49:52возможность другой какой-нибудь одной
- 4:49:55рутине зайти в эту критическую секцию.
- 4:49:57То есть мы благодаря Мьютексу в
- 4:49:59критической секции выстраиваем наши
- 4:50:01горутины в очередь, и они у нас как
- 4:50:03бегуны на эстафете, берут палочку
- 4:50:06эстафетную и потом следующей рутине её
- 4:50:08передают. Но не может быть такого, чтобы
- 4:50:10две рутины сразу взяли эту эстафетную
- 4:50:12палочку, потому что у нас один мьютекс,
- 4:50:14и мы одновременно только одну блокировку
- 4:50:16можем на него взять. Поэтому в данном
- 4:50:18случае у нас не будет никакого состояния
- 4:50:21гонки, потому что мы выстраиваем все
- 4:50:23горутины в критической секции в очередь.
- 4:50:26И не будет такого, что параллельно две
- 4:50:28рутины вошли в критическую секцию, а
- 4:50:30значит, не будет такого, что параллельно
- 4:50:32две рутины одновременно добавляют
- 4:50:33элементы в слайс. Они будут это делать
- 4:50:35последовательно, благодаря чему мы
- 4:50:37избавились от состояния гонки. Весь
- 4:50:39остальной код будет выполняться
- 4:50:40параллельно, и мы всё равно получим
- 4:50:41прибавку в скорости. Но именно
- 4:50:43критическая секция будет выполняться
- 4:50:45последовательно, благодаря чему мы
- 4:50:46избежим состояния гонки. Давайте
- 4:50:48запустим программу, посмотрим, какая у
- 4:50:49нас итоговая длина этого слайса.
- 4:50:5310.000 10.000 10.000 10.000
- 4:50:5610.000 и всегда будет 10.000, потому что
- 4:50:59пока самая последняя рутина не запишет
- 4:51:01вот в этот вот слайд своё значение, у
- 4:51:03нас не закончится блокировка на вейт
- 4:51:05группе. Ну а так как у нас критическая
- 4:51:08секция обложена Мьютексом, мы не
- 4:51:10потеряем записи из-за состояния гонки. И
- 4:51:12всё, у нас 10 горутин, каждая добавит по
- 4:51:151.000 элементов. Мы это всё дождёмся
- 4:51:17благодаря ведт гругруппе, а благодаря
- 4:51:19Мьютексу мы ещё и не потеряем записи на
- 4:51:22состоянии гонки. Всё, мы зарегулировали
- 4:51:24корректно наш конкурентный доступ к
- 4:51:27слайсу. Ну, вообще умные люди скажут,
- 4:51:29что мы же в Main Ррутине тоже имеем
- 4:51:31доступ к слайсу, да? Мы вот получаем его
- 4:51:33длину. Тоже надо бы вообще там ютексом
- 4:51:35обложить. И я отвечу: "Да, верно, но у
- 4:51:37нас слишком простой пример сейчас для
- 4:51:39такого случая, чтобы в мейне что-нибудь
- 4:51:41сломалось". Поэтому я этого не сделал.
- 4:51:43Но по-хорошему, всё верно, тут тоже
- 4:51:45нужно блокировать и разблокировать,
- 4:51:49потому что это такое правило, что если у
- 4:51:50нас есть конкурентный доступ к какой-то
- 4:51:52переменной, то нам нужно его обкладывать
- 4:51:55мьютексами, чтобы не происходило
- 4:51:56состояние гонки. Давайте разберём ещё
- 4:51:58один вариант Мьютекса, который у нас
- 4:52:00доступен. У нас вот есть обычный Mutкс,
- 4:52:03а есть RV Muteex. Сейчас посмотрим
- 4:52:05пример, когда действительно есть
- 4:52:06разница, какимксом пользоваться.
- 4:52:10[музыка]
- 4:52:14Для того, чтобы нам подступиться к
- 4:52:16пониманию вообще, что такое RV Mutex,
- 4:52:18для чего он нужен, для начала поговорим
- 4:52:20о некоторых базовых концепциях, без
- 4:52:22которых вообще нет смысла что-то дальше
- 4:52:23обсуждать. Как мы с вами уже выяснили,
- 4:52:26изменение значения переменной - это
- 4:52:28многосоставная операция. Прочитать из
- 4:52:30памяти, изменить и сохранить обратно в
- 4:52:32память. О'кей, мы поняли, что если мы в
- 4:52:34каком-то участке кода с вами меняем
- 4:52:37значение нашей переменной, то мы должны
- 4:52:39до изменения значения этой переменной
- 4:52:41взять блокировку на Мьютексе, а после
- 4:52:43изменения значения переменной, после
- 4:52:44выхода из критической секции снять
- 4:52:46блокировку с Мьютекса. Таким образом, мы
- 4:52:48благодаря Мьютексу изолируем критическую
- 4:52:50секцию, и она у нас становится
- 4:52:52конкурентнобезопасно без состояния
- 4:52:54гонки. Хорошо, но если мы в двух разных
- 4:52:56рутинах не хотим менять значение
- 4:52:58переменной, а хотим просто взять и
- 4:53:00прочитать его, одна рутина читает
- 4:53:02переменную, и другая грутина тоже в этот
- 4:53:04же момент параллельно читает значение
- 4:53:06этой переменной. Казалось бы, у нас
- 4:53:08чтение значений с памяти - это
- 4:53:11односоставная операция, то есть
- 4:53:13атомарная операция, которая
- 4:53:15конкурентнобезопасна. Единая целая
- 4:53:17операция, которая очень упрощённо говоря
- 4:53:19выполняется за один такт. То есть у нас
- 4:53:21нету возможности как-то попасть тут в
- 4:53:22состояние гонки, потому что мы что, мы
- 4:53:24просто прочитали раз, прочитали два,
- 4:53:27хорошо, типа мы берём, дочитаем, всё
- 4:53:29вроде как круто. И в таком случае может
- 4:53:31показаться, что если мы в каком-то
- 4:53:32участке кода параллельно из двух разных
- 4:53:34грутин вычитываем значение переменной,
- 4:53:36то мы это никак мьютексами можем не
- 4:53:38обкладывать. Ну то есть зачем? Мы же
- 4:53:39можем прочитать просто без состояния
- 4:53:41гонки. Чтение - это у нас односоставная
- 4:53:43операция, что как бы, ну какая разница,
- 4:53:44что они параллельно читают. Раз
- 4:53:46прочитали, два прочитали, ну хорошо,
- 4:53:48всё, типа, никакого состояния гонки нет.
- 4:53:50Но на самом деле это не так. И сейчас мы
- 4:53:52с вами посмотрим, почему. Даже если мы
- 4:53:55просто, казалось бы, читаем из памяти,
- 4:53:57просто производим атомарную операцию
- 4:53:58односоставную, всё равно нам операцию
- 4:54:00чтения переменной, при условии, что
- 4:54:02доступ к этой переменной есть у
- 4:54:04нескольких грутин, необходимо
- 4:54:06обкладывать Мьютексом. Сейчас у нас
- 4:54:07перед глазами двоичное представление
- 4:54:10десятичного числа 95. Самое обычное,
- 4:54:12знакомое нам всем десятичное число 95.
- 4:54:15Вот это его двоичное представление. 0,
- 4:54:180,1 и потом четыре единицы. Хорошо, мы
- 4:54:21можем предположить, что это наше
- 4:54:23количество лайков под постом в
- 4:54:24какой-нибудь социальной сети. Вот это
- 4:54:27наши лайки. Хорошо. И теперь представим,
- 4:54:30что у нас наша социальная сеть. У неё
- 4:54:32большое количество пользователей,
- 4:54:33большая нагрузка. Это значит, что мы,
- 4:54:35понятное дело, захотим использовать
- 4:54:37рутины. Почему мы уже разобрали в
- 4:54:39предыдущей теме, потому что рутины
- 4:54:40помогают нам предоставить наш сервис
- 4:54:42большому количеству пользователей, чтобы
- 4:54:44каждый пользователь не ждал просто всех
- 4:54:46остальных, пока мы в один поток
- 4:54:48обработаем все запросы. Нет, у нас много
- 4:54:50потоков, много грутин. Соответственно,
- 4:54:52доступ к нашей переменной лайков тоже
- 4:54:54будет из разных гартин. Например, одни
- 4:54:55пользователи на какой-то пост хотят в
- 4:54:57один момент времени поставить лайки, а
- 4:54:59другие пользователи в этот же момент
- 4:55:00времени хотят это значение лайков
- 4:55:02прочитать. Ну хорошо. Вот наша
- 4:55:04переменная лайков со значением 95. Ну и
- 4:55:06вспоминаем, что первым делом для того,
- 4:55:08чтобы поменять значение переменной, нам
- 4:55:10нужно из памяти прочитать её в регистре
- 4:55:12процессора. Хорошо. Вот мы взяли эту
- 4:55:14нашу переменную и прочитали её. Вот она,
- 4:55:1695. После этого мы производим операцию
- 4:55:19прибавления единицы. Было 95, стало 96.
- 4:55:22Вот как 96 выглядит в двоичном
- 4:55:24представлении после того, как мы
- 4:55:26прибавили единичку к 95. Ну,
- 4:55:28соответственно, 95 96 двоичное
- 4:55:30представление 96. Потом третье действие
- 4:55:33- это нам необходимо новое полученное
- 4:55:35значение сохранить обратно в память,
- 4:55:37положить обратно в переменную, которую
- 4:55:39мы изначально прочитали. И тут самый
- 4:55:41ключевой момент. Некоторые процессоры на
- 4:55:44некоторых архитектурах иногда могут
- 4:55:46сохранять переменную обратно в память.
- 4:55:48Не сразу целиком, а по половинке.
- 4:55:51Сначала первую половинку сохраняют,
- 4:55:54потом вторую половинку сохраняют.
- 4:55:56Вызвано это тем, что у нас на примерчике
- 4:55:57тут всего 8 бит в числе. А бывают у нас,
- 4:56:01как мы уже с вами помним, и
- 4:56:02тридцатидвухбитные интеджеры, и
- 4:56:04шестидесятичетырёхбитные интеджеры. И
- 4:56:07поэтому разработчики процессора приняли
- 4:56:08решение, что наиболее оптимально в
- 4:56:10некоторых ситуациях будет сохранять
- 4:56:12значение переменной не сразу целиком вот
- 4:56:14так вот его брать и переносить сюда, а
- 4:56:16по половинке. Сначала одну половинку вот
- 4:56:17так вот процессор берёт и
- 4:56:19перезаписывает, а потом вторую половинку
- 4:56:21также берёт и перезаписывает. Но тут в
- 4:56:24момент, когда процессор успел только
- 4:56:26лишь одну половинку перезаписать, может
- 4:56:28прийти ещё какая-нибудь рутина и
- 4:56:30захотеть прочитать значение вот этой
- 4:56:32переменной. В итоге, что мы с вами можем
- 4:56:34получить в один момент? что у нас первая
- 4:56:37часть нового значения уже успела
- 4:56:39перезаписаться в нашу переменную, а
- 4:56:42старая часть у нас осталась от старой
- 4:56:44переменной. Как вы видите, у нас только
- 4:56:46первые четыре бита перезаписались из
- 4:56:48нового значения, а последние четыре бита
- 4:56:51у нас остались из старого значения. Вот
- 4:56:53они. Я их вот зелёным цветом отмечу, что
- 4:56:55это наше старое значение, которое
- 4:56:56осталось ещё от предыдущей переменной.
- 4:56:58То есть у нас в очень небольшой момент
- 4:57:00времени может произойти следующая
- 4:57:02ситуация, что у нас первая половина
- 4:57:05переменной уже перезаписалась новым
- 4:57:07значением, а старая половина переменной
- 4:57:09у нас осталась ещё от старого значения,
- 4:57:12а это, извините меня, значение 111. Ну,
- 4:57:15внезапно, то есть у нас было 95, мы
- 4:57:17прибавили единичку, и получилось у нас
- 4:57:20111. Почему так? Но на самом деле,
- 4:57:24правда, ради вот это вот значение, оно,
- 4:57:25если когда-нибудь и появится, оно прямо
- 4:57:28вот в очень короткий промежуток времени
- 4:57:30будет действительно находиться в памяти.
- 4:57:32Потом оно как можно скорее всё-таки
- 4:57:34перезапишется нашим корректным значением
- 4:57:3696, потому что вторая половина
- 4:57:38переменной уже успеет дозаписаться. Это
- 4:57:41значит, что вот это мы сотрём, вот так
- 4:57:43вот перезапишется, и тут уже будет наше
- 4:57:4496, как и нужно. Но это не исключает
- 4:57:47того, что на один очень короткий
- 4:57:49промежуток времени у нас действительно
- 4:57:51получится совсем иное значение, не то,
- 4:57:53которое должно быть. И внезапно
- 4:57:55оказывается, что даже несмотря на то,
- 4:57:57что мы в принципе читаем из памяти за
- 4:57:59один такт, если к нашей переменной
- 4:58:01лайков одновременно имеет доступ
- 4:58:03несколько грутин, то мы не можем себе
- 4:58:05позволить вот так вот безопасно читать
- 4:58:08из памяти значения этой переменной и не
- 4:58:10обкладываться мьютексами. Почему? Потому
- 4:58:12что мы случайно можем начать читать из
- 4:58:14памяти значение переменной лайков именно
- 4:58:16в тот момент, когда идёт пересборка
- 4:58:18нового значения переменной. И внезапно у
- 4:58:20нас на посте должно было быть 96 лайков,
- 4:58:23а у нас 111 лайков. Но это число, оно
- 4:58:26совершенно не отражает действительность.
- 4:58:28Потом пользователи к нам придут,
- 4:58:29жалуются, что мы им накручиваем лайки. А
- 4:58:31теперь представьте, что у нас не
- 4:58:32восьмибитное число, а
- 4:58:34шестидесятичетырёхбитное. Насколько там
- 4:58:36сильно порядок ошибки может зарешать?
- 4:58:38Это как вот буквально представьте, что
- 4:58:40вы заходите на свою страницу ВКонтакте и
- 4:58:42видите, что у вас на аватарке 100.000
- 4:58:44лайков, хотя у вас там должно быть 30
- 4:58:46лайков. А просто потому, что приложение
- 4:58:48ВКонтакте не зарегулировало конкурентный
- 4:58:50доступ к переменной лайков и решило
- 4:58:52прочитать значение переменной лайков
- 4:58:54прямо посередине операции перезаписи. В
- 4:58:57итоге первая половина перезаписалась,
- 4:58:58вторая ещё не успела, и мы прямо в этот
- 4:59:00момент прочитали значение. Там было
- 4:59:02совсем не то, что нам нужно. Поэтому,
- 4:59:04несмотря на то, что, казалось бы,
- 4:59:05чтение, значение, переменно из памяти -
- 4:59:07это односоставная операция, и мы это
- 4:59:09можем делать конкурентно, безопасно, без
- 4:59:11регулировки мютексами, что? Двоечка
- 4:59:13взяли двоечку прочитали ещё прочитали,
- 4:59:15всё равно в один момент случайно тут
- 4:59:16может быть не два, а 56. И мы не будем
- 4:59:18понимать, почему тут вдруг 56 стало, а
- 4:59:21потому что какая-то другая горутинка
- 4:59:23решила эту переменную увеличить на
- 4:59:25единичку. И у нас посередине вот этой
- 4:59:26записи произошло половинчатое число,
- 4:59:28которое мы с вами рассматривали, что у
- 4:59:30нас половина битов перезаписалась,
- 4:59:31половина не перезаписалась. В итоге у
- 4:59:33нас получилось совсем не то число, что
- 4:59:34мы ждали. Ну, конкретно 56, а тут сейчас
- 4:59:36взял из головы, понятное дело, там будет
- 4:59:37чуть-чуть другое число. Вот сразу скажу,
- 4:59:40что этот случай он довольно редкий, но
- 4:59:43он уже нам даёт понять, что независимо
- 4:59:46от того, пишем мы что-то в переменную
- 4:59:48или читаем мы что-то из переменной, у
- 4:59:50нас это слайс или мапа, всё равно, если
- 4:59:53какой-то переменной, есть конкурентный
- 4:59:55доступ из разных грутин, и неважно, они
- 4:59:58читают или пишут, или пишут и читают,
- 5:00:01нам всё равно взаимодействие с этой
- 5:00:03переменной нужно регулировать. Если у
- 5:00:05нас атомики, то они это сами делают под
- 5:00:07капотом. Если у нас какой-нибудь
- 5:00:09integerстринг или там мапы, слайсы или
- 5:00:11наши какие-то кастомные структуры,
- 5:00:13конечно же, мы должны пользоваться
- 5:00:15мьютексами. О'кей, мы разобрали, что
- 5:00:17читать из памяти мы просто так не можем,
- 5:00:19если у нас конкурентный доступ к
- 5:00:21какой-то переменной. Нам в любом случае
- 5:00:23нужно обкладывать это всё взаимодействие
- 5:00:25мютексами или как-то иначе регулировать
- 5:00:27конкурентный доступ. Ну, тут стоит
- 5:00:29сделать небольшую помарочку, что каналы,
- 5:00:31они так же как атомики под капотом, нам
- 5:00:33позволяют корректно обрабатывать
- 5:00:35конкурентное взаимодействие с одним и
- 5:00:36тем же каналом. Поэтому, когда мы с вами
- 5:00:38писали и читали из канала, мы это
- 5:00:40Мьютексом не обкладывали, потому что
- 5:00:42канал в Голенге сам может регулировать
- 5:00:44конкурентный доступ. Нам вот атомики и
- 5:00:46каналы не нужно обкладывать никакими
- 5:00:48мьютоксами, даже если к ним имеется
- 5:00:49доступ в разных грутинах. Теперь мы с
- 5:00:52вами стали на один шажок ближе к
- 5:00:53пониманию того, что же такое RVMEX и для
- 5:00:55чего он нужен. Но пока ещё мы не дошли
- 5:00:58до этого самого понимания, поэтому
- 5:00:59двигаемся дальше. Переходим к
- 5:01:01конкретному примеру уже внутри кода.
- 5:01:03Представим, что мы с вами разрабатываем
- 5:01:05социальную сеть, в которой можно ставить
- 5:01:07лайки и получать количество лайков на
- 5:01:09каких-то постах. Ну вот, например, как
- 5:01:11вы ВКонтакте там запостили что-нибудь, и
- 5:01:13какие-то ваши друзья могут зайти и
- 5:01:15посмотреть количество лайков просто, а
- 5:01:17какие-то друзья могут зайти и поставить
- 5:01:19эти самые лайки. Переменную like я
- 5:01:21назову likes типа integer, и она будет
- 5:01:23равна нулю. Также опишу две функции,
- 5:01:26которые будут взаимодействовать с этой
- 5:01:28переменной. Первая функция у меня будет
- 5:01:29называться setl, и она будет просто
- 5:01:32прибавлять количество лайков на
- 5:01:33единичку. Вторая функция у меня будет
- 5:01:35называться Get Like, и она будет
- 5:01:37имитировать чтение переменной Like. Для
- 5:01:39того, чтобы сымитировать чтение
- 5:01:41переменной Like, я воспользуюсь
- 5:01:42следующим фокусом. Для компилятора
- 5:01:45Goang, для языка программирования, для
- 5:01:47нашей будущей программы здесь
- 5:01:48действительно будет выполняться
- 5:01:50настоящая операция чтения переменной
- 5:01:52Les. Но никуда этот результат не будет
- 5:01:55записываться. Просто для того, чтобы мне
- 5:01:56не создавать тут какую-то непонятную
- 5:01:58переменную. Непонятно потом, что с ней
- 5:01:59делать, как-то, может быть, на экран
- 5:02:01выводить. Но вывод на экран - это тоже
- 5:02:03какая-то отдельная операция, которая
- 5:02:05требует процессорного времени. В итоге у
- 5:02:07нас эксперимент может быть не совсем
- 5:02:09чистый. Поэтому я для чистоты
- 5:02:11эксперимента буду следующим образом
- 5:02:13читать значение переменной LX. Просто
- 5:02:15для того, чтобы чисто физически на самом
- 5:02:17деле произвести операцию чтения, но
- 5:02:19никуда этот результат я бы не записал.
- 5:02:21Конечно же, у нас приложение будет
- 5:02:23высоконагруженное, поэтому одновременно
- 5:02:25множество пользователей взаимодействует
- 5:02:27с этим приложением. Поэтому я для
- 5:02:29обработки каждого запроса
- 5:02:30пользовательского, будь то поставить
- 5:02:32лайк или получить количество лайков,
- 5:02:34буду запускать отдельную горутину.
- 5:02:36Почему так, мы с вами уже обсудили в
- 5:02:37предыдущих темах, потому что если мы
- 5:02:39каждого пользователя будем обрабатывать
- 5:02:41один за одним последовательно, без
- 5:02:43использования рутин, то мы просто очень
- 5:02:45сильно замедлим наше приложение. И
- 5:02:47пользователь, который пришёл там
- 5:02:49стотысячным, он будет ждать всех
- 5:02:51остальных 99.999,
- 5:02:54когда же мы их, наконец-таки,
- 5:02:55обработаем. Нас это не устраивает. Мы
- 5:02:57хотим обрабатывать пользователей
- 5:02:58параллельно, быстро, одновременно.
- 5:03:00Поэтому нам нужны горутины. Ну,
- 5:03:02соответственно, я возьму просто в цикле
- 5:03:04от одного до десяти возьму и буду
- 5:03:09запускать рутину setlк. В соседнем цикле
- 5:03:13также от одного до десяти
- 5:03:15я буду запускать гартину getl.
- 5:03:19Я захочу дождаться выполнения всех этих
- 5:03:21горутин, поэтому, конечно же, я тут
- 5:03:22захочу заиспользовать вейт группу,
- 5:03:26которую нужно принимать внутри наших
- 5:03:28рутин по указателю и давать понять, что
- 5:03:31наша рутина закончилась тогда, когда она
- 5:03:33действительно закончилась.
- 5:03:39О'кей. Теперь я перед запуском каждой
- 5:03:41рутины должен добавить единичку,
- 5:03:45а потом передаю вейт группу в саму
- 5:03:47горутину. То есть у меня тут будет плюс
- 5:03:49о, когда горутина выполнится, будет -1.
- 5:03:52И потом я буду ждать, когда же итоговое
- 5:03:54значение станет равно нулю. И когда оно
- 5:03:56станет равно нулю, это значит, что все
- 5:03:57запущенные горутины у меня завершились,
- 5:03:59и я могу продолжать своё выполнение. Но
- 5:04:02пока этого не случилось, я здесь вве
- 5:04:04этого ждать. Ну и, конечно же, я захочу
- 5:04:06замерить время выполнения всего этого
- 5:04:07кода. Поэтому до запуска рутин я пишу.
- 5:04:12И после отработки всех горутин, после
- 5:04:14того, как мы дождались всех их
- 5:04:15выполнение окончательного, я выведу на
- 5:04:18экран время выполнения.
- 5:04:21Time sins init time. Хорошо, давайте
- 5:04:25запустим нашу программу, посмотрим, что
- 5:04:26вообще в ней происходит, как она
- 5:04:27отрабатывает и за сколько времени. Ну,
- 5:04:30видим вообще 20 микросекунд, что очень
- 5:04:32мало. Для нашего эксперимента нам нужно
- 5:04:34сымитировать какую-то большую нагрузку
- 5:04:36на наше приложение. Поэтому каждый раз,
- 5:04:38когда я буду ставить лайк или читать эту
- 5:04:40переменную, я это буду делать не один
- 5:04:42раз, а 100.000. I меньше либо равно чем
- 5:04:45100.000 I+. И получается, в каждой
- 5:04:48рутине я уже буду 100.000 раз изменять
- 5:04:50значение переменнойлайк. И то же самое с
- 5:04:52сочтением.
- 5:04:57[аплодисменты]
- 5:05:00О'кей. Давайте теперь замерим время
- 5:05:02выполнения нашей программы. Ну и видим,
- 5:05:05что действительно стало подольше здесь.
- 5:05:07В нашей программе уже есть критическая
- 5:05:09ошибка, очень страшная, и мы с вами её
- 5:05:12разбирали. Это гонка данных. Здесь мы с
- 5:05:15вами в отдельных рутинах увеличиваем
- 5:05:17количество переменной Les. Здесь мы с
- 5:05:19вами в отдельных грутинах читаем
- 5:05:21значение этой переменной, но мы никак
- 5:05:23эти критические секции не изолируем.
- 5:05:25Вообще-то в Гошке есть инструмент,
- 5:05:27который помогает обнаруживать гонки
- 5:05:29данных автоматически. То есть, если
- 5:05:31действительно в программе были вызваны
- 5:05:33функции рутины запущены, которые
- 5:05:36действительно привели к гонке данных, то
- 5:05:38этот инструмент поможет нам эти гонки
- 5:05:39данных обнаружить. Выглядит он следующим
- 5:05:41образом. Мы во время запуска нашей
- 5:05:43программы перед main.
- 5:05:46Тире Race. Это флажок Race, который нам
- 5:05:49помогает автоматически определять
- 5:05:51наличие гонок данных в нашей программе.
- 5:05:53Запускаем и видим, что у нас
- 5:05:55действительно появилось предупреждение о
- 5:05:57том, что у нас дата Race, то есть гонка
- 5:05:59данных. И нам даже с вами тут подскажут
- 5:06:01строчки, в которых у нас эта самая гонка
- 5:06:03происходит. Вот нам написано, что в
- 5:06:05пятнадцатой строчке идёт взаимодействие
- 5:06:07с переменной и в двадцать третьей
- 5:06:08строчке доступ к этой переменной. Таким
- 5:06:10образом, нам вот этот вот флажок рейс
- 5:06:12помог определить, что в нашей программе
- 5:06:14действительно были гонки данных. Это
- 5:06:15такой вопрос, который довольно часто
- 5:06:17встречается на собеседованиях вообще,
- 5:06:19как в запущенной программе у нас
- 5:06:21определить, есть ли гонки данных или
- 5:06:22нет. Вот с помощью флажка Race сразу же
- 5:06:24встаёт вопрос тогда почему же вообще
- 5:06:26абсолютно всегда нам не запускать наши
- 5:06:27программы с флашком Race? Да, потому что
- 5:06:29запуск программ Flashom Race, он
- 5:06:31накладывает дополнительную нагрузку на
- 5:06:33саму программу. Поэтому, когда мы
- 5:06:35запускаем наши программы уже на продакш
- 5:06:37сервере, где-то на удалённом сервере,
- 5:06:39где настоящие пользователи будут
- 5:06:40использовать наше приложение, то мы этот
- 5:06:42флажок Rйс не захотим ставить, потому
- 5:06:44что он просто будет замедлять, в
- 5:06:46принципе, время выполнения каждого
- 5:06:48отдельного участка в нашей программе.
- 5:06:50Потому что каждое взаимодействие с
- 5:06:51памятью в нашей программе будет
- 5:06:53анализироваться на предмет гонок данных.
- 5:06:56Это дополнительная нагрузка, которая нам
- 5:06:58в продакшене совершенно не нужна. Но
- 5:07:00когда мы у себя локально запускаем
- 5:07:01какие-то наши программки, то время от
- 5:07:03времени полезно запустить программку с
- 5:07:05флажком Race для того, чтобы увидеть,
- 5:07:07есть ли у нас какие-то гонки данных или
- 5:07:09нету. Этот flashжок Rйс, конечно же, не
- 5:07:11суперрультимативный инструмент для
- 5:07:12поиска гонок данных, потому что если у
- 5:07:15нас не будет во время запуска очередного
- 5:07:17нашей программы вызвано тех функций,
- 5:07:20которые вызывают у нас гонку данных,
- 5:07:21проще говоря, если у нас во время
- 5:07:23очередного запуска программы с флашком
- 5:07:25Race не будет фактической гонки данных,
- 5:07:28которую прямо можно будет поймать за
- 5:07:29руку, как дешёвку, просто потому что у
- 5:07:31нас эти функции не были вызваны, тогда,
- 5:07:33конечно, нам FL Race не поможет.
- 5:07:36Всё, у нас как будто бы нету никаких
- 5:07:38гонок данных. Хотя по факту в этих
- 5:07:40местах, если эти функции будут вызваны,
- 5:07:43гонка данных будет. Поэтому флажок рейс
- 5:07:45используем, но тем не менее, как
- 5:07:46говорится, доверяй, но проверяй. Всё
- 5:07:48равно во время написания кода нужно быть
- 5:07:50очень внимательным и не допускать гонок
- 5:07:52данных. Ну, сейчас у нас эта гонка
- 5:07:54данных есть, и мы уже с вами помним, как
- 5:07:56от неё можно избавиться. В данном
- 5:07:58случае, конечно же, можно было бы
- 5:07:59воспользоваться атомиками и
- 5:08:01целочисленную переменную intставить в
- 5:08:03виде атомик инта. Но у нас сейчас тема
- 5:08:05мютексы, поэтому мы избегать гонок
- 5:08:07данных в случае с этой переменной будем
- 5:08:10при помощи Мьютекса. Ну, воспользуемся
- 5:08:12нашим стандартным Мьютексом, который мы
- 5:08:13сейчас знаем. Это Syntex самый обычный.
- 5:08:16Я буду блокировать наши критические
- 5:08:18секции и разблокировать. Таким образом,
- 5:08:20я, взяв блокировку на Мютексе, после
- 5:08:22чего освободив её, изолировал
- 5:08:24критическую секцию в семнадцатой
- 5:08:25строчке, избавившись от гонок данных
- 5:08:28именно в семнадцатой строчке. Но в
- 5:08:29двадцать шестой строчке у нас гонки
- 5:08:31данных эти самые остаются, поэтому нам
- 5:08:33их здесь тоже нужно избегать. Всё,
- 5:08:36теперь мы с вами можем запустить нашу
- 5:08:37программку с флашком Race и увидеть,
- 5:08:39что, во-первых, наша программа стала
- 5:08:40гораздо дольше выполняться. Напоминаю,
- 5:08:42сколько она выполнялась у нас без
- 5:08:43использования Мютекса,
- 5:08:46хоть и датареaceй, но мы видим, что 10
- 5:08:48мсекунд. С использованием Ютекса она
- 5:08:50стала у нас выполняться 700 мсекунд. То
- 5:08:53есть в 70 раз у нас из-за использования
- 5:08:55Мьютекса наша программа стала
- 5:08:57выполняться дольше. Казалось бы, это
- 5:08:59плохо, но по факту это нам помогает
- 5:09:01избегать гонок данных. А почему
- 5:09:02фактическое время выполнения программы
- 5:09:04стало дольше? Да, потому что мы
- 5:09:06выстроили рутины в очередь, и они уже
- 5:09:08вот именно в этой критической секции у
- 5:09:10нас не параллельно выполнялись, что
- 5:09:11вроде бы и быстрее, но приводит к гонкам
- 5:09:13данных, а медленнее. Зато мы
- 5:09:15зарегулировали их конкурентную работу с
- 5:09:18переменной Les. Хорошо, теперь мы можем
- 5:09:20опять запустить нашу программу и
- 5:09:22увидеть, что после того, как мы
- 5:09:23проставили мютексы в критических
- 5:09:25секциях, у нас нету никакой ошибки.
- 5:09:27Дальше я буду запускать программу без
- 5:09:28флашка Rйс, просто для того, чтобы она
- 5:09:30быстрее запускалась и быстрее
- 5:09:31отрабатывала. Как вы видите, один лишь
- 5:09:33флажок рейс.
- 5:09:35в три раза замедляет нашу программу. Это
- 5:09:37ещё раз ответ на вопрос, почему мы в
- 5:09:39продакшене не будем использовать
- 5:09:40программы с флашком рейс и почему этот
- 5:09:42флажок нужно использовать лишь иногда.
- 5:09:44Потому что в иных случаях он будет очень
- 5:09:46сильно замедлять нашу программу, как вот
- 5:09:47в данном случае в три раза замедление
- 5:09:49идёт. Хорошо, мы с вами вроде бы как
- 5:09:51умеем теперь менять переменную, мы умеем
- 5:09:53читать её значения. В отдельных рутинах
- 5:09:55всё круто. Теперь немножко возвращаемся
- 5:09:57опять к этой нашей схемке, к нашему
- 5:09:59состоянию гонки. Мы с вами узнали, что
- 5:10:01несмотря на то, что чтение переменно из
- 5:10:03памяти - это односоставная операция, всё
- 5:10:06равно мы не можем здесь вот так вот
- 5:10:07взять и конкурентно, безопасно читать из
- 5:10:10этой переменной LS, потому что во время
- 5:10:12нашего очередного чтения в этой
- 5:10:14переменной может быть другой горутиной
- 5:10:16параллельно идти какая-то запись. Эта
- 5:10:19запись у нас поменяет только половину
- 5:10:21нашей переменной, и мы прямо в этот вот
- 5:10:23момент прочитаем её значение. В
- 5:10:25следующий уже момент переменная
- 5:10:27полностью перезапишется новым значением.
- 5:10:29Но мы в неудачный момент решили
- 5:10:30прочитать и вот эту вот половинчатую
- 5:10:32непонятную переменную, которая только
- 5:10:34лишь наполовину обновила своё значение,
- 5:10:36мы прочитаем. В итоге можем получить
- 5:10:38совершенно иное значение. Поэтому здесь
- 5:10:40мы не можем просто так взять и без
- 5:10:41мьютекса вот это читать. У нас тут
- 5:10:43получается какой-то должен быть. Но
- 5:10:46из-за того, что у нас тут самый обычный,
- 5:10:48мы при чтении нашей самой вот этой вот
- 5:10:51переменной также будем выстраивать наши
- 5:10:53все горутины в очередь. Соответственно,
- 5:10:55когда у нас несколько грутин будет
- 5:10:57приходить за этой переменной и пытаться
- 5:10:59её прочитать, они всё равно будут это
- 5:11:01делать последовательно. Сначала эта
- 5:11:03рутина возьмёт блокировку наteк,
- 5:11:04прочитает значение переменной, потом
- 5:11:06разблокирует. Потом это возьмёт
- 5:11:08блокировку нак, прочитать значение
- 5:11:10переменной и разблокирует. Потом это
- 5:11:13возьмёт блокировку на MEx, прочитает
- 5:11:14значение переменной и разблокирует Mute.
- 5:11:17Они у нас будут выполняться одна за
- 5:11:18одной. Но тут есть один момент. На самом
- 5:11:21деле, если бы у нас была гарантия того,
- 5:11:25что во время нашего конкурентного чтения
- 5:11:27значения какой-то переменной это самое
- 5:11:29значение бы не перезаписывалось, тогда
- 5:11:32мы бы смогли на самом деле конкурентно
- 5:11:34безопасно читать значение переменной.
- 5:11:37Ну, мы знаем, да, у нас есть гарантия,
- 5:11:39что это значение никем не
- 5:11:41перезаписывается в данный момент. Оно
- 5:11:42просто лежит в памяти и его никто не
- 5:11:45меняет. В таком случае мы бы реально
- 5:11:46смогли взять в разных грутинах значения
- 5:11:48этой переменной прочитать и всё. При
- 5:11:50этом сделать это параллельно и при этом
- 5:11:52конкурентнобезопасно. Но нам для этого
- 5:11:54нужна гарантия, что в этот момент никто
- 5:11:57не будет менять значение этой самой
- 5:11:59переменной. То есть, если бы у нас была
- 5:12:01гарантия, что в тот момент, когда мы
- 5:12:03начинаем параллельно читать значение
- 5:12:05нашей переменной из разных горутин,
- 5:12:07никто другой не менял бы значение этой
- 5:12:10переменной в момент чтения, тогда мы бы
- 5:12:13смогли гарантировать безопасное,
- 5:12:15конкурентное, параллельное чтение
- 5:12:18значения одной переменной. При том, что
- 5:12:19у нас опять же гарантия, что никто в
- 5:12:21этот момент не пытается поменять
- 5:12:23значение этой переменной. Потому что
- 5:12:25если в этот момент никто не пытается
- 5:12:27поменять значение этой переменной,
- 5:12:29значит, мы, в принципе, исключаем
- 5:12:30ситуацию, что наша переменная будет
- 5:12:32изменена только на половинку, и мы
- 5:12:34прочитаем вот эту вот наполовину
- 5:12:35изменённую переменную. Нет, если у нас
- 5:12:38есть гарантия того, что никто не меняет
- 5:12:40эту переменную в данный момент, тогда мы
- 5:12:42сможем безопасно, параллельно читать
- 5:12:44значение этой самой переменной, зная,
- 5:12:46что там лежит корректное, посчитанное
- 5:12:48нужное значение, зная, что там лежит
- 5:12:50финальное корректное значение.
- 5:12:52Получается, при обеспечении гарантии,
- 5:12:54что в какой-то определённый момент никто
- 5:12:56не меняет значение данной переменной, мы
- 5:12:58бы могли сразу большому количеству
- 5:13:00горутин позволить прийти и параллельно
- 5:13:03начать читать значение этой переменной.
- 5:13:06Тем самым мы избежим проблемы, что во
- 5:13:08время чтения какой-то переменной все эти
- 5:13:11горутины выстраиваются в очередь друг за
- 5:13:13другом. Нет, если у нас есть гарантия,
- 5:13:16что в данный момент никто значение этой
- 5:13:18переменной не меняет, тогда мы можем
- 5:13:20позволить этим гартинам начать читать
- 5:13:22значение переменной не друг за другом, а
- 5:13:24параллельно. Как будто бы никакого
- 5:13:26мьютекса и нету. Тем самым мы бы могли
- 5:13:28не терять время, которое мы теряем из-за
- 5:13:31того, что наши горутины одна за одной
- 5:13:33читают переменную, и при этом обеспечить
- 5:13:35безопасность этой операции. Ну, в Гошке
- 5:13:37есть такой инструмент, и называется он
- 5:13:39как раз-таки RV Mutex. Как он выглядит?
- 5:13:42Выглядит он точно так же, как обычный
- 5:13:44Mex, и лежит он точно так же в пакете
- 5:13:46Syn, только называется не Mex, а RV
- 5:13:49Mutex. У этого RВК помимо обычных
- 5:13:51методов log и unlock есть ещё методы
- 5:13:55rlog и rl. Что это такое? Это
- 5:13:59возможность отдельно взять блокировку на
- 5:14:01чтение и отдельно освободить блокировку
- 5:14:04на чтение. Как это используется? Мы
- 5:14:06благодаря нашему РВМЮксу во время
- 5:14:09изоляции критической секции, которая
- 5:14:11отвечает за чтение, можем брать
- 5:14:13блокировки, собственно, на чтение. Что
- 5:14:16это нам даст? Давайте я сначала всё
- 5:14:18верну, как было. Обычный кс верну нам, и
- 5:14:20мы замерим время выполнения нашей
- 5:14:22программы, когда мы использовали обычный
- 5:14:24Muttex. Запускаю и вижу, что около 200
- 5:14:27мсекунд. И сколько бы я не запускал,
- 5:14:30плюс-минус 200 мскунд наша программа
- 5:14:32выполняется. А теперь мы возьмём и
- 5:14:34заиспользуем RVM Muteex. И во время
- 5:14:36изоляции критической секции, которая
- 5:14:38отвечает за чтение переменной, я возьму
- 5:14:40и тут буду использовать блокировки
- 5:14:42специально для чтения. Теперь я запускаю
- 5:14:44программу и вижу, что начинает она
- 5:14:46работать буквально в два раза быстрее.
- 5:14:48даже больше, чем в два раза быстрее.
- 5:14:50Было 200, 205-207 миссекунд, стало 88.
- 5:14:54То есть в два плюс раза быстрее начала
- 5:14:57работать наша программа. Почему же наша
- 5:14:59программа начала работать в два раза
- 5:15:01быстрее? Связано это с тем, как именно
- 5:15:03работает R MEEX. Не случайно у РВ
- 5:15:06Мьютекса есть два разных вида
- 5:15:08блокировки. Обычно и блокировка на
- 5:15:10чтение. Чем они отличаются? Ну, для
- 5:15:12начала определимся с терминологией. Мы в
- 5:15:14нашем случае, в случае с Rмmютексом,
- 5:15:16mtx.log, log, то есть обычную блокировку
- 5:15:19будем называть блокировкой на запись, а
- 5:15:21mtx.rlog мы будем называть блокировкой
- 5:15:23на чтение. Хорошо. Так вот, вспоминаем,
- 5:15:26как работает наша обычная блокировка. В
- 5:15:27данном случае блокировка на запись. Одна
- 5:15:29какая-то рутина приходит и берёт эту
- 5:15:31блокировку. Когда другая рутина попадёт
- 5:15:33в эту секцию и попытается взять эту
- 5:15:35блокировку, она будет ждать, пока первая
- 5:15:38грутина отпустит взятую ранее
- 5:15:40блокировку. Тем самым мы выстраиваем в
- 5:15:42этой критической секции все наши
- 5:15:44горутины в очередь. Они эту блокировку
- 5:15:46передают как эстафетную палочку. В итоге
- 5:15:48мы в критической секции исключаем
- 5:15:50состояние гонки. Хорошо, но мы это с
- 5:15:52вами, в принципе, уже знаем. Теперь
- 5:15:54давайте рассмотрим, как работает
- 5:15:55блокировка на чтение. Когда какая-то
- 5:15:57рутина первая приходит и хочет взять
- 5:16:00блокировку на чтение, она сперва будет
- 5:16:02смотреть, есть ли уже какие-то взятые
- 5:16:05блокировки на запись. И только лишь в
- 5:16:07случае, если уже никаких нету активных
- 5:16:11блокировок на запись, тогда у нас сможет
- 5:16:13взяться эта блокировка на чтение и
- 5:16:16прочитаться в этот же момент, если
- 5:16:18параллельно в блокировку на чтение
- 5:16:21пришло несколько грутин, и они все
- 5:16:23одновременно захотели взять эту
- 5:16:25блокировку. В отличие от обычной
- 5:16:27блокировки на запись, блокировку на
- 5:16:30чтение могут брать одновременно
- 5:16:32параллельно несколько рутин, но только
- 5:16:35лишь в случае, если в этот момент не
- 5:16:37взята никакая блокировка на запись.
- 5:16:40Поэтому мы в данной секции одновременно
- 5:16:43можем как избавляться от состояния
- 5:16:45гонки, так и пропускать наши горутины
- 5:16:48сюда работать параллельно. Получается,
- 5:16:50если здесь мы с вами смогли взять
- 5:16:52блокировку на чтение, это та наша самая
- 5:16:55гарантия, что в этот момент не взято
- 5:16:57никаких блокировок на записи. А если в
- 5:17:00этот момент не взято никаких блокировок
- 5:17:02на запись, это значит, что никто не
- 5:17:05пишет в нашу переменную. А если никто не
- 5:17:08пишет в нашу переменную, это значит, что
- 5:17:10мы можем конкурентно, параллельно,
- 5:17:12безопасно эту самую переменную читать,
- 5:17:15потому что мы не наткнёмся на момент,
- 5:17:17что кто-то в неё пишет, успел записать
- 5:17:19только половинку значения, и мы в итоге
- 5:17:20получили непонятно, что это блокировка
- 5:17:22на чтение - это та самая наша гарантия,
- 5:17:25которую мы хотели получить, что если у
- 5:17:28нас получилось взять эту блокировку,
- 5:17:30значит, в этот момент никто не пишет в
- 5:17:32нашу переменную. Это значит, что мы
- 5:17:34можем безопасно, параллельно,
- 5:17:35конкурентно читать значение нашей
- 5:17:37переменной. Но если вдруг пока
- 5:17:39происходит какой-то параллельное
- 5:17:41конкурентное чтение значений нашей
- 5:17:43переменной, кто-то после этого захочет
- 5:17:45прийти, взять блокировку на запись и
- 5:17:48что-то вот записать в нашу самую
- 5:17:50переменную. Что будет происходить в этот
- 5:17:51момент, когда кто-то придёт и попытается
- 5:17:53взять блокировку на запись, и в этот
- 5:17:55момент он обнаружит, что уже взяты
- 5:17:57какие-то блокировки на чтение, что в
- 5:17:59этот самый момент кто-то читает нашу
- 5:18:01переменную. Мы будем здесь в попытке
- 5:18:03взять блокировку на запись, ждать, пока
- 5:18:05все те, кто читают, они закончат читать.
- 5:18:08И после этого мы, наконец-таки, сможем
- 5:18:10взять блокировку на запись и начать
- 5:18:12записывать значения в нашу переменную.
- 5:18:14Там новые и новые значения туда
- 5:18:16перезаписывать. В момент, когда мы всё
- 5:18:18новые и новые значения перезаписываем в
- 5:18:19нашу переменную, опять может прийти
- 5:18:21кто-то, кто захочет взять блокировку на
- 5:18:23чтение. В таком случае те, кто хотят
- 5:18:25прийти и взять блокировку на чтение во
- 5:18:27время того, как кто-то другой уже пишет
- 5:18:30в нашу переменную, они будут в этом
- 5:18:32месте покорно все ждать, пока один за
- 5:18:35одним будет производиться запись в
- 5:18:37переменную, благодаря чему мы здесь
- 5:18:39будем выжидать, пока в нашу переменную
- 5:18:42будет производиться запись и когда она
- 5:18:43закончится. Когда вся запись в нашу
- 5:18:45переменную закончится, это значит, что,
- 5:18:48наконец-таки, опять соблюдается наше
- 5:18:50условие безопасного параллельного чтения
- 5:18:53значения переменной. И вот здесь вот уже
- 5:18:55опять много сразу горутин смогут взять
- 5:18:58эти свои блокировки на чтение и начать
- 5:19:00параллельно читать. В конечном счёте все
- 5:19:02эти манипуляции позволяют нам в момент
- 5:19:05чтения не заставлять все наши горутины
- 5:19:08выстраиваться одной за другой. Мы просто
- 5:19:10их просим подождать, пока наступит
- 5:19:12момент, когда никто не будет изменять
- 5:19:15значение нашей переменной. И как только
- 5:19:17мы дождёмся, что никто не меняет
- 5:19:19значения нашей переменной, мы сможем
- 5:19:21сразу же параллельно запустить много
- 5:19:23рутин в чтение нашей переменной,
- 5:19:25благодаря чему операция чтения, которая
- 5:19:28состоит у нас по сути из одного шага и
- 5:19:30которую мы могли бы в принципе
- 5:19:31производить параллельно, мы будем
- 5:19:33производить параллельно. А ту ошибку, с
- 5:19:35которой мы из-за этого сможем
- 5:19:36столкнуться, что у нас в момент
- 5:19:37параллельного чтения переменная лишь
- 5:19:39наполовину будет перезаписана, мы
- 5:19:41избегаем благодаря тому, что мы
- 5:19:43гарантируем, что в момент взятия
- 5:19:44блокировки на чтение ни одна блокировка
- 5:19:47на запись не взята. Значит, никто в этот
- 5:19:48момент не пишет. Поэтому мы, несмотря на
- 5:19:50то, что пишем в переменную всё ещё по
- 5:19:52очереди, читаем из переменной, мы уже
- 5:19:55параллельно. И благодаря этому мы видим,
- 5:19:57что время выполнения нашей программы
- 5:19:59действительно ускорилось в два раза. Ну
- 5:20:01вот ещё раз попробуем опять взять
- 5:20:02обычный, везде брать самые обычные
- 5:20:05блокировки. Сохраняем файл. И вот я
- 5:20:07запускаю программу. Видим 210 мскунд.
- 5:20:10211, 205, 209, 203. О'кей. А теперь мы
- 5:20:14используем RMUEX и позволяем нашим
- 5:20:17рутинкам параллельно читать в момент,
- 5:20:19когда у нас никто не пишет. То есть
- 5:20:21позволяем параллельно читать, при этом
- 5:20:23избегая ошибки конкурентного доступа.
- 5:20:25Запускаем и видим 85 мскунд. Ну, в
- 5:20:27принципе, больше чем в два раза наша
- 5:20:29программа благодаря ВМютексу ускорилась.
- 5:20:32Ну, можем, в принципе, для чистоты
- 5:20:33эксперимента ещё с флашком Race
- 5:20:35запустить, чтобы действительно
- 5:20:36убедиться, что никаких гонок данных у
- 5:20:37нас в этот момент нету. Всё, никаких
- 5:20:40гонок данных нету. Ну, потому что мы
- 5:20:41запустили с флашком рейс. У нас вот наша
- 5:20:43программа вот в 10 раз дольше начала
- 5:20:44выполняться, поэтому запускаем без
- 5:20:45флашка рейс. Мы же убедились, что гонок
- 5:20:47данных нету, и видим, что действительно
- 5:20:49наша программа благодаря ВМютексу начала
- 5:20:51работать гораздо быстрее, а именно в
- 5:20:52больше, чем в два раза. Тут ещё может
- 5:20:54быть такой момент, такой вопрос. Почему,
- 5:20:56если у нас RVMEX такой хороший, почему
- 5:20:58нам тогда, в принципе, везде не
- 5:20:59использовать RVMEX, даже пускай мы бы
- 5:21:01ничего не читали, просто всё равно
- 5:21:03использовать по умолчанию везде RVMEX,
- 5:21:05там, где нужно брать обычные блокировки,
- 5:21:07там, где нужно брать блокировки на
- 5:21:09чтение. Но RMEX при взятии обычных
- 5:21:12блокировок он обычно оказывается
- 5:21:14медленнее, чем обычный, потому что
- 5:21:16ВМЮксу для поддержания функциональности,
- 5:21:19взятия блокировок отдельных на запись,
- 5:21:21отдельных на чтение, ему нужен так
- 5:21:23называемый overheadх, то есть ему нужно
- 5:21:25некоторое дополнительное количество
- 5:21:26ресурсов, чтобы всё это обрабатывать.
- 5:21:28Поэтому он в обычных блокировках
- 5:21:30чуть-чуть медленнее. Поэтому, если мы
- 5:21:32видим, что нам нужно использовать только
- 5:21:34самые обычные блокировки, и мы нигде не
- 5:21:36будем использовать блокировки на чтение,
- 5:21:38лучше использовать обычный кс. Но если
- 5:21:40мы с вами видим, что у нас происходят и
- 5:21:42блокировки на запись, и хорошо бы ещё
- 5:21:44где-то уметь параллельно читать и при
- 5:21:45этом сбегать состояния гонки, то берём и
- 5:21:47используем Vtex. Я понимаю, что
- 5:21:49представленный материал может вызывать
- 5:21:51трудности в понимании, но я вам заранее
- 5:21:53говорил, что тема с РВМютоксами, она
- 5:21:55требует прямо вот отдельно вот такого
- 5:21:57рассмотрения, очень плотного, и очень
- 5:21:59много времени она требует на себя для
- 5:22:01того, чтобы её наконец-таки осознать. Но
- 5:22:03когда вы сможете осознать, что такое
- 5:22:05Mutx, что такое RV Mutex, в чём между
- 5:22:07ними отличие и когда что использовать,
- 5:22:09вы сможете писать по-настоящему
- 5:22:10производительные приложения, которые
- 5:22:12смогут работать с большой нагрузкой и
- 5:22:14обеспечивать хорошим сервисом огромное
- 5:22:16количество одновременных пользователей.
- 5:22:19[музыка]
- 5:22:23Теперь нам осталось взять и всё то, что
- 5:22:25мы прошли касаемо конкурентной работы в
- 5:22:28Голенге, собрать воедино. К выполнению
- 5:22:30предлагается следующий проект. Это схема
- 5:22:32проекта. И сейчас мы разберём её
- 5:22:34поподробнее. Мы с вами будем описывать
- 5:22:36предприятие, на котором работают шахтёры
- 5:22:38и почтальоны. Наш прораб в начале
- 5:22:40рабочего дня запускает энное количество
- 5:22:43шахтёров и энное количество почтальонов.
- 5:22:45На схеме это три шахтёра и три
- 5:22:47почтальона. По факту мы должны уметь
- 5:22:49запускать произвольное количество как
- 5:22:51шахтёров, так и почтальонов. В отличие
- 5:22:52от наших предыдущих примеров, шахтёры
- 5:22:54будут не один раз ходить в шахту,
- 5:22:56добывать уголь и завершаться, а в
- 5:22:58бесконечном цикле добывать этот самый
- 5:22:59уголь, пока мы им не скажем завершится.
- 5:23:02Говорить завершится мы им будем через
- 5:23:04контекст. Поэтому все шахтёры должны
- 5:23:05быть завязаны на каком-то контексте. Во
- 5:23:08время добычи угля каждый шахтёр будет
- 5:23:10добывать разное количество угля. И этот
- 5:23:12самый уголь, он будет приносить в пункт
- 5:23:13передачи угля, где мы уже в нашем
- 5:23:15предприятии будем принимать этот самый
- 5:23:17уголь из пункта передачи угля и
- 5:23:19складировать в нашу переменную кол.
- 5:23:21Теперь рассмотрим почтальонов. При
- 5:23:23запуске каждого почтальона мы ему будем
- 5:23:25передавать то письмо, которое он должен
- 5:23:27переносить. Этот почтальон будет раз в
- 5:23:29секунду в бесконечном цикле носить нам
- 5:23:31эти письма. Мы точно также со стороны
- 5:23:33нашего предприятия будем в пункте
- 5:23:35передачи писем эти самые письма
- 5:23:37принимать и складировать их на нашу
- 5:23:38почту. Все почтальоны, ровно так же, как
- 5:23:41и шахтёры, должны быть завязаны на
- 5:23:43каком-то контексте. И мы благодаря
- 5:23:45отмене этого контекста сможем завершить
- 5:23:47рабочий день этих почтальонов. Они
- 5:23:49донесут свои последние письма и завершат
- 5:23:51свой рабочий день. После чего мы увидим
- 5:23:53об этом вывод на экран. И в конце мы
- 5:23:55выведем весь суммарный добытый за день
- 5:23:57уголь и все суммарные полученные за день
- 5:23:59сообщения. Ну давайте приступим к
- 5:24:01написанию нашего кода. У меня совершенно
- 5:24:03пустой проект. Я создам main.goфайл. В
- 5:24:06нём объявлю главный пакет и главную
- 5:24:08функцию.
- 5:24:09Я сразу же понимаю, что у меня тут будет
- 5:24:11довольно большое количество программного
- 5:24:13кода. Я не хочу это всё в одном main.
- 5:24:15Файле писать, поэтому я хочу создать
- 5:24:17себе пакеты. Но для того, чтобы я мог
- 5:24:19работать с пакетами, мне для начала
- 5:24:21нужно объявить, что я модуль. Поэтому я
- 5:24:22так и сделаю. Got init. И в данном
- 5:24:24случае я модуль назову не study, а
- 5:24:27concurrency,
- 5:24:29как бы символизируя о том, что этот
- 5:24:30модуль призван помочь нам закрепить все
- 5:24:33полученные знания касаемо темы
- 5:24:35параллелизма. Хорошо, создал модуль. И
- 5:24:37теперь я могу начать создавать пакеты.
- 5:24:39Первый пакет я назову майнер. В нём
- 5:24:41будет вся логика, которая касается
- 5:24:42шахтёров. И второй пакет я назову
- 5:24:44постман. Там будет вся логика, которая
- 5:24:45будет касаться наших почтальонов.
- 5:24:47Хорошо, поехали писать майнера. Создам
- 5:24:49тут файл miner. Пакет Miner, конечно же,
- 5:24:52и начну с самого простого описания
- 5:24:54нашего с вами шахтёра. Функция будет у
- 5:24:57меня майнер называться с большой буквы,
- 5:24:59чтобы я в случае чего мог к ней иметь
- 5:25:01доступ из мейнпакета. Что у нас должен
- 5:25:03майнер делать? Давайте ещё раз
- 5:25:04посмотрим. Он должен в бесконечном
- 5:25:06цикле, значит, сразу же можем написать
- 5:25:08бесконечный цикл, добывать уголь и потом
- 5:25:11носить его в пункт передачи угля. То
- 5:25:13есть должен быть какой-то канал, куда мы
- 5:25:15будем носить этот уголь. Мы, в принципе,
- 5:25:16могли бы создать наш канал внутри этой
- 5:25:18функции miner. Transfer point make chan
- 5:25:23int, но тут сразу же возникает проблема,
- 5:25:25что у нас, получается, у каждого майнера
- 5:25:28был бы свой канал, но мы хотим, чтобы
- 5:25:30все запущенные нами шахтёры ходили в
- 5:25:33один и тот же канал. Это значит, что
- 5:25:35лучшим решением будет принимать этот
- 5:25:36самый канал в аргументах функции,
- 5:25:40[аплодисменты]
- 5:25:42а создавать его где-то извне. Ну и,
- 5:25:44соответственно, передавать уже потом
- 5:25:45каждому нашему шахтёру этот самый
- 5:25:46трансфер поинт. Хорошо. Потом мы захотим
- 5:25:48делать какие-то выводы на экран от имени
- 5:25:50каждого шахтёра. Я первый шахтёр, я
- 5:25:53приступил к работе. Я второй шахтёр, я
- 5:25:55приступил к работе. Я третий шахтёр, я
- 5:25:56приступил к работе. То есть нам бы
- 5:25:58хорошо внутри шахтёра понимать, каким
- 5:26:00номером нас наградили. Для этого я также
- 5:26:02буду принимать переменную N типа int, и
- 5:26:04в ней будет зашит номер шахтёра: третий,
- 5:26:06первый или же второй, ну или
- 5:26:08какой-нибудь другой, в зависимости от
- 5:26:09того, сколько мы, в принципе, запустили
- 5:26:10этих наших шахтёров. Также мы можем
- 5:26:12видеть, что каждый шахтёр у нас добывает
- 5:26:14разное количество угля. Этот параметр я
- 5:26:16также буду принимать в аргументах нашей
- 5:26:18функции и назову его Power. Также
- 5:26:20целочисленный Power, то есть мощность
- 5:26:22нашего шахтёра. Насколько шахтёР мощный,
- 5:26:24сколько угля он, в принципе, добывает в
- 5:26:26секунду. Теперь я могу, в принципе,
- 5:26:27начать описывать нашего шахтёра. Я буду
- 5:26:30писать. Я шахтёр номер. И тут вот будет
- 5:26:33номер нашего шахтёра. N начал добывать
- 5:26:37уголь.
- 5:26:39О'кей. После этого мы видим, что мы
- 5:26:41должны заснуть на 1 секунду. Тайм сп. 1
- 5:26:45секунда.
- 5:26:47После я хочу дать понять, что я шахтёр
- 5:26:52номер такой-то,
- 5:26:56добыл уголь. Всё, мы уголь добыли. И
- 5:26:59после того, как мы уголь добыли, мы
- 5:27:01должны его принести в пункт передачи
- 5:27:03угля. Но мы это и сделаем. То есть мы в
- 5:27:05наш пункт передачи угля будем нести наш
- 5:27:07уголь. А угля мы будем нести столько,
- 5:27:09сколько у нас пар. Ну, то есть какая у
- 5:27:10нас мощность, столько угля мы несём.
- 5:27:12Если у нас мощность 10, значит, мы несём
- 5:27:1410 угля. Если у нас мощность 30, значит,
- 5:27:16мы несём 30 угля. Хорошо. И после
- 5:27:18каждого раза, когда я всё-таки передал
- 5:27:20уголь, я тоже хочу об этом уведомлять. Я
- 5:27:23шахтёр номер такой-то, передал уголь. Ну
- 5:27:29и каждый раз, когда я добываю уголь и
- 5:27:30передаю, я ещё, конечно же, хочу знать,
- 5:27:32сколько именно угля я добыл и сколько
- 5:27:33угля я передал. Всё, мы с вами описали
- 5:27:36работу нашего какого-то отдельного
- 5:27:37майнера. Он в бесконечном цикле будет
- 5:27:38добывать уголь, передавать его в наш пут
- 5:27:40передачи угля и после этого выводить
- 5:27:42информацию о том, что он всё-таки уголь
- 5:27:44добыл. Тут я хочу вам показать фишечку,
- 5:27:46которую мы с вами ещё не рассматривали.
- 5:27:47Здесь мы с вами внутри майнера принимаем
- 5:27:49канал Transfer Point, и мы в него только
- 5:27:52лишь хотим писать. Мы не хотим из этого
- 5:27:54канала читать какое-то значение. Верно?
- 5:27:57Ведь у нас цель этого канала с точки
- 5:27:59зрения шахтёра - это принести туда
- 5:28:02уголь. Но мы не хотим, чтобы наш шахтёр
- 5:28:04приходил в этот канал и забирал оттуда
- 5:28:06уголь. Мы этого не хотим. Поэтому мы
- 5:28:08можем в тот момент, когда мы принимаем
- 5:28:10наш канал в аргументах функции, написать
- 5:28:13здесь такую вот стрелочку. Это сразу же
- 5:28:15говорит о том, что мы внутри майнера
- 5:28:17данный канал сможем использовать только
- 5:28:19лишь для того, чтобы в него писать.
- 5:28:21Сразу видно, что при попытке что-то
- 5:28:22прочитать из этого канала у меня пишет,
- 5:28:24что это invalid operation, то есть я не
- 5:28:26могу получать значение из канала,
- 5:28:28который send. То есть sendly - это
- 5:28:30значит канал, в который мы только можем
- 5:28:32посылать какие-то значения. Всё. Таким
- 5:28:34образом я обезопасил себя от того, что
- 5:28:35какой-то шахтёр внезапно захочет взять
- 5:28:38угля из пункта передачи угля. Хотя
- 5:28:39задача шахтёра- лишь носить туда уголь,
- 5:28:42но не брать его оттуда. Хорошо. Теперь
- 5:28:44нам нужна какая-то функция, временно
- 5:28:45назову её фу, которая бы принимала
- 5:28:47количество шахтёров, то есть майр coun.
- 5:28:51и смогла бы, исходя из этого майer
- 5:28:52count, сразу же запустить нам несколько
- 5:28:54шахтёров. Хорошо, получается, мы должны
- 5:28:57в каком-то цикле от одного до майр count
- 5:29:02запускать наших шахтёров. Этих шахтёров
- 5:29:04мы будем запускать, понятное дело, в
- 5:29:06отдельной рутине. Каждый отдельный
- 5:29:08шахтёр - это отдельная рутина, потому
- 5:29:09что если они у нас будут все в одной
- 5:29:10гарутине работать, то это, по сути,
- 5:29:12будет один шахтёр. Нам нужно, чтобы их
- 5:29:14было много. Поэтому мы запускаем
- 5:29:15отдельную гарутину для каждого шахтёра.
- 5:29:17Ну хорошо, наш каждый шахтёр требует,
- 5:29:19чтобы мы в него передали канал. Давайте
- 5:29:21вот здесь вот функции, которая будет
- 5:29:22создавать этих наших шахтёров, будем
- 5:29:24инициализировать этот самый канал.
- 5:29:26Назову его call transfer point. То есть
- 5:29:28call - это с английского уголь.
- 5:29:30Получается пункт передачи угля. Здесь
- 5:29:32это просто трансфер point, потому что
- 5:29:33внутри шахтёра. Итак, понятно, что если
- 5:29:35я шахтёр, то я буду передавать уголь
- 5:29:37никак не письма. Создаю канал
- 5:29:39целочисленный и передаю его в каждого
- 5:29:42нашего шахтёра. Потом смотрим, шахтёр
- 5:29:44требует номер свой. Номер - это у нас i.
- 5:29:47Если I у нас один, значит, это первый
- 5:29:48шахтёр. Если I2, значит второй. Ну и так
- 5:29:51далее. Потом у нас каждый шахтёр требует
- 5:29:53себе пар, то есть то, насколько он будет
- 5:29:55мощно добывать уголь. Тут можно
- 5:29:57придумывать довольно сложные правила. Я
- 5:29:58сделаю довольно просто. I x 10. То есть,
- 5:30:01если у меня шахтёр первый, значит,
- 5:30:03мощность 10. Если шахтёР второй, тогда
- 5:30:05мощность 20. Если шахтёР третий,
- 5:30:07мощность 30. Ну и так далее. Хорошо, мы
- 5:30:09научились запускать какое-то количество
- 5:30:10шахтёров, но мы также помним, что мы,
- 5:30:12вообще-то, хотели бы этих шахтёров уметь
- 5:30:15завершать. Когда у нас завершается
- 5:30:17рабочий день, мы говорим шахтёрам:
- 5:30:19"Рабочий день закончен, они последние
- 5:30:20свои угольки доносят и завершаются".
- 5:30:23Завершать какую-то группу горутин мы
- 5:30:24можем через контекст. Соответственно,
- 5:30:26нам нужно, чтобы каждый шахтёр зависел
- 5:30:28ещё, помимо прочего, от контекста.
- 5:30:32И нам этот самый контекст нужно внутрь
- 5:30:33этих шахтёров передавать. Тут, сразу
- 5:30:35скажу, можно по-разному поступить, но я
- 5:30:38сделаю так, что эти контексты я буду
- 5:30:39создавать в мейне и в мейне же отменять
- 5:30:42просто для того, чтобы нам было проще
- 5:30:43это увидеть наглядно, как работает.
- 5:30:45Поэтому я функции фу сразу же буду
- 5:30:47принимать какой-то контекст. И этот
- 5:30:49самый контекст, который я принял
- 5:30:50функцией фу, я сразу же буду передавать
- 5:30:52в майнера. Всё. Ну, теперь у нас майнер
- 5:30:55принимает контекст, но он ничего с ним
- 5:30:57не делает. Если мы контекст отменим
- 5:30:59внезапно, то майнер всё равно сможет
- 5:31:02работать, и он вообще даже на этот
- 5:31:03контекст никак не посмотрит. Нужно это
- 5:31:05исправлять. Поэтому здесь мы
- 5:31:07воспользуемся конструкцией Select. И на
- 5:31:09каждой итерации бесконечного цикла в
- 5:31:10самом её начале я буду смотреть, отменён
- 5:31:13ли контекст. И если контекст отменён,
- 5:31:16тогда я буду выводить: "Я шахтёр номер
- 5:31:19такой-то". Мой рабочий день закончен.
- 5:31:24Вот так вот. и действительно буду
- 5:31:26завершать выполнение нашей функции. Но
- 5:31:28если у меня ещё контекст не отменён, то
- 5:31:31тогда в секции дефолт я буду
- 5:31:32обрабатывать вот эту самую логику нашего
- 5:31:34шахтёра. Теперь мы смогли завязать
- 5:31:36нашего шахтёра на контекст. И
- 5:31:38действительно, когда контекст будет
- 5:31:39отменяться, то шахтёр будет дорабатывать
- 5:31:41свою последнюю добычу и после этого идти
- 5:31:44на следующую трацию бесконечного цикла и
- 5:31:46видеть, что у нас контекст отменён, а
- 5:31:48значит, рабочий день окончен. О'кей, мы
- 5:31:50создали пункт передачи угля внутри
- 5:31:52функции, которая запускает наших
- 5:31:54шахтёров. Но как нам потом получить
- 5:31:56доступ к этому пункту передачи? Как нам
- 5:31:58потом получить доступ к этому каналу из
- 5:32:00мейна, например, если мы из мейна будем
- 5:32:02вызывать вот эту вот функцию фу? Да, на
- 5:32:04самом деле довольно просто. Мы сможем
- 5:32:06этот самый канал возвращать
- 5:32:10следующим образом. Создали пункт
- 5:32:12передачи, уведомили всех шахтёров, где
- 5:32:14именно находится их пункт передачи, и
- 5:32:16потом отдали возможность
- 5:32:17взаимодействиять с этим пунктом
- 5:32:18передачи. к тому, кто в принципе
- 5:32:20инициализировал запуск всех шахтёров.
- 5:32:22Например, если я из мейна буду эту
- 5:32:23функцию F вы взывать, то мне вернётся
- 5:32:25call transfer Point, и я смогу из него
- 5:32:26вычитывать значение. Вы можете обратить
- 5:32:28внимание, что функция F у меня
- 5:32:29называется с маленькой буквы, поэтому я
- 5:32:31не смогу получить к ней доступ из мейна,
- 5:32:33и вы будете правы. Но фу - это пока что
- 5:32:35временное название. Скоро мы эту функцию
- 5:32:37переименуем. Ну давайте пока я с большой
- 5:32:39буквы назову, чтобы никто не путался. И
- 5:32:41теперь представим, что мы из мейна
- 5:32:42проинициализировали этих наших шахтёров.
- 5:32:44То есть transfer point - это у меня маer
- 5:32:48тофу, потому что fu у нас функция,
- 5:32:50которая, собственно, производит всю эту
- 5:32:51логику. Я запускаю эту функцию fu и
- 5:32:53получаю канал. После чего я захочу в
- 5:32:55каком-то цикле из этого канала читать,
- 5:32:57собственно, значение, которые нам в него
- 5:32:58приходят, вычитывать этот уголь. И если
- 5:33:01данный канал никто никогда не закроет,
- 5:33:03тогда я здесь навечно заблокируюсь и
- 5:33:05получу дедлок. Я этого не хочу.
- 5:33:07Получается, канал нужно закрывать.
- 5:33:10Закрывать канал нужно в тот момент,
- 5:33:12когда все майнеры дописали в него. Когда
- 5:33:15последний майнер допишет своё значение в
- 5:33:17канал, тогда и только тогда я смогу
- 5:33:19канал закрыть. Получается, мне каким-то
- 5:33:21образом нужно дожидаться окончания
- 5:33:23выполнения всех майнеров. А дожидаться
- 5:33:25окончания выполнения мы с вами умеем
- 5:33:26через вейт группу. Я создаю вейт группу.
- 5:33:31Перед каждым запуском нашего майнера я в
- 5:33:33эту вейт группу добавляю единичку. А
- 5:33:35потом внутри нашего майнера я эту самую
- 5:33:37вейт груг груруппу должен по указателю
- 5:33:38принимать и сообщать этой самой вейт
- 5:33:40гругруппе, когда каждый майнер будет
- 5:33:41завершён. Ну давайте я вот здесь вот
- 5:33:43буду принимать эту самую вейт группу.
- 5:33:46И как обычно по старой схеме через def
- 5:33:49vgdan я буду уверен, что когда у меня
- 5:33:51этот майнер завершится, тогда отработает
- 5:33:53Vdan, и тогда я сообщу группе, что у нас
- 5:33:55очередной майнер был завершён. Тут,
- 5:33:57конечно же, нужно передать эту группу
- 5:33:58вторым аргументом, потому что мы так и
- 5:34:00написали. И можно чуть-чуть поработать
- 5:34:02над форматированием кода. Обычно в
- 5:34:03горшке, когда слишком много входящих
- 5:34:04аргументов в функцию, то они
- 5:34:06форматируются не горизонтально, а
- 5:34:07вертикально следующим образом. Так
- 5:34:08повышается читаемость. Хотя на первый
- 5:34:10взгляд может показаться, что выглядит
- 5:34:11как-то непривычно. О'кей, получается, мы
- 5:34:14здесь создаём группу, запускаем какое-то
- 5:34:16количество майнеров, и теперь нам нужно
- 5:34:18дожидаться, пока они все выполнятся. Мы
- 5:34:19могли бы по старинке вот так вот
- 5:34:21написать VGAT. То есть получается здесь
- 5:34:23мы дождёмся, пока все наши майнеры
- 5:34:24выполнятся, и после этого я смогу
- 5:34:26закрыть наш канал.
- 5:34:28канал закроется, и получается, что здесь
- 5:34:30у нас цикл завершится, дедлока не будет,
- 5:34:32но здесь кроется очень коварная ошибка.
- 5:34:35Смотрите, мы запустили наших майнеров, и
- 5:34:37мы начали ждать, пока наши майнеры
- 5:34:38завершатся. Но канал-то мы возвращаем из
- 5:34:41нашей функции после ВГйта. Получается,
- 5:34:44мы не сможем вернуть наш канал до тех
- 5:34:46пор, пока не завершатся все майнеры. А
- 5:34:49все майнеры не завершатся, пока мы не
- 5:34:51отменим контекст. Получается, мы, в
- 5:34:52принципе, не сможем вычитать даже
- 5:34:54значение из нашего канала, потому что мы
- 5:34:56написали, что мы вернём канал только,
- 5:34:58когда все майнеры завершатся. Это
- 5:35:00неправильно. Мы хотим возвращать канал
- 5:35:02сразу же, как мы запустили всех наших
- 5:35:04майнеров, чтобы здесь иметь возможность
- 5:35:06из этого канала читать. Но при этом,
- 5:35:08когда все майнеры завершатся, мы канал
- 5:35:10хотим закрывать, чтобы тут у нас не
- 5:35:12происходило дедлока. Есть решение для
- 5:35:13этой проблемы. Мы просто ожидание
- 5:35:15завершения всех майнеров и закрытия
- 5:35:17канала выносим в ещё одну отдельную
- 5:35:20рутину.
- 5:35:22Следующим образом получается, мы
- 5:35:24запускаем наших майнеров и сразу же в
- 5:35:26отдельной рутине мы запустим ожидание
- 5:35:28окончания всех майнеров. И когда они
- 5:35:30закончат свою работу, мы закроем канал.
- 5:35:32При этом из-за того, что мы эту логику
- 5:35:33запустили в отдельной рутине, она нам не
- 5:35:36помешает сразу же после запуска майнеров
- 5:35:37вернуть пункт передачи угля, из которого
- 5:35:40мы сможем вычитывать наш поступаемый
- 5:35:41уголь. Хорошо. И тут нам осталось
- 5:35:43рассмотреть такую небольшую фичу,
- 5:35:45аналогично той, что мы использовали в
- 5:35:46майнере, а именно вот эта вот стрелочка
- 5:35:48рядом с каналом. В майнере мы сказали,
- 5:35:50что мы в канал хотим только писать.
- 5:35:53Доставать оттуда мы ничего не хотим.
- 5:35:55Можно сделать то же самое, только
- 5:35:56наоборот. Сказать, что из канала мы
- 5:35:58будем только лишь читать,
- 5:36:01а писать в канал мы ничего не будем. Для
- 5:36:03чего это нужно? Для того, чтобы кто-то
- 5:36:05после того, как запустил всех наших
- 5:36:07майнеров, получил пункт передачи угля,
- 5:36:09чтобы он туда не смог подкинуть какой-то
- 5:36:11левый уголь, чтобы мы из пункта передачи
- 5:36:14угля получали уголь только тот, что нам
- 5:36:16туда записали наши майнеры. Поэтому я
- 5:36:18ставлю вот такую вот стрелочку, и она не
- 5:36:21позволит мне в этот канал что-то
- 5:36:23записать из мейна. Ну вот, допустим,
- 5:36:25call transfer point. Я хочу сюда 10
- 5:36:26записать. Я не могу, мне опять пишет
- 5:36:28invalid operation, что я не могу
- 5:36:29записать что-то в канал, который призван
- 5:36:31только лишь получать значение.
- 5:36:34Ну, в принципе, я могу сказать, что
- 5:36:35майнера мы уже описали. Теперь нам
- 5:36:37осталось разобраться, что же это за
- 5:36:38функция фу. На самом деле, я сразу же не
- 5:36:41стал давать название этой функции, чтобы
- 5:36:42обсудить это в конце описания наших
- 5:36:44майнеров. Представленная
- 5:36:45функциональность обычно называется
- 5:36:47пулом. Так как этот пул у нас относится
- 5:36:49к майнерам, это должно называться майнер
- 5:36:51пулol.
- 5:36:53Пул чисто дословно переводится как
- 5:36:54бассейн, но если более корректно
- 5:36:56пытаться переводить это на русский язык,
- 5:36:58это скорее какое-то место жительства,
- 5:36:59какое-то объединение. То есть это
- 5:37:01объединение места жительства для наших
- 5:37:03майнеров. Вот эта вот функция, она их
- 5:37:05инициализирует, она дожидается, пока эти
- 5:37:08самые майнеры завершатся. Она закрывает
- 5:37:10канал, который к ним относится. И, в
- 5:37:12принципе, всю логистику, которая нужна
- 5:37:13для того, чтобы запустить этих майнеров,
- 5:37:15берёт на себя эта функция. Поэтому она и
- 5:37:17называется пулol. Ну и повторюсь, так
- 5:37:19как относится к майнерам, это маerр p.
- 5:37:21Теперь переходим к написанию
- 5:37:22почтальонов.
- 5:37:23Postman.go. Пакет у нас будет Postman. И
- 5:37:27то же самое мы начнём сначала с описания
- 5:37:29отдельного почтальона.
- 5:37:31Постман. Что у нас почтальон вообще
- 5:37:34должен уметь? Ну, во-первых, я точно так
- 5:37:36же захочу, как и у шахтёров, выводить
- 5:37:38номер свой. Поэтому почтальон будет
- 5:37:40принимать какой-то номер. Почтальон
- 5:37:42работает, конечно же, в бесконечном
- 5:37:43цикле. Потом почтальон будет принимать
- 5:37:45какое-то письмо и носить его нам на
- 5:37:47почту. Хорошо, я должен понять, что
- 5:37:50именно за письмо. Mail я его назову. Я
- 5:37:53ношу. У почталёнов также, как и у
- 5:37:55шахтёров, есть свой пункт передачи.
- 5:37:56Только до этого это был пункт передачи
- 5:37:58угля, а тут это пункт передачи писем.
- 5:38:01Хорошо, я должен точно так же принимать
- 5:38:03transfer pointт, только это у нас уже
- 5:38:04будет строковый канал. И я сразу же
- 5:38:07здесь пропишу, что я в этот канал буду
- 5:38:08только лишь писать. А читать из него я
- 5:38:10внутри почтальона не буду. То есть
- 5:38:12почтальон не должен читать письма,
- 5:38:14которые он относит. Хорошо, можем начать
- 5:38:16описывать.
- 5:38:18Я почтальон
- 5:38:21номер такой-то.
- 5:38:24Взял письмо.
- 5:38:26Хорошо. После того, как почтальон взял
- 5:38:28письмо, он целую одну секунду будет это
- 5:38:30самое письмо нести.
- 5:38:32[аплодисменты]
- 5:38:33Потом он скажет: "Я поч тальон номер
- 5:38:38такой-то,
- 5:38:40донёс письмо до почты". Ну и тут через
- 5:38:44двоеточие мы захотим понять, что именно
- 5:38:46за письмо он там донёс. И после того,
- 5:38:48как Почтальон донёс письмо до почты, ему
- 5:38:49нужно это письмо в эту почту передать.
- 5:38:52Соответственно, мы берём и в наш пункт
- 5:38:54передачи это самое письмо будем
- 5:38:55записывать. Письмо у нас типа strст strн
- 5:38:57канал тоже типастринг. Поэтому мы можем
- 5:38:59передавать строку в строковом канале.
- 5:39:01После того, как мы передадим наше с вами
- 5:39:02письмо, мы захотим вывести на экран, что
- 5:39:05я почталь он номер такой-то, передал
- 5:39:10письмо.
- 5:39:11Ну и выведим, что за письмо мы с вами
- 5:39:12передали. После того, как почтальон
- 5:39:14передаст своё письмо, после того, как
- 5:39:16письмо будет передано в почту, почтальон
- 5:39:18опять пойдёт на новую итерацию, опять
- 5:39:20возьмёт письмо, которое мы ему сказали,
- 5:39:22и секунду будет нести до почты, потом
- 5:39:24передавать в почту, потом скажет, что он
- 5:39:25это самое письмо передал. О'кей. Теперь
- 5:39:28мы с вами сможем начать описывать
- 5:39:29функцию Postman pool. Почему она так
- 5:39:31называется, я думаю, уже понятно. И в
- 5:39:33ней мы должны знать, сколько почталёнов
- 5:39:34мы хотим запускать. То есть это у меня
- 5:39:36будет Postman Count. И в цикле ровно
- 5:39:39столько раз, сколько мне сказали, нужно
- 5:39:41запустить этих почтальонов, я столько
- 5:39:43почтальонов и запущу. Го постма. У нас
- 5:39:46почтальон требует пункт передачи. И
- 5:39:48аналогично предыдущему примеру шахтёрами
- 5:39:50я буду тут создавать пункт передачи.
- 5:39:52Только в этот раз это mail transfer
- 5:39:54point. То есть до этого у нас был пункт
- 5:39:56передачи угля, но сейчас это пункт
- 5:39:58передачи писем. Создаю канал строкового
- 5:40:01типа и передаю его нашим шахтёрам.
- 5:40:03Дальше шахтёры захотят знать свой номер
- 5:40:05и письмо, которое они будут передавать.
- 5:40:08Как мы поймём, какой почталён какое
- 5:40:10письмо должен передавать? Ну, давайте
- 5:40:11опишем небольшую функцию в нашем пакете.
- 5:40:13Она будет у нас приватная, называться с
- 5:40:15маленькой буквы. И она будет говорить,
- 5:40:16какому почтальону какое письмо нужно
- 5:40:18носить. Назову это Postman to mail. То
- 5:40:21есть мы будем принимать номер почтальона
- 5:40:23и возвращать сообщение, которое этот
- 5:40:24почтальон должен переносить. Ну и,
- 5:40:26соответственно, N - это номер
- 5:40:27почтальона. Можно назвать это Postman
- 5:40:30Number. И возвращать мы будем какое-то
- 5:40:33письмо. И я тут хочу воспользоваться
- 5:40:34мапой. Назову я эту мапу PTM, то есть
- 5:40:36Postman to Mail. Это у меня будет мапа,
- 5:40:38ключом которой будет integer, а
- 5:40:40значением string. И я смогу первому
- 5:40:43нашему почтальону задавать письмо,
- 5:40:45второму почтальону задавать письмо, ну и
- 5:40:46так далее. У первого почтальона будет
- 5:40:48письмо семейный привет.
- 5:40:51У второго почтальона будет письмо
- 5:40:54приглашение от друга. У третьего
- 5:40:57почтальона будет письмоция
- 5:40:59из автосервиса. Ну и так мы, в принципе,
- 5:41:02дальше можем для любого почтальона
- 5:41:03задать его письмо, которое он будет
- 5:41:05переносить. Дальше я буду получать,
- 5:41:07собственно, это письмо Mail и значение
- 5:41:09ок из этой мапы. Ключом будет переданный
- 5:41:11номер почтальона. И если у меня не ОК,
- 5:41:14то есть мне передали почтальона, для
- 5:41:15которого нету писем. Например, мне
- 5:41:17передали четвёртого почтальона. Я
- 5:41:18попробую из этой мапы получить значение
- 5:41:20по ключу четыре. Этого ключа нету. Ок
- 5:41:22будет флse. Я попаду в эту секцию. И в
- 5:41:25таком случае, если мне передали
- 5:41:26почтальоны, для которого нет письма,
- 5:41:28пускай эти почтальоны разносят лотерею.
- 5:41:31Ну вот просто если ему нечего делать,
- 5:41:33писем для него нету, пускай тогда
- 5:41:34разносит лотерею. Я буду возвращать
- 5:41:36лотерея. О'кей. Ну а если мы не вернули
- 5:41:40лотерею, значит, у нас было какое-то
- 5:41:41письмо для почтальона, и я буду это
- 5:41:43самое письмо возвращать. Всё. И здесь я
- 5:41:45буду прямо в рамках вызова этой функции
- 5:41:48тут сразу же вызывать ещё одну функцию
- 5:41:50Postman to mail и передавать I.
- 5:41:53Соответственно, у меня для первого
- 5:41:54почтальона номер его будет один и письмо
- 5:41:57его будет семейный привет. Для второго
- 5:41:59почтальона у меня номер будет два и
- 5:42:01письмо его будет приглашение от друга.
- 5:42:03Для третьего почтальона у меня будет
- 5:42:05информация из автосервиса. Ну а для
- 5:42:06четвёртого и далее у меня будет лотерея.
- 5:42:08О'кей, запустили наших почтальонов. Для
- 5:42:10того, чтобы у нас был доступ к пункту
- 5:42:11передачи писем из мейна, я должен этот
- 5:42:14пункт передачи из постман пула
- 5:42:15возвращать.
- 5:42:17Это будет строковый канал. Из этого
- 5:42:19канала мы захотим в мейне только лишь
- 5:42:21читать. Поэтому я здесь ставлю эту
- 5:42:22стрелочку, и я возвращаю этот самый
- 5:42:24канал. Теперь вспоминаем, что мы хотим
- 5:42:26ровно так же, как и у шахтёров, у
- 5:42:28почтальонов, иметь возможность завершать
- 5:42:31их рабочий день, чтобы они не работали
- 5:42:33бесконечно. Для этого мы должны наших
- 5:42:35почтальонов завязать на контекст.
- 5:42:36Аналогично предыдущему примеру я буду
- 5:42:38здесь принимать некоторый контекст.
- 5:42:41И ровно также внутри каждого почтальона
- 5:42:43я буду принимать контекст и завязываться
- 5:42:46на него. Для того, чтобы мне тут
- 5:42:47завязаться на контекст, я воспользуюсь
- 5:42:49конструкцией Select. И в кейсе с
- 5:42:51контекст дан я буду ждать, пока этот
- 5:42:53контекст завершится. Если этот кейс у
- 5:42:55меня отрабатывает, значит контекст
- 5:42:57завершается, значит рабочий день
- 5:42:58окончен. Я почтальон номер такой-то, мой
- 5:43:03рабочий день закончен. Отлично. И,
- 5:43:06собственно, буду завершать выполнение
- 5:43:08нашей функции. И в секции дефолт я буду
- 5:43:10оставлять основную логику для нашего
- 5:43:12почтальона.
- 5:43:14Таким вот образом у нас тут ругается
- 5:43:16редактор кода, говорит, что не хватает
- 5:43:17аргументов, потому что мы не передаём
- 5:43:19контекст, который требуется у нас в
- 5:43:21аргументах функции. Будем его, конечно
- 5:43:22же, передавать.
- 5:43:24Дальше мы будем сталкиваться с той же
- 5:43:26проблемой, что мы раньше сталкивались с
- 5:43:28нашими шахтёрами. То, что мы возвращаем
- 5:43:29канал, но мы его не закрываем.
- 5:43:31Получается, если мы из этого канала
- 5:43:32будем читать, давайте вот здесь вот я
- 5:43:34сделаю mail transfer point, это у меня
- 5:43:36postman. Postmanpol. Если мы из циклафра
- 5:43:39из этого канала что-то будем читать, то
- 5:43:41в один момент мы обнаружим, что мы в
- 5:43:42дедлоке, потому что у нас в канал рано
- 5:43:44или поздно почталёны перестанут что-то
- 5:43:46записывать, контекст отменится, они все
- 5:43:48завершатся, а мы так и будем продолжать
- 5:43:51ждать каких-то значений из этого канала,
- 5:43:52хотя их там уже никогда не будет.
- 5:43:54Почему? Потому что канал мы не закрыли.
- 5:43:55Канал нужно закрывать, и аналогично
- 5:43:57предыдущему способу я это сделаю. То
- 5:43:58есть для того, чтобы нам закрывать этот
- 5:44:00канал, нужно, во-первых, дождаться, пока
- 5:44:01все почтальоны завершатся. Ждать всех
- 5:44:03почталёнов мы будем через вейт групппу,
- 5:44:06и они все должны её принимать.
- 5:44:10После того, как мы запускаем каждого
- 5:44:12почталёна, в конце его работы мы будем
- 5:44:15сигнализировать о том, что его работа,
- 5:44:16собственно, завершилась. Перед запуском
- 5:44:18каждого почталёна мы будем добавлять
- 5:44:19единичку. Ну и тут, вроде бы, мы должны
- 5:44:21их ждать и после этого закрывать наш
- 5:44:23канал. Но повторюсь, так делать мы не
- 5:44:25можем, потому что здесь мы заблокируемся
- 5:44:27и будем ждать, пока все почталёны
- 5:44:29завершатся, при этом даже не успев
- 5:44:30вернуть канал для передачи писем.
- 5:44:33Поэтому мы ожидание завершения всех
- 5:44:35почтальонов и закрытие канала по
- 5:44:38завершению всех почтальонов тоже будем в
- 5:44:40отдельную рутину выносить следующим
- 5:44:41образом. Ну, я могу нас с вами
- 5:44:43поздравить. Мы с вами описали шахтёров,
- 5:44:45мы с вами описали почтальонов. Теперь
- 5:44:46осталось написать логику, которая бы
- 5:44:48запускала этих наших почтальонов и
- 5:44:50шахтёров и которая бы читала входящее от
- 5:44:52них сообщения. Давайте начнём это
- 5:44:54делать. Вот наш call transfer point. Это
- 5:44:58пункт передачи угля. Он у нас майнер.
- 5:45:01Майer pool. Сразу же майer pулol у нас
- 5:45:03требует какого-то контекста. Мы захотим
- 5:45:05этих майнеров завязать на какой-то
- 5:45:07контекст. Поэтому я в мейне создам майр
- 5:45:10conex и miner cancel.
- 5:45:13Это у меня будет контекст viscel.
- 5:45:15Наследую его от контекст бэкграунда. То
- 5:45:17есть мне нужно просто иметь контекст,
- 5:45:19который я, если что, смогу отменить.
- 5:45:21Хорошо. Майнер контекстам передаём и
- 5:45:23количество майнеров. Давайте сделаем два
- 5:45:25майнера, чтобы вывод на консоль был
- 5:45:26довольно читаемым. Хорошо, запускаю
- 5:45:28наших майнеров и получаю канал, из
- 5:45:30которого я буду получать уголь. О'кей.
- 5:45:32Потом мы захотим сразу же запустить
- 5:45:34наших почтальонов. Это у меня будет Mail
- 5:45:35Transfer Point и это Postman, то есть
- 5:45:38почтальон и здесь Postman Pol. Опять же
- 5:45:40требует контекста. Для почтальонов я
- 5:45:42сделаю отдельный контекст. Postman
- 5:45:44context Postman cancel context visel
- 5:45:49context background. У меня майрконтекст
- 5:45:51и постмаконтекст не зависят друг от
- 5:45:53друга. То есть я смогу как завершить
- 5:45:55рабочий день нашим шахтёрам и при этом
- 5:45:57захочу, чтобы почтальоны продолжили
- 5:45:59работать. Также и наоборот я могу
- 5:46:01завершить рабочий день почтальоном, но
- 5:46:02захочу, чтобы наши шахтёры продолжили
- 5:46:04пахать. Поэтому я отдельно эти контексты
- 5:46:06делаю. Оба наследуют контекст бэкграунда
- 5:46:09и не завязываю друг на друге. Не делаю
- 5:46:10какой-то из этих контекстов
- 5:46:11родительским, а какой-то дочерним. Мне
- 5:46:13это здесь не нужно. О'кей. Передаём
- 5:46:14постмакотекст. И количество почталёнов
- 5:46:16тоже будет два. О'кей. Теперь нам нужно
- 5:46:18куда-то эти значения складывать. Ну, мы
- 5:46:20создадим в самом начале две переменные.
- 5:46:21Первое - это будет call, то есть наш
- 5:46:23уголь целочисленный, а вторая переменная
- 5:46:25- это будет наши письма. То есть mails-
- 5:46:27это будет слайс строк, то есть слайс
- 5:46:29писем, который мы с вами получили из
- 5:46:31почты. Теперь нам нужно каким-то образом
- 5:46:33одновременно читать из двух каналов. Мы
- 5:46:36это делать тоже умеем. Это конструкция
- 5:46:38select. И здесь кейс, когда мы с вами
- 5:46:40будем получать уголь из пункта передачи
- 5:46:42угля.
- 5:46:44и кейс, когда мы будем получать письмо
- 5:46:45из пункта передачи писем, когда мы с
- 5:46:48вами будем получать какой-то уголь, я
- 5:46:50захочу его прибавлять в переменную кол,
- 5:46:52то есть call у меня будет плюс равно c.
- 5:46:54И когда я буду получать здесь какое-то
- 5:46:55письмо, я захочу его класть в нашу
- 5:46:58стопку писем. Получается, я буду
- 5:46:59добавлять элемент в слайс равно append
- 5:47:03mails. И вот новое пришедшее письмо. Но
- 5:47:05при такой конструкции мы лишь один раз
- 5:47:07прочитаем какое-то значение, и наша
- 5:47:09программа завершится. Мы хотим несколько
- 5:47:11раз читать значение приходящее, поэтому
- 5:47:12будем делать это в бесконечном цикле.
- 5:47:16Вот так вот. Теперь что имеем? Мы
- 5:47:18создали нашу переменную, в которой мы
- 5:47:20будем складировать уголь, и создали
- 5:47:21переменную, в которой мы будем
- 5:47:22складировать наши письма. Мы создали
- 5:47:24контексты для контроля выполнения за
- 5:47:25шахтёрами и за почталёнами, после чего
- 5:47:27мы запустили шахтёров и почталёнов,
- 5:47:29получив от них пункты передачи. Дальше в
- 5:47:31бесконечном цикле при помощи селекта мы
- 5:47:32будем вычитывать приходящий уголь и
- 5:47:34вычитывать приходящие сообщения. Но тут
- 5:47:36есть проблема, что если у нас канал
- 5:47:37закроется, то мы будем вычитывать
- 5:47:39значение по умолчанию, прибавлять этот
- 5:47:41нолик, который мы будем вычитывать
- 5:47:42отсюда, и потом бесконечно идти на
- 5:47:44следующую итерацию. Также в случае с
- 5:47:45письмами. Если канал писем будет закрыт,
- 5:47:47мы здесь будем вычитывать значения по
- 5:47:49умолчанию, добавлять пустую строку в
- 5:47:51наше письма и идти на следующую
- 5:47:53итерацию. Нам это не подходит, и поэтому
- 5:47:55мы хотим знать, что полученное значение
- 5:47:57- это не значение по умолчанию. А если у
- 5:47:59нас не ок, то есть если всё-таки пришло
- 5:48:01значение по умолчанию, тогда я должен
- 5:48:03как-то об этом засигнализировать.
- 5:48:05Получается, мне нужно отдельно держать
- 5:48:06две переменные. Is call closed
- 5:48:10изначально false, и is mail closed тоже
- 5:48:13изначально false. Что это за переменные?
- 5:48:15Это два флажка, в которых я буду
- 5:48:16сохранять информацию о том, закрыт ли у
- 5:48:19меня тот или иной канал для того, чтобы
- 5:48:21не читать оттуда дальше сообщения и для
- 5:48:23того, чтобы, когда оба канала закрылись,
- 5:48:25я мог наконец-таки завершить наш цикл. И
- 5:48:27получается, наш цикл, он уже будет не
- 5:48:29совсем бесконечный. Он будет идти до тех
- 5:48:31пор, пока у нас открыт канал для
- 5:48:33передачи угля. или до тех пор, пока у
- 5:48:36нас открыт канал для передачи писем. И
- 5:48:38когда мы с вами получим значение по
- 5:48:39умолчанию, это значит канал для передачи
- 5:48:41угля у нас закрыт, и я напишу is call
- 5:48:44closed. Это у меня будет true. И сразу
- 5:48:46же я буду переходить на следующую
- 5:48:47итерацию, потому что если я здесь не
- 5:48:49буду переходить на следующую итерацию,
- 5:48:51то я хоть и узнаю, что это значение по
- 5:48:52умолчанию, я всё равно дальше его
- 5:48:54добавлю. То же самое с письмами у нас. Я
- 5:48:56хочу знать, является ли это значение
- 5:48:58значением по умолчанию. И если это у
- 5:49:00меня действительно пришло значение по
- 5:49:01умолчанию, а не кто-то действительно
- 5:49:04туда записал нормальное значение, я буду
- 5:49:06писать из mail closet true и переходить
- 5:49:08на следующую итерацию. Таким образом, в
- 5:49:10момент, когда у меня закроется канал
- 5:49:12передачи угля и канал передачи писем,
- 5:49:14завершится наш цикл. Но до тех пор, пока
- 5:49:16хотя бы один канал открытый, мы будем
- 5:49:19пытаться прочитать из этого открытого
- 5:49:20канала. Ну и что нам остаётся сделать,
- 5:49:22это, наверное, в конце всей программы
- 5:49:24вывести суммарное количество добытого
- 5:49:25угля. Суммарно добытый уголь.
- 5:49:30Это наша переменна. И
- 5:49:33суммарное количество полученных писем -
- 5:49:36это у нас размер нашего слайса mails.
- 5:49:40Таким вот образом. Но видно, что мы ещё
- 5:49:42не использовали отмену контекста
- 5:49:44майнеров и отмену контекста наших
- 5:49:46почталёнов. Ну давайте это сделаем,
- 5:49:47иначе они будут бесконечно работать.
- 5:49:49Сразу же, как только я создал контекст,
- 5:49:51я в отдельной грутине
- 5:49:53спустя 3 секунды
- 5:49:57отменю контекст майнеров. И ещё в другой
- 5:50:01рутине я через 6 секунд
- 5:50:05отменю контекст постманов, то есть наших
- 5:50:07почтальонов. Таким образом, у меня 3
- 5:50:09секунды поработают майнеры завершатся, и
- 5:50:116 секунд поработают почтальоны и
- 5:50:12завершаться. Почему я это делаю в
- 5:50:14отдельных горутинах? Да, потому что если
- 5:50:15бы это было без рутин, то получается,
- 5:50:17что я бы с вами создал контексты, 3
- 5:50:20секунды поспал, отменил контекст, потом
- 5:50:21ещё 6 секунд поспал, отменил ещё один
- 5:50:23контекст. И в итоге на момент, когда я
- 5:50:25бы запускал наших майнеров и постманов,
- 5:50:27оба контекста были бы уже отменены. Мне
- 5:50:29это не нужно. Мне нужно, чтобы в
- 5:50:31отдельных гарутинах я уже смог запустить
- 5:50:33свои почтальоны и начать читать у них и
- 5:50:35своих шахтёров. А в отдельных рутинках
- 5:50:37через какое-то время эти контексты
- 5:50:38отменились, как бы имитируя конец
- 5:50:40рабочего дня. Ну, давайте теперь
- 5:50:41запустим нашу программу и посмотрим, что
- 5:50:43тут вообще произошло, как она работает.
- 5:50:46Я почтальон один взял письмо, я
- 5:50:48почтальон 2 взял письмо. То есть первые
- 5:50:50наши два почтальона, они взяли свои
- 5:50:52письма, и шахтёр точно также первый и
- 5:50:53второй начал добывать уголь. Потом мы
- 5:50:55видим, что второй шахтёр успел добыть
- 5:50:56уголь и его передать, и начал заново
- 5:50:58добывать свой уголь. Потом мы видим, что
- 5:51:00второй почтальон у нас донёс письмо до
- 5:51:02почты и передал его и снова пошёл брать
- 5:51:04письмо. Потом первый почтальон донёс
- 5:51:06своё письмо до почты и передал его и
- 5:51:08тоже пошёл брать письмо. Потом Шахтёр
- 5:51:10первый у нас добыл уголь и передал его.
- 5:51:11И таким образом мы видим, что
- 5:51:13параллельно работают наши шахтёры и наши
- 5:51:15почтальоны. Потом в один момент мы можем
- 5:51:17наблюдать, что рабочий день второго
- 5:51:19шахтёра был закончен, а потом и рабочий
- 5:51:21день первого шахтёра. Почему? Потому что
- 5:51:23в этот момент мы отменили через 3
- 5:51:25секунды контекст для шахтёров. И после
- 5:51:27этого у нас работают только почтальоны.
- 5:51:30Ну, потом и рабочий день почтальонов был
- 5:51:31окончен. Можем для более конкретного
- 5:51:33вывода сразу же перед отменой контекста
- 5:51:35говорить, что рабочий день шахтёров
- 5:51:40окончен. И здесь тоже рабочий день почта
- 5:51:45ионов окончен.
- 5:51:48И для того, чтобы этот вывод был на
- 5:51:49экране более виден, я вот здесь добавлю
- 5:51:51вот такие вот знаки,
- 5:51:54потому что в общем выводе довольно
- 5:51:55сложно разглядеть. Запускаю. Вот наши
- 5:51:59почталённые шахтёры начали работать.
- 5:52:01Опа. Рабочий день шахтёров окончен.
- 5:52:03После этого шахтёры на своей последней
- 5:52:05итерации добывают уголь, который они
- 5:52:07начали добывать, и оканчивают свой
- 5:52:09рабочий день. Первый шахтёр и второй
- 5:52:11шахтёр окончил рабочий день. После этого
- 5:52:13мы видим, что работают только
- 5:52:15почтальоны. Когда рабочий день
- 5:52:16почтольонов окончен, они до конца
- 5:52:18доносят свои письма, которые они должны
- 5:52:20были донести, и заканчивают свой рабочий
- 5:52:22день. И мы видим, что суммарно мы добыли
- 5:52:2490 угля, и 12 писем было получено.
- 5:52:27Теперь мы можем запустить побольше наших
- 5:52:29шахтёров. Пускай уже, например, 10
- 5:52:30трудится шахтёров и 10 почталнов.
- 5:52:32Понятное дело, в выводе нам сейчас
- 5:52:34вообще будет сложно разобраться, но мы
- 5:52:35зато сможем увидеть, сколько итогового
- 5:52:37угля и сколько итоговых писем мы
- 5:52:39получили. В итоге мы получили 1.650 угля
- 5:52:42и 60 писем, хотя прошло ровно столько
- 5:52:45же. можете сами засечь время выполнения
- 5:52:47этой программы и удостовериться, что
- 5:52:48плюс-минус за одинаковое время мы смогли
- 5:52:50за счёт увеличения количества шахтёров и
- 5:52:52за счёт увеличения количества почталнов
- 5:52:54добыть гораздо больше угля и получить
- 5:52:56гораздо больше писем. По сути, мы уже
- 5:52:58реализовали изначальную концепцию. Мы
- 5:53:00смогли из нашего предприятия запустить
- 5:53:02шахтёров. Мы смогли из нашего
- 5:53:03предприятия запустить почтальонов. Они
- 5:53:05все нам приносят уголь и письма,
- 5:53:07соответственно, а мы их потом на нашем
- 5:53:09предприятии складируем. И в итоге видим,
- 5:53:11что добыли 1.650 угля и получили 60
- 5:53:13писем. Но здесь, в нашем примерчике,
- 5:53:16есть один не очень красивый момент, а
- 5:53:18именно вот этот вот кусок кода.
- 5:53:20Наверное, он кого-то из вас даже мог
- 5:53:21напугать, потому что выглядит это
- 5:53:22довольно стрёмно. Есть несколько иной
- 5:53:24способ, как мы можем читать из двух
- 5:53:27разных каналов одновременно. Давайте
- 5:53:29посмотрим, как это может выглядеть. Я
- 5:53:31запущу отдельную рутину, в которой я
- 5:53:33через циклф
- 5:53:36читать из канала call transfer point. И
- 5:53:39когда я из этого канала прочитаю, я буду
- 5:53:41в переменную кол прибавлять полученное
- 5:53:43значение и запущу ещё одну грутину, в
- 5:53:46которой я уже буду читать также через
- 5:53:49циклф, только уже письмо. И когда я
- 5:53:51получу какое-то письмо, я его хочу
- 5:53:53добавить в нашу стопку писем
- 5:53:57следующим образом. И получается, нам вся
- 5:53:59вот эта тегомотина становится не нужна.
- 5:54:01Мы в отдельной рутине будем просто
- 5:54:02читать из канала для угля, в отдельной
- 5:54:04рутине читать из канала для писем и
- 5:54:06складывать полученные значения. Ну а
- 5:54:08теперь что произойдёт? Давайте попробуем
- 5:54:09запустить нашу программу.
- 5:54:11Опа, добытый уголь ноль, получено ноль
- 5:54:14писем. Ещё раз, опять по нулям. Почему
- 5:54:16так? Да потому что раньше мы с вами
- 5:54:18читали из этих каналов сами. То есть мы
- 5:54:20вот как прораб, мы вот на месте прораба
- 5:54:22брали и читали из этих каналов. И
- 5:54:24поэтому здесь мы строго дожидались, пока
- 5:54:26наша программа завершится. Но теперь мы
- 5:54:28с вами делегировали чтение из этих
- 5:54:30каналов двум разным сотрудникам. Но сами
- 5:54:32мы никак не дожидались этих сотрудников
- 5:54:34и просто сказали: "Добыто ноль угля" и
- 5:54:36завершили своё выполнение. Поэтому нам
- 5:54:38нужно дожидаться, пока эти сотрудники
- 5:54:39дочитают до конца и своих каналов. Для
- 5:54:41этого у нас есть йгруппа.
- 5:54:45Здесь мы можем по старинке начать
- 5:54:46описывать принимаемые значения
- 5:54:49нашей функции и передавать туда в группу
- 5:54:51по указателю. Но так как это у нас
- 5:54:52анонимная функция, то она имеет прямой
- 5:54:54доступ к описанным на более верхних
- 5:54:56уровнях переменным. Поэтому можно здесь
- 5:54:58этого не делать, а получить доступ к
- 5:55:01этой вейт гругруппе напрямую, так же как
- 5:55:02мы получали доступ к переменной call
- 5:55:04здесь. Поэтому здесь я буду через def
- 5:55:07делать VG да, а перед запуском этой
- 5:55:09грутины я буду вгруппу добавлять
- 5:55:10единичку. И здесь то же самое. Def vde.
- 5:55:13И перед запуском этой грутины я буду
- 5:55:15добавлять единичку. После чего я должен
- 5:55:17дождаться этих наших двух рабочих, и я
- 5:55:20смогу корректно дождаться, пока они
- 5:55:21дочитают всё из своих каналов, и буду
- 5:55:23выводить на экран наши значения. Сразу
- 5:55:25скажу, тут у нас опять в программу
- 5:55:27закралась ошибка. Сразу скажу, это
- 5:55:29проблема с гонкой данных именно в
- 5:55:31моменте, когда мы переменной call
- 5:55:33добавляем значение и в моменте, когда мы
- 5:55:35в нашу стопку писем кладём новое письмо.
- 5:55:37Почему здесь гонка данных? Потому что мы
- 5:55:39и к переменной Call, и к переменной Mail
- 5:55:41имеем доступ из разных рутин. Первая
- 5:55:44рутина - это у нас main рутина, имеет
- 5:55:46доступ к переменной кол, которую она
- 5:55:48потом выводит на экран. Вторая грутина,
- 5:55:50которая имеет доступ к переменной кол -
- 5:55:52это вот этот наш рабочий, который
- 5:55:53вычитывает у нас из пункта передачи
- 5:55:55угля. Ну, то же самое доступ имеет как
- 5:55:57main рутина, которая потом выводит
- 5:55:59количество писем в нашей стопке, так и
- 5:56:01рутина, которая вычитывает из пункта
- 5:56:02передачи писем. И что самое коварное,
- 5:56:04эту гонку данных нам не поможет
- 5:56:06задетектить даже флажок Race. Запускаю с
- 5:56:09флажком Race.
- 5:56:11И, как мы видим, всё работает. И наш
- 5:56:13флажок Rйс вообще ничего нам не говорит.
- 5:56:16Почему? Потому что нам просто повезло,
- 5:56:18что в момент, когда мы вычитываем
- 5:56:20значение с переменной, у нас уже точно
- 5:56:23все записи будут завершены. А почему все
- 5:56:26записи будут завершены? Да потому что мы
- 5:56:28здесь ждём, пока они реально будут
- 5:56:30завершены через VGWAT. И только после
- 5:56:31того, как мы всё с вами вычитаем из этих
- 5:56:33каналов и запишем в наши переменные,
- 5:56:36только после этого мы будем читать
- 5:56:37значения этих переменных. Но это не
- 5:56:39значит, что мы здесь можем забить на
- 5:56:40регулирование конкурентного доступа,
- 5:56:42потому что в один случайный момент
- 5:56:44когда-нибудь может в нашей программе ещё
- 5:56:47какая-нибудь рутинка случайно
- 5:56:48запуститься, которая тоже будет иметь
- 5:56:51доступ к переменной кол. Например, у нас
- 5:56:52будут какие-нибудь бонусные программы, и
- 5:56:54нам в наш уголь будет добавляться по 10
- 5:56:56угля. И всё. И тут мы напоримся на гонку
- 5:56:59данных со всеми вытекающими её
- 5:57:00проблемами. Поэтому просто необходимо
- 5:57:03взять за правило, что если у нас есть
- 5:57:05доступ какой-то переменный из двух и
- 5:57:07более разных грутин, тогда мы должны это
- 5:57:09регулировать. В данном конкретном случае
- 5:57:11заместо переменных типа integer, мы
- 5:57:13можем использовать атомики. Это у нас
- 5:57:15будет atomic integer. Ну, возьму int 64.
- 5:57:18И здесь я буду делать call.D и буду
- 5:57:20передавать туда полученный уголь. Но мы
- 5:57:22сможем увидеть, что здесь вот
- 5:57:24подчёркивание. Мы принимаем из нашего
- 5:57:25канала integer, а хотим добавить in64. С
- 5:57:29точки зрения голенга мы должны приводить
- 5:57:30это всё к одному типу. И делается это
- 5:57:32следующим образом. Я в in64 в скобочке
- 5:57:35возьму приходящий int. Таким образом в
- 5:57:37метод нашего атомика будет попадать
- 5:57:39именно int. Ну а для того, чтобы
- 5:57:41регулировать конкурентный доступ к
- 5:57:43нашему слайсу, я буду использовать meex.
- 5:57:45Так как у нас здесь мы читаем этот наш
- 5:57:47слайд всего лишь единожды, а пишем мы
- 5:57:49всё время, то я заиспользую обычный,
- 5:57:52потому что здесь у меня нету какого-то
- 5:57:53конкурентного нагруженного чтения для
- 5:57:55того, чтобы я мог выделять как-то в
- 5:57:57отдельное чтение эти самые рутины,
- 5:57:58которые захотят прочитать. Поэтому мне
- 5:58:00тут rmutx не сильно нужен. Мне нужен
- 5:58:01обычный mute mtx sy mutex. И я буду
- 5:58:06перед тем, как писать что-то в наш
- 5:58:08слайс, брать блокировку на этот mute и в
- 5:58:10конце я буду разблокировать его. Точно
- 5:58:12так же, перед тем, как что-то вывести на
- 5:58:14экран, я буду брать блокировку на этот к
- 5:58:16и после этого буду разблокировать, чтобы
- 5:58:18максимально исключить конкурентный
- 5:58:20доступ, нерегулируемый, к нашей
- 5:58:22переменной, которая есть доступ у
- 5:58:23нескольких разных грутин. Ну и тут,
- 5:58:25конечно же, я должен выводить не сам
- 5:58:27атомик, а то, что возвращает нам метод,
- 5:58:29а именно значение, которое лежит внутри
- 5:58:31этого атомика. Теперь вот это вот месиво
- 5:58:33из кода я могу отсюда удалить.
- 5:58:35И мы видим, что получили более красивое
- 5:58:37решение, где просто у нас в двух
- 5:58:38отдельных горутинках вычитывается этот
- 5:58:40самый канальчик, и его полученное
- 5:58:42значение кладётся в наши переменные в
- 5:58:44стопку угля и в стопку писем. Можно
- 5:58:46подумать, что тогда нам, получается,
- 5:58:47сект вообще не нужен, но нет, мы здесь
- 5:58:49очень активно используем селект. И это
- 5:58:51тот самый популярный кейс использования
- 5:58:54селекта, когда мы в бесконечном цикле на
- 5:58:56селекте ожидаем завершение отмену нашего
- 5:58:59контекста. И если контекст отменён, мы
- 5:59:01завершаем своё выполнение, иначе мы
- 5:59:03выполняем основную логику нашей рутины.
- 5:59:06Вот это самый популярный кейс
- 5:59:07использования селекта. Но тем не менее
- 5:59:09есть ещё множество других кейсов,
- 5:59:10поэтому не думайте, что селект полезен
- 5:59:12только для этого. Но конкретно в нашем
- 5:59:14случае, на мой личный взгляд, более
- 5:59:16красиво воспользоваться следующей
- 5:59:17записью, нежели мы до этого через циклф
- 5:59:20руками какие-то флажки отслеживали. Как
- 5:59:22по мне, довольно некрасиво.
- 5:59:24Ну давайте финальный запуск нашей
- 5:59:26программы сделаем и посмотрим, как она
- 5:59:27работает теперь. Ну, запускаем нашу
- 5:59:29программу. Можем с флажком Race, но мы
- 5:59:31уже с вами выяснили, что флажок Race он
- 5:59:33нам поможет найти гонку данных только
- 5:59:35если нам так повезло, что во время
- 5:59:37запуска программы по-настоящему была
- 5:59:40задетекчена гонка данных. Иначе Flagжок
- 5:59:43Race нам не поможет. То есть в нашем
- 5:59:45запуске программы здесь как бы не было
- 5:59:47гонки данных, потому что мы сначала
- 5:59:48записали в нашу переменную значения и
- 5:59:50только после этого вычитали оттуда, но
- 5:59:52нам просто так повезло. Могло быть
- 5:59:54иначе. Но тем не менее оставлю этот
- 5:59:56флажок рейс на всякий случай, и мы можем
- 5:59:57запустить нашу программу. Мы запустили
- 6:00:00наших шахтёров, почталёнов. Они
- 6:00:01работают, носят письма. И как мы видим,
- 6:00:03что почталёны, для которых мы с вами не
- 6:00:05прописали конкретные письма, которые они
- 6:00:07будут носить, они носят лотерею. Ну и
- 6:00:09всё, в конце мы получаем те наши самые
- 6:00:101.650 угля и 60 писем. Можем даже для
- 6:00:13прикола сейчас ещё больше запустить
- 6:00:14наших шахтёров и почтальонов. 100
- 6:00:16шахтёров у нас будет и 100 почтальонов.
- 6:00:18Ну просто по приколу посмотрим, что
- 6:00:20будет. Не зря же мы с вами описывали
- 6:00:21такую крутую логику, которая нам
- 6:00:23позволяет переменное количество шахтёров
- 6:00:24и почтальонов запускать. Ну, поехали.
- 6:00:27Напоминаю, 100 шахтёров, 100
- 6:00:28почтальонов.
- 6:00:31Ждём, пока они все выполнятся. Очень
- 6:00:33много выводов от сотни шахтёров, сотни
- 6:00:34почтоонов. И в конце мы получаем 600
- 6:00:37писем и 151.500
- 6:00:40добытого угля, что довольно много. И это
- 6:00:43всё при том, что время выполнения
- 6:00:44программы осталось неизменным. Ну,
- 6:00:46давайте для чистоты эксперимента я
- 6:00:47всё-таки буду это самое время выполнения
- 6:00:49программы замерять. После запуска наших
- 6:00:52шахтёров и почталёнов я буду засекать
- 6:00:54время. Init time, time now. И в конце я
- 6:00:59буду выводить
- 6:01:02затраченное время time since ийм. Ну,
- 6:01:06сначала я запущу для трёх шахтёров и
- 6:01:08трёх почтальонов. Давайте посмотрим
- 6:01:10только без флашка рейс, чтобы он не
- 6:01:11влиял на время выполнения нашей
- 6:01:12программы.
- 6:01:15видим, три шахтёра, три почтальона
- 6:01:16добывают какой-то там уголь. 6 секунд
- 6:01:19прошло. Теперь я сразу же здесь поставлю
- 6:01:21хоть 300.
- 6:01:23300 шахтёров, 300 почтальонов.
- 6:01:26Трактористов у нас нету, хотя могли бы и
- 6:01:28написать. Ждём, ждём, ждём. Всё те же 6
- 6:01:32секунд. Только мы теперь получили
- 6:01:341.354.500
- 6:01:37угля и 1.800 писем. Чувствуете разницу?
- 6:01:40Мы за счёт рутин. Мы благодаря тому, что
- 6:01:42наши шахтёры и наши почтальоны работали
- 6:01:44в отдельных гартинах, мы смогли за одно
- 6:01:47и то же время кратно увеличить
- 6:01:49количество добытого угля. В этом то
- 6:01:51самое преимущество рутин. Ну а на этом
- 6:01:53примере мы с вами разобрали очень много
- 6:01:55тем, на самом деле мы с вами разобрали,
- 6:01:56в принципе, рутины, вейтгруппы,
- 6:01:59контексты, каналы, селект, атомики,
- 6:02:02мютексы, в принципе, регулирования
- 6:02:04доступа к переменным, к которым есть
- 6:02:06доступ у двух и более рутин. Всё это в
- 6:02:08сумме нам дало такой большой обширный
- 6:02:10пример, как всё, что мы с вами прошли,
- 6:02:12касаемо конкурентной работы нашего
- 6:02:14приложения, работает сообща. По сути, мы
- 6:02:16с вами полностью и корректно решили
- 6:02:19изначально поставленную задачу, но тем
- 6:02:21не менее в представленном коде есть пара
- 6:02:23моментов, которые бы я хотел с вами
- 6:02:25обсудить напоследок. Первый момент.
- 6:02:27Кто-то мог заметить, что мы как функцию
- 6:02:29Miner, так и функцию Postman не
- 6:02:31используем за рамками пакета, в котором
- 6:02:34эти функции были объявлены. А поэтому,
- 6:02:36возможно, стоило эти функции назвать
- 6:02:38изначально с маленькой буквы, чтобы их
- 6:02:40инкапсулировать.
- 6:02:41[аплодисменты]
- 6:02:42Ну, мол, мы же в мейне нигде напрямую
- 6:02:45функцию майнер не используем, а значит,
- 6:02:46зачем нам иметь к ней доступ? И по сути
- 6:02:49люди, которые руководствуются данной
- 6:02:51логикой, будут правы, потому что зачем
- 6:02:52нам иметь эту функцию с большой буквы?
- 6:02:54Она же будет нам просто загораживать
- 6:02:56подсказки редактора кода. Когда я
- 6:02:58обращаюсь к пакету Майнер, очевидно, я
- 6:03:00хочу обратиться к майнер-пулу. По
- 6:03:01крайней мере, в нашем примере, я не хочу
- 6:03:03зачем-то отдельно иметь доступ к
- 6:03:05майнеру, самому к функции майнер,
- 6:03:07поэтому резонно назвать её с маленькой
- 6:03:09буквы. Это имеет основание данная
- 6:03:11логика, и ничего против неё я в данный
- 6:03:12момент сказать не могу. Но возможно в
- 6:03:15будущем какие-нибудь наши коллеги,
- 6:03:17которые будут пользоваться нашими
- 6:03:18пакетами, захотят вручную научиться
- 6:03:21отдельно создавать майнеров уже под свои
- 6:03:24задачи. Например, захотят создать
- 6:03:25майнера, у которого пар будет 100.000. А
- 6:03:28мы не предоставляем через наш майнер
- 6:03:30пулol таких возможностей. Мы через наш
- 6:03:32майнер пулol предоставляем лишь
- 6:03:33возможность менять количество запущенных
- 6:03:35майнеров, но не позволяем как-то точечно
- 6:03:38настраивать мощность наших шахтёров. И
- 6:03:40тут уже два варианта. Либо как-то
- 6:03:41кастомизировать и дотюнивать наш майнер
- 6:03:44пулol, дописывать его и менять для того,
- 6:03:46чтобы можно было точечно каким-то
- 6:03:47отдельным шахтёрам задавать силу, либо
- 6:03:49предоставлять напрямую нашим коллегам
- 6:03:51доступ к этой функции майр, чтобы они
- 6:03:52уже сами свои кастомные майнер пулы
- 6:03:54могли написать и запускать уже готовую
- 6:03:57реализацию шахтёра. Тут можно посмотреть
- 6:03:59на этот вопрос с двух сторон. И мы эти
- 6:04:00две стороны с вами обсудили. Ни та, ни
- 6:04:02другая сторона не является ни плохой, ни
- 6:04:03хорошей. Просто нужно действовать по
- 6:04:05ситуации. Но вообще довольно-таки
- 6:04:07хорошая логика изначально, если не
- 6:04:10предполагается где-то использование
- 6:04:12функции, структуры, метода или какой-то
- 6:04:15переменной из других пакетов, тогда
- 6:04:18называть эту функцию, структуру метод
- 6:04:19или переменную с маленькой буквы, просто
- 6:04:22чтобы не загромождать вот этот вот вывод
- 6:04:24в подсказках нашего редактора кода.
- 6:04:27Сейчас у нас майнер с маленькой буквы, а
- 6:04:29значит, из других пакетов доступен
- 6:04:30только майнер пулol, что мы, в принципе,
- 6:04:32изначально при разработке нашего пакета
- 6:04:34и предполагали. А потом уже по мере
- 6:04:36необходимости, если мы будем понимать,
- 6:04:38что вот без этого нашего майнера в
- 6:04:39других пакетах никак, тогда уже и
- 6:04:41назовём его с большой буквы. Давайте
- 6:04:43Постмана тоже верну с большуй буквы.
- 6:04:44Пока это был первый момент. Второй
- 6:04:46момент, давайте здесь сделаем количество
- 6:04:47наших шахтёров три и запустим нашу
- 6:04:51программу. Работают наши шахтёры
- 6:04:53почтальоны работают, работают. И как мы
- 6:04:55видим, когда заканчивается рабочий день
- 6:04:57шахтёров, они не сразу идут домой.
- 6:05:00Шахтёры дорабатывают свою последнюю
- 6:05:02добычу угля. Третий шахтёр дорабатывает
- 6:05:05свою последнюю добычу угля. Первый
- 6:05:07шахтёр дорабатывает свою добычу угля. И
- 6:05:09второй шахтёр дорабатывает свою добычу
- 6:05:11угля. И только в конце доработки они
- 6:05:13будут говорить, что их рабочий день
- 6:05:14закончен. Но вдруг мы с вами захотим
- 6:05:17сделать такую логику, что как только
- 6:05:20рабочий день был закончен, как только мы
- 6:05:22отменили контекст шахтёров, они сразу же
- 6:05:25должны завершиться. Ну, к примеру, к нам
- 6:05:27пришла какая-нибудь проверяющая комиссия
- 6:05:29и сказала: "А почему это вы ваших
- 6:05:30рабочих заставляете дорабатывать
- 6:05:32последнюю итерацию, если их рабочий день
- 6:05:35закончен? Не было бы справедливым их
- 6:05:37сразу отпускать домой, даже если они не
- 6:05:39успели доработать последнюю свою
- 6:05:40итерацию? Ну, если такой разговор пошёл,
- 6:05:43то мы можем это сделать. Нам позволяет,
- 6:05:45конечно же, это реализовать. Как бы
- 6:05:47выглядел код, если бы нам нужно было
- 6:05:49наших шахтёров отпускать домой, как
- 6:05:51только был закончен рабочий день, а не
- 6:05:54как сейчас. У нас рабочий день
- 6:05:55заканчивается, и они только дорабатывают
- 6:05:57итерацию свою последнюю, только после
- 6:05:59этого идут домой. Ну давайте смотреть.
- 6:06:01Во-первых, почему сейчас происходит так,
- 6:06:03как происходит? Потому что, допустим, мы
- 6:06:05отработали какую-то предыдущую итерацию,
- 6:06:07добыли уголь, идём на следующую итерацию
- 6:06:09нашего бесконечного цикла и смотрим,
- 6:06:10контекст отменён, нет. Идём добывать
- 6:06:13уголь. И в этот момент, пока мы вот
- 6:06:15здесь вот на таймслипе заснули на
- 6:06:17секунду, где-то в параллельной рутине
- 6:06:19этот контекст мог быть отменён, но мы
- 6:06:21здесь никак на это внимание не обращаем
- 6:06:24никакого, и мы просто будем дорабатывать
- 6:06:26честно нашу работу и только после этого
- 6:06:29идти на следующую итерацию и узнавать,
- 6:06:30что, оказывается, контекст был отменён.
- 6:06:32Можно написать по-другому, как это будет
- 6:06:34выглядеть. Мы изначально на каждой
- 6:06:35итерации нашего бесконечного цикла будем
- 6:06:37говорить: "Я шахтёр умер такой-то, начал
- 6:06:39добывать уголь". После этого, как
- 6:06:40говорится, смотрите за руками. Я пишу
- 6:06:42select и в уже привычном нам кейсе буду
- 6:06:45дожидаться, пока контекст отменится. Ну,
- 6:06:48пока вроде бы всё то же самое, что у нас
- 6:06:49было раньше. Но теперь я во втором кейсе
- 6:06:52буду писать следующую магию. Пакет, в
- 6:06:55нём функция автор. В неё передаю одну
- 6:06:57секунду.
- 6:06:58И тут я буду писать, что я шахтёр номер
- 6:07:01такой-то, добыл уголь. Что здесь
- 6:07:03случилось? Я начал итерацию бесконечного
- 6:07:05цикла и сразу же вывел информацию о том,
- 6:07:07что я такой-то шахтёр, начал добывать
- 6:07:09уголь. Потом в селекте я жду в двух
- 6:07:11кейсах два канала. Первый канал, который
- 6:07:14отвечает за то, что контекст отменится.
- 6:07:16Второй канал, в который нам придёт
- 6:07:18значение после одной секунды. Такая
- 6:07:20конструкция та time after в кейсе, в
- 6:07:22селекте, она позволяет выполнять нам
- 6:07:24какой-то участок кода по истечению
- 6:07:26переданного времени. Как это в сумме всё
- 6:07:28работает? Когда наша программа попадёт
- 6:07:30на этот селект, отработает либо вот этот
- 6:07:33вот кейс через секунду, либо вот этот
- 6:07:35вот кейс, если во время ожидания этой
- 6:07:37секунды у нас пришло окончание рабочего
- 6:07:39дня. Тут мы решаем первую проблему, что
- 6:07:41если у нас во время добычи угля
- 6:07:43закончился рабочий день, то мы должны
- 6:07:44будем доработать итерацию нашу. Нет,
- 6:07:46теперь, если у нас во время ожидания
- 6:07:48секунды, то есть во время добычи нашего
- 6:07:49угля, наступило окончание рабочего дня,
- 6:07:52мы сразу же этот самый рабочий день
- 6:07:53завершим. О'кей. После этого у нас идёт
- 6:07:56запись в пункт передачи угля. Но на
- 6:07:58записи, как мы помним, мы тоже можем
- 6:07:59заблокироваться, если кто-то с другой
- 6:08:01стороны не будет вовремя вычитывать наши
- 6:08:04значения из канала. А значит, тут мы
- 6:08:05тоже можем заблокироваться. А значит,
- 6:08:07если во время блокировки на записи в
- 6:08:08канал у нас завершится рабочий день, то
- 6:08:10мы всё равно не пойдём домой сразу, а
- 6:08:12будем ждать, пока кто-то заберёт добытый
- 6:08:13уголь и только после этого сможем пойти
- 6:08:15домой. Нам это не нравится, нас это не
- 6:08:17устраивает, поэтому пишем второй селект.
- 6:08:19В первом кейсе, как обычно, мы ждём
- 6:08:20окончания рабочего дня. Тут, в принципе,
- 6:08:22можно вот это скопировать всё, потом
- 6:08:23вынести в какую-то отдельную функцию. И
- 6:08:25во втором кейсе мы будем с вами
- 6:08:27записывать в Transf point наш добытый
- 6:08:29уголь. Ну и выводить информацию о том,
- 6:08:31что мы этот самый уголь с вами передали.
- 6:08:34Что тут случилось? Теперь во втором
- 6:08:35селекте мы опять описываем два кейса.
- 6:08:37Первый кейс у нас разблокируется, если
- 6:08:39закончится рабочий день. Второй кейс у
- 6:08:41нас разблокируется, если мы сможем
- 6:08:42записать что-то в канал. Тем самым мы с
- 6:08:44вами в двух местах, в которых мы могли
- 6:08:46раньше блокироваться, а это секунда,
- 6:08:48которую мы засыпаем и запись в канал, мы
- 6:08:50теперь на этих местах отдельно
- 6:08:52отслеживаем завершение рабочего дня.
- 6:08:54Теперь мы отслеживаем завершение
- 6:08:55рабочего дня, как во время добычи угля,
- 6:08:57так и во время передачи. К чему это
- 6:08:59приведёт? Давайте посмотрим. Запускаю
- 6:09:02нашу программу. Работают, работают
- 6:09:04шахтёры почталёны. И как только мы
- 6:09:06завершили рабочий день шахтёров, они
- 6:09:08моментально все завершили свой,
- 6:09:10собственно, рабочий день. Они не стали
- 6:09:13дорабатывать свою последнюю добычу. У
- 6:09:15них как только звонок прозвенел, сразу
- 6:09:17пошли домой. И даже если у нас тут будет
- 6:09:19с вами 10 шахтёров, результат будет тот
- 6:09:21же самый.
- 6:09:24Опа, рабочий день шахтёров окончен, и
- 6:09:26они все от первого до последнего
- 6:09:28моментально пошли домой. Какой же из
- 6:09:31этих двух стилей написания кода мы в
- 6:09:32итоге с вами должны выбрать? А зависит
- 6:09:34всё от поставленной задачи. На то мы
- 6:09:36здесь с вами и нужны как программисты,
- 6:09:38чтобы выбирать, какие именно конструкции
- 6:09:40в какой ситуации использовать.
- 6:09:41Конструкция, которую мы с вами писали
- 6:09:43изначально, она подходит нам под
- 6:09:45поставленную задачу. Если эта самая
- 6:09:47поставленная задача звучит так: "Я хочу,
- 6:09:49чтобы наши шахтёры работали, пока я не
- 6:09:52скажу им, что рабочий день окончен. Но
- 6:09:54когда рабочий день будет окончен, если в
- 6:09:56этот момент какой-то шахтёр ещё добывает
- 6:09:59уголь, то он должен до конца доработать
- 6:10:01эту свою добычу и принести уголь в пункт
- 6:10:04передачи и, собственно, отдать. До тех
- 6:10:06пор, даже если рабочий день закончен,
- 6:10:09шахтёр не может идти домой. Он должен
- 6:10:11доработать свою последнюю итерацию. В
- 6:10:12таком случае нам вот эта конструкция
- 6:10:14подходит наилучшим образом. Но если
- 6:10:16изначальная поставленная задача звучит
- 6:10:18как, несмотря на то, добывает ли в этот
- 6:10:20момент шахтёр уголь или ждёт, пока
- 6:10:22кто-нибудь его уголь возьмёт с другой
- 6:10:24стороны на пункте передачи, неважно,
- 6:10:26если в этот момент завершается рабочий
- 6:10:28день, шахтёр сразу же должен идти домой
- 6:10:31без задержек. Пускай он прямо на месте
- 6:10:33бросает свой уголь, бросает свою кирку,
- 6:10:35идёт домой. Если задача звучит следующим
- 6:10:37образом, тогда нам подходит вот эта
- 6:10:39конструкция. Но теперь мы, наверное,
- 6:10:40разобрали всё, что касается этой задачи,
- 6:10:42разобрали все её подводные камни.
- 6:10:45[музыка]
- 6:10:50Следующая тема, которую мы с вами
- 6:10:51разберём - это HTTP. Что хочу сказать?
- 6:10:55Отныне и впредь я буду считать, что с
- 6:10:57базовым синтаксисом языка Goldeng мы с
- 6:10:59вами разобрались. Конечно, я всё ещё
- 6:11:01буду объяснять тот код, что я пишу,
- 6:11:04особенно если это что-то новое, но уже
- 6:11:06максимально подробно на том, как
- 6:11:08работает цикл или как работает условное
- 6:11:10ветвление или как работает канал в
- 6:11:12рутине. Я уже прямо очень подробно на
- 6:11:15этом останавливаться не буду, потому что
- 6:11:16это будет отнимать много времени. А те,
- 6:11:18кто хотел разобраться, они уже
- 6:11:20разобрались, пересмотрели урок,
- 6:11:21перерешали домашку, написали пэтпроекты.
- 6:11:24Поэтому мы двигаемся дальше и будем
- 6:11:26фокусировать своё внимание именно на
- 6:11:28HTTP. Что это вообще такое, для чего это
- 6:11:30нужно и как используется. Всё это мы
- 6:11:32сейчас обсудим. Давайте представим, что
- 6:11:34мы с вами разрабатываем приложение
- 6:11:36такси. То есть это приложение, где
- 6:11:38пользователи могут находить таксистов, а
- 6:11:40таксисты могут находить пользователей.
- 6:11:42благодаря чему пользователи смогут
- 6:11:43доезжать, куда им нужно, а таксисты
- 6:11:45смогут зарабатывать деньги. Ну,
- 6:11:47получается, у нас в нашем приложении
- 6:11:48будут как таксисты, так и пассажиры,
- 6:11:50которые ищут себе, собственно, такси,
- 6:11:52чтобы куда-нибудь доехать. Представим,
- 6:11:53что у нас 10.000 таксистов и 10.000
- 6:11:56пассажиров пользуются нашим приложением.
- 6:11:59Вот таким вот образом я изобразил 10.000
- 6:12:01таксистов и 10.000 пассажиров. Каждый
- 6:12:03отдельный прямоугольничек - это
- 6:12:05отдельное устройство, отдельный смартфон
- 6:12:07в руках отдельного таксиста либо
- 6:12:09отдельного пассажира. То есть это
- 6:12:11смартфон какого-то первого пассажира,
- 6:12:12это смартфон какого-то второго
- 6:12:14пассажира, это смартфон какого-то
- 6:12:15третьего пассажира, это условные
- 6:12:17смартфоны остальных пассажиров и вот
- 6:12:18смартфон десятитысячного пассажира. То
- 6:12:20же самое со смартфонами таксистов. То
- 6:12:22есть каждый прямоугольничек - это
- 6:12:23какое-то отдельное устройство. Ну теперь
- 6:12:25представим, что у нас какой-то пассажир,
- 6:12:27например, первый захотел взять и сделать
- 6:12:29заказ. Он у себя в приложении вбил точку
- 6:12:31А, вбил точку Б и нажал "Хочу сделать
- 6:12:34заказ". Получается, о том, что пассажир
- 6:12:37у себя на телефоне сделал заказ, должны
- 6:12:39узнать таксисты для того, чтобы уже
- 6:12:41выбрать, они хотят принять этот заказ
- 6:12:42либо отклонить. Получается каким-то
- 6:12:44образом то, что у себя пассажир на
- 6:12:46телефоне нажимает какие-то кнопочки, об
- 6:12:48этом должны узнать телефоны, которые
- 6:12:51стоят у таксистов в машинах, чтобы
- 6:12:53запрос на поездку, который пассажир у
- 6:12:55себя вёл, отобразился у таксистов на
- 6:12:57телефоне. Да ещё и не у одного таксиста,
- 6:12:59а сразу у нескольких таксистов. Ведь мы,
- 6:13:01когда такси с вами заказываем, нам там
- 6:13:03на карте показывают, что вот мы, вот
- 6:13:05дорога, например, да, и что вот здесь
- 6:13:07вот есть машинки наших таксистов. Раз
- 6:13:09машинка тут вот два машинка. И нам
- 6:13:12показывают, что сначала мы делаем запрос
- 6:13:14на эту машинку, потом на эту, и они уже
- 6:13:16либо принимают наш заказ, либо
- 6:13:17отклоняют. Хорошо, но каким образом нам
- 6:13:19вообще реализовать то, что если пассажир
- 6:13:21у себя на телефоне что-то нажимает, то
- 6:13:24эта информация каким-то образом доходит
- 6:13:25до смартфона таксиста? То есть они же
- 6:13:27вообще могут находиться не то, что в
- 6:13:29разных районах города какого-то одного,
- 6:13:31они могут находиться в разных городах.
- 6:13:33Пассажир и таксист, которому пришёл
- 6:13:35запрос от пассажира. Как вообще с
- 6:13:36телефона пассажира какая-то информация
- 6:13:39может доходить до телефона таксиста? А
- 6:13:41информация эта ходит по сети, то есть
- 6:13:43происходит сетевой запрос. Что вообще
- 6:13:46такое сетевой запрос или если более обще
- 6:13:48говоря, что вообще такое сетевое
- 6:13:50взаимодействие? Сетевое взаимодействие -
- 6:13:52это когда два физически разных
- 6:13:54устройства, которые находятся далеко
- 6:13:56друг от друга, могут обмениваться между
- 6:13:58собой какой-то информацией, как это
- 6:14:00обычно происходит. Ну, предположим,
- 6:14:02пассажир, он заказывает такси у себя из
- 6:14:04дома, и у него дома есть Wi-Fi-роутер.
- 6:14:06Получается, пассажир, когда что-то
- 6:14:08жмякает на своём смартфоне и нажимает
- 6:14:09кнопку заказать такси, тогда его
- 6:14:12смартфон передаёт информацию роутеру по
- 6:14:15воздуху. роутер принимает эту информацию
- 6:14:17от смартфона и потом уже по кабелю через
- 6:14:20много слоёв абстракции, через много
- 6:14:22всякой аппаратуры передаёт эту
- 6:14:24информацию в сотовую вышку. Вот она наша
- 6:14:26сотовая вышка. Таксист, в свою очередь,
- 6:14:28который проезжает на своей машине мимо
- 6:14:30этой сотовой вышки, ловит от неё сигнал
- 6:14:33и информация о том, что пассажир нажал у
- 6:14:35себя на телефоне заказ, через вот эти
- 6:14:37вот все дебри попадает таксисту на
- 6:14:40телефон. И так же само в обратную
- 6:14:41сторону. Когда таксист нажмёт у себя
- 6:14:43кнопку "Принять заказ", то эта
- 6:14:45информация пойдёт на сотовую вышку. Ну,
- 6:14:47потому что таксист где-то в дороге, у
- 6:14:49него там в машине нетфая, у него есть 4G
- 6:14:51LTE. И вот он по этому 4G LTE, а это
- 6:14:54именно сотовые вышки за это отвечают,
- 6:14:56передаёт информацию на эту сотовую вышку
- 6:14:58о том, что он принял заказ. Информация
- 6:15:00со смартфона таксиста передаётся на
- 6:15:01сотовую вышку. Сотовая вышка по проводам
- 6:15:04уже там через большое количество
- 6:15:05аппаратуры передаёт эту информацию в
- 6:15:07роутер, который находится у пассажира
- 6:15:08дома. Этот роутер уже благодаря своей
- 6:15:10антеннке передаёт эту информацию
- 6:15:12пассажиру на телефон. И у пассажира на
- 6:15:14телефоне появляется информация о том,
- 6:15:16что его заказ принят. Ожидайте, такси
- 6:15:17там приедет через 10 минут. Вот это всё
- 6:15:19вместе и есть сетевое взаимодействие,
- 6:15:22когда два физически разных устройства,
- 6:15:25которые находятся далеко друг от друга,
- 6:15:27ну они могут находиться, конечно, и
- 6:15:28близко, но для общего случая возьмём,
- 6:15:30что эти смартфоны, эти устройства
- 6:15:32находятся далеко друг от друга. Они
- 6:15:34могут без какого-то физического контакта
- 6:15:37обмениваться информацией. Это и есть
- 6:15:39сетевое взаимодействие. Хорошо
- 6:15:41получается, когда один из пассажиров
- 6:15:42хочет сделать заказ, он должен сделать
- 6:15:44сетевой запрос на все устройства
- 6:15:47таксистов для того, чтобы оповестить
- 6:15:49этих самых таксистов, что этот пассажир,
- 6:15:51собственно, хочет сделать заказ. О'кей,
- 6:15:53нажимаю кнопку заказать. И получается, я
- 6:15:55должен оповестить всех таксистов о том,
- 6:15:57что я сейчас хочу сделать заказ.
- 6:15:59Какие-то таксисты уже на своей стороне,
- 6:16:01приняв запрос, они отфильтруют этот наш
- 6:16:03заказ, например, по геопозиции. Ну,
- 6:16:05увидят, что слишком далеко ехать, типа
- 6:16:06нет, я это даже не буду рассматривать.
- 6:16:08Какие-то таксисты просто отклонят наш
- 6:16:10заказ, а какой-то таксист один найдётся,
- 6:16:12и он этот заказ примет. А что это
- 6:16:14значит? Это значит, что, во-первых,
- 6:16:16устройство таксиста должно обратно на
- 6:16:18устройство пассажира дать ответ, что
- 6:16:20заказ принят, чтобы пассажир у себя на
- 6:16:22телефоне увидел, что заказ принят и что
- 6:16:24таксист скоро приедет. Это во-первых. А,
- 6:16:26во-вторых, таксист, который принял
- 6:16:27заказ, его приложение, его смартфон
- 6:16:30должны дать сетевые запросы на всех
- 6:16:32остальных таксистов для того, чтобы им
- 6:16:34сообщить, что этот заказ уже принят и вы
- 6:16:37можете не пытаться его принять. Ну, для
- 6:16:38того, чтобы заказ от одного и того же
- 6:16:40пассажира, не приняли два разных
- 6:16:41таксиста. Как только какой-то таксист
- 6:16:43принимает этот заказ, он должен
- 6:16:44оповестить всех остальных, что он этот
- 6:16:46заказ принял. Ну и, в принципе, так
- 6:16:47дальше будет всё продолжаться. Какой-то
- 6:16:49пассажир хочет сделать заказ. Он должен
- 6:16:51на все смартфоны таксистов отослать
- 6:16:53информацию о том, что он хочет сделать
- 6:16:55заказ. После этого все таксисты получат
- 6:16:58этот запрос. Кто-то его отфильтрует по
- 6:16:59геопозиции, кто-то ещё отфильтрует
- 6:17:01как-то, кто-то отклонит заказ. И потом
- 6:17:03один найдётся тот, кто этот заказ
- 6:17:04примет. Он должен сразу же обратно этому
- 6:17:07пассажиру дать ответ, что заказ принят.
- 6:17:09И должен всем остальным таксистам
- 6:17:10сообщить о том, что этот заказ принят
- 6:17:13для того, чтобы они не приняли этот
- 6:17:14заказ тоже. Ну, потому что от одного
- 6:17:16пассажира можно принять заказ только
- 6:17:17один раз. К вам же не может два таксиста
- 6:17:19приехать на один заказ. Как бы будет
- 6:17:20странно, что один из них будет делать.
- 6:17:22Задом ехать там по пятам. Ну нет, такого
- 6:17:24не бывает. Вот. И мы можем благодаря
- 6:17:26несложной математике посмотреть, что по
- 6:17:28сути каждое действие, будь то заказ
- 6:17:31такси или будь то там отмена заказа или
- 6:17:34подтверждение заказа, оно потенциально
- 6:17:36может генерировать нам до 10.000,
- 6:17:39умноженно на 10.000 операций, а это 100
- 6:17:43млн операций. Получается, казалось бы,
- 6:17:47просто один заказ такси может
- 6:17:49сгенерировать 100 млн сетевых запросов.
- 6:17:53А это даже человеку, который особо не
- 6:17:55шарит в программировании и в сетевом
- 6:17:57взаимодействии, понятно, что это слишком
- 6:17:59много, 100 млн сетевых запросов. У нас
- 6:18:02даже элементов в слайсе столько не было.
- 6:18:04У нас столько переменных с вами не было.
- 6:18:06У нас значения в переменных такие не
- 6:18:08лежали. А тут 100 млн сетевых запросов.
- 6:18:11А я напоминаю, что каждый сетевой запрос
- 6:18:13- это огромный путь. Это путь от
- 6:18:14смартфона до Wi-Fi-роутера, потом по
- 6:18:17кабелям до сотовой вышки, потом по
- 6:18:18воздуху до смартфона таксиста и, скорее
- 6:18:21всего, ещё и обратно. Вы представляете,
- 6:18:22какой-то большой путь, а тут нам на
- 6:18:24каждый заказ такси требуется 100 млн
- 6:18:27таких операций. 100 млн раз бегать
- 6:18:29туда-обратно. Ну, мне кажется, это даже
- 6:18:32на слух звучит как-то довольно дико. И
- 6:18:33тут сразу же встаёт вопрос: а что
- 6:18:35делать-то? Ну да, 100 млн. Ну а
- 6:18:37по-другому-то никак. Мы же должны как-то
- 6:18:39с каждого смартфона пассажира сообщить
- 6:18:41каждому смартфону таксиста: "Делаю я
- 6:18:43заказ, не делаю я заказ, где я
- 6:18:45нахожусь". Таксист должен каждому
- 6:18:47пассажиру дать понять, принимает он его
- 6:18:49заказ или отклоняет его заказ. А это у
- 6:18:51нас всего лишь 10.000 таксистов и 10.000
- 6:18:53пассажиров. А теперь представим
- 6:18:55какой-нибудь Яндекста такси, какие там
- 6:18:57масштабы, и поймём, что там далеко не
- 6:18:5910.000 пассажиров, там может быть
- 6:19:00миллион пассажиров и там может быть
- 6:19:02десятки тысяч таксистов, сотни тысяч
- 6:19:05таксистов. И сразу можно прикинуть,
- 6:19:06насколько вот эта цифра наша
- 6:19:07увеличивается и насколько вообще
- 6:19:09безобразно становится взаимодействие
- 6:19:10сетевое между устройствами, на которых
- 6:19:12установлено наше приложение. Это
- 6:19:14во-первых. И во-вторых, самая большая
- 6:19:16проблема в том, что это очень сложно
- 6:19:19разрабатывать. То есть очень сложно на
- 6:19:21каждом отдельном смартфоне таксиста
- 6:19:23отследить, какой пассажир делает заказ.
- 6:19:25Очень сложно все эти запросы принять,
- 6:19:27обработать. Очень сложно, когда один
- 6:19:29таксист и второй делают подтверждение
- 6:19:31заказа одновременно. Что в этот момент
- 6:19:34делать? Как они между собой договорятся,
- 6:19:35кто первый? Это всё вызывает большое
- 6:19:38количество вопросов. Это всё вызывает
- 6:19:39очень большую сложность во время
- 6:19:41разработки. Поэтому умные дяди и тёти
- 6:19:43придумали, как лучше организовывать
- 6:19:46сетевое взаимодействие при условии
- 6:19:48большого количества пользователей. А что
- 6:19:50таксисты, что пассажиры - это, по сути,
- 6:19:52пользователи в нашей системе. А
- 6:19:54придумали умные дяди и тёти следующую
- 6:19:56схему. Теперь у нас пассажиры и таксисты
- 6:19:59не общаются друг с другом напрямую.
- 6:20:01Теперь они общаются через один общий
- 6:20:03удалённый сервер. Как теперь будет
- 6:20:05выглядеть взаимодействие между
- 6:20:07устройствами пассажиров и устройствами
- 6:20:09таксистов? Ну, смотрим. Пассажир хочет
- 6:20:11сделать заказ. Он нажимает на своём
- 6:20:13смартфоне кнопочку "Сделать заказ". И
- 6:20:15этот сетевой запрос, информация о том,
- 6:20:17что этот пассажир хочет сделать заказ,
- 6:20:19она идёт в этот удалённый сервер. Этот
- 6:20:22удалённый сервер эту информацию
- 6:20:23принимает и переваривает. Сервер знает
- 6:20:26положение каждого таксиста. Он знает,
- 6:20:28какой из этих таксистов с точки зрения
- 6:20:30геолокации подойдёт данному пассажиру. И
- 6:20:33он уже этим таксистам, только тем,
- 6:20:36которые нужны, сделает предложение
- 6:20:38принять заказ. например, вот этому и вот
- 6:20:41десятитысячному таксисту тоже сделает
- 6:20:43предложение принять заказ. И мы уже
- 6:20:44можем начинать видеть огромные
- 6:20:46преимущества при такой организации
- 6:20:48сетевого взаимодействия. Во-первых,
- 6:20:50теперь все остальные таксисты и все
- 6:20:53остальные пассажиры избавлены от
- 6:20:55необходимости знать о том, где же там
- 6:20:58какой пассажир какой заказ делает. Всю
- 6:21:00эту ответственность на себя берёт этот
- 6:21:02удалённый сервер. Проще говоря, этот
- 6:21:04удалённый сервер и называется Backend.
- 6:21:07То есть эээнд - это некоторая
- 6:21:08подкапотная реализация нашего с вами
- 6:21:10сервиса такси. Этот самый ээкэнд, ну,
- 6:21:13дословно переводится как задняя часть,
- 6:21:15задний конец, но по факту это именно
- 6:21:17логика, которая вертится на удалённом
- 6:21:19сервере и которая обрабатывает все
- 6:21:21массивы данных. К этой логике, то есть к
- 6:21:23этому бэкэнду, к этому приложению, к
- 6:21:25этому удалённому серверу подключаются
- 6:21:27уже все остальные пользовательские
- 6:21:29устройства. Напоминаю, для нашего
- 6:21:31приложения, что таксисты, что пассажиры
- 6:21:33- это пользователи. Просто одни
- 6:21:35пользователи в виде таксистов, они
- 6:21:37принимают заказы и развозят людей.
- 6:21:39Вторые пользователи нашего приложения
- 6:21:41находят этих таксистов и перевозятся из
- 6:21:43точки А в точку Б. Хорошо. Представим,
- 6:21:45один из водителей подтвердил заказ. И
- 6:21:47всё, что теперь нужно сделать - это
- 6:21:49передать информацию на ээкэнд о том, что
- 6:21:51ты заказ подтвердил. Кэнд уже сам
- 6:21:53нужному пользователю даст ответ, что
- 6:21:55заказ подтверждён, а второму таксисту,
- 6:21:57который был предложен заказ, но он ещё
- 6:21:59не успел никак отреагировать, эээнд
- 6:22:01отошлёт информацию о том, что заказ уже
- 6:22:02принят другим таксистом. Ты можешь
- 6:22:04выбирать другие заказы. Ну, рассмотрим
- 6:22:06ещё какую-нибудь ситуацию. Ещё один
- 6:22:08человек хочет заказать такси. Он делает
- 6:22:10заказ. Этот запрос идёт в бэкэнд. энд
- 6:22:13уже находит таксистов, которые подходят
- 6:22:15под этого пассажира по геолокации, по
- 6:22:17премиальности, там, бюджетное такси или
- 6:22:19какое-нибудь бизнес-такси. Находит
- 6:22:21нужных таксистов и даёт предложение
- 6:22:24взять этот заказ только нужным
- 6:22:26таксистам. А теперь представим, что
- 6:22:28параллельно ещё один пассажир такой же
- 6:22:29заказ сделал из того же места. И этот
- 6:22:31заказ он пошёл тем же самым таксистом на
- 6:22:34предложение. И тут один из таксистов
- 6:22:35принял заказ вот этого пассажира.
- 6:22:38Соответственно, для вот этого пассажира
- 6:22:39нужно дать понять, что этот таксист уже
- 6:22:42за ним не приедет. Получается, этот
- 6:22:44таксист о том, что он принял заказ, даёт
- 6:22:46ответ бэкэнду. Бэкэнд вот этому
- 6:22:47пассажиру даёт ответ, что его заказ
- 6:22:49принят. А вот этому пассажиру даёт
- 6:22:51ответ, что его заказ вот этот таксист не
- 6:22:53примет, но, возможно, примет другой
- 6:22:55таксист. Потом этот таксист принимает
- 6:22:56заказ, отдаёт информацию об этом в
- 6:22:58эээкэнд, и эээнд уже отвечает
- 6:23:00пользователю, который сделал заказ о
- 6:23:02том, что его заказ принят, и передаст
- 6:23:03всю информацию о вот этом вот таксисте.
- 6:23:05А теперь давайте математически
- 6:23:07посчитаем, насколько выгодней вот такая
- 6:23:09организация сетевого взаимодействия. Не
- 6:23:11когда у нас каждое устройство с каждым
- 6:23:13должно общаться, они все между собой
- 6:23:15должны общаться, а когда все устройства
- 6:23:17общаются через эээнд. Теперь у нас
- 6:23:19получается, что в самом худшем случае
- 6:23:21все пассажиры, все 10.000 пассажиров
- 6:23:24одновременно начинают делать заказ, и
- 6:23:26этот заказ подходит всем 10.000
- 6:23:28таксистов. Получается, каждый запрос
- 6:23:31нашего пассажира, он может генерировать,
- 6:23:33во-первых, запрос на 10.000 таксистских
- 6:23:36приложений, а потом ещё 10.000 ответов
- 6:23:38от этих таксистских приложений, кто
- 6:23:40принял, кто отклонил заказ. А это
- 6:23:42получается 10.000 плюс 10.000, а это
- 6:23:4620.000. Что называется, сравните цифры.
- 6:23:49Если до этого, когда у нас была система,
- 6:23:52где каждое устройство напрямую без
- 6:23:54всякого бэкэнда общалось с каждым, у нас
- 6:23:56получалась цифра в 100 млн. Но стоило
- 6:24:00нам просто добавить ээкэнд, через
- 6:24:01который общаются все эти клиентские
- 6:24:03устройства. Так у нас количество сетевых
- 6:24:05взаимодействий между разными
- 6:24:06устройствами для худшего сценария
- 6:24:09сократилось со 100 млн до 20.000. Если
- 6:24:13вы не очень можете сравнить в голове
- 6:24:14порядок этих цифр, представьте, что вы
- 6:24:17на своём каршеринге каком-нибудь
- 6:24:19врезались в дорогущий Bentle. И в первом
- 6:24:22случае вышел хозяин и сказал: "Вы мне
- 6:24:24должны 100 млн руб." Он через суд
- 6:24:27обратился и действительно суд
- 6:24:28постановил, что вы должны выплатить 100
- 6:24:30млн руб. Либо такая же ситуация, но
- 6:24:3320.000 руб. Ну, чувствуете, да, разницу?
- 6:24:35Тут это просто неподъёмные цифры,
- 6:24:37которые люди, ну, за всю жизнь не
- 6:24:39зарабатывают, а тут, ну, 20.000 20.000.
- 6:24:41Неприятно, конечно, но пережить можно. И
- 6:24:44это ответ на вопрос, почему вообще в
- 6:24:46современном мире в большинстве случаев,
- 6:24:49в подавляющем большинстве случаев,
- 6:24:51сетевое взаимодействие выглядит именно
- 6:24:52следующим образом. Когда у нас есть
- 6:24:54какие-то клиентские устройства и есть
- 6:24:56ээнд, который обрабатывает запросы от
- 6:24:58этих клиентских устройств. Просто
- 6:25:00потому, что если бы устройства общались
- 6:25:02друг между другом напрямую, это
- 6:25:03генерировало бы такое количество сетевых
- 6:25:05запросов, что просто ни одно приложение
- 6:25:07бы не справилось, ни один смартфон бы
- 6:25:09этого не вывез. Помнить обо всех других
- 6:25:11таксистах, помнить обо всех пассажирах,
- 6:25:13делать какие-то выводы, да, это даже с
- 6:25:15точки зрения разработчиков был бы просто
- 6:25:17производственный кошмар. А так у нас
- 6:25:19есть эээнд, так называемая единая точка
- 6:25:21истины, в которой мы можем сделать
- 6:25:23запросик и получить ответик. А эээнд уже
- 6:25:26сам решает, кому передать заказ, какая
- 6:25:28стоимость будет у заказа, каким
- 6:25:30таксистам предложить этот заказ,
- 6:25:31отфильтрует их по классам комфорта,
- 6:25:33отфильтрует их по рейтингу. Этим всем
- 6:25:35занимается эээнд. Во-первых, это просто
- 6:25:37логически гораздо проще разработать.
- 6:25:39Во-вторых, это гораздо меньше сетевого
- 6:25:41взаимодействия, а как следствие, гораздо
- 6:25:43выше скорость, стабильность и надёжность
- 6:25:46нашей разрабатываемой системы. Хотелось
- 6:25:48бы дополнить, что тут под клиентским
- 6:25:50устройством подразумевается не
- 6:25:51обязательно смартфон. То есть вообще вот
- 6:25:53эта вот схема взаимодействия называется
- 6:25:55клиентсерверная архитектура. Когда у нас
- 6:25:57есть какие-то клиенты, неважно, будь то
- 6:26:00пассажир или таксист, и есть какой-то
- 6:26:02сервер, то есть наш ээкэнд, и эти
- 6:26:04клиентские устройства, они ходят в
- 6:26:06сервер, а сервер даёт им какие-то
- 6:26:08ответы. Это клиент серверная
- 6:26:10архитектура, и она наиболее популярна в
- 6:26:12нашем современном мире. Но клиентом
- 6:26:15может быть не только приложение на
- 6:26:16смартфоне. Клиентом может быть,
- 6:26:18например, веб-сайт. Ну, представим, что
- 6:26:20у нас не приложение такси, а
- 6:26:21какая-нибудь социальная сеть. Ну,
- 6:26:22например, Telegram. Мы Телеграмом можем
- 6:26:24пользоваться с мобильного устройства.
- 6:26:27Мы Телеграмом можем пользоваться из
- 6:26:29веб-браузера. Мы Телеграмом можем
- 6:26:31пользоваться с десктопного приложения на
- 6:26:33нашем ноутбуке. И это всё будут разные
- 6:26:36клиенты. мобильный клиент, то есть
- 6:26:38мобильное приложение, веб-клиент, то
- 6:26:41есть веб-браузер, или же десктопный
- 6:26:43клиент, то есть десктопное приложение на
- 6:26:45нашем с вами компьютере. И эти все
- 6:26:47клиенты будут общаться с одним и тем же
- 6:26:50бэкэндом. Это и позволяет нам отправить
- 6:26:52сообщение с телефона, домой прийти,
- 6:26:54открыть компьютер и увидеть эти же самые
- 6:26:57наши отправленные сообщения, только уже
- 6:26:58на компьютере. Потому что всё
- 6:27:00взаимодействие сетевое между разными
- 6:27:02устройствами, оно происходит через ээд.
- 6:27:05эээкэнд не только упрощает нам вообще
- 6:27:07жизнь в виде количества сетевых запросов
- 6:27:08и сложности системы, но ещё и позволяет
- 6:27:11держать всю информацию в одном месте, в
- 6:27:13том числе переписки, изображения,
- 6:27:15видеозаписи, контакты и какие-нибудь
- 6:27:17сохранённые сообщения. Всё это хранится
- 6:27:19в одном месте, и мы потом с разных
- 6:27:21клиентских устройств, неважно, что
- 6:27:23именно это за вид клиентского
- 6:27:24устройства, можем в этот бэкэнд делать
- 6:27:26сетевые запросы, а нам бэкэнд будет
- 6:27:28присылать сетевые ответы, которые
- 6:27:29содержат уже все наши переписки,
- 6:27:31фотографии и так далее. Именно поэтому
- 6:27:33наиболее оптимальная и наиболее часто
- 6:27:35используемая в современном мире
- 6:27:37архитектура построения сетевых
- 6:27:38приложений - это клиент серверная
- 6:27:40архитектура. И теперь у нас счастливый
- 6:27:42момент. Мы, смотря на эту схему,
- 6:27:45наконец-таки, как Goldeng разработчики
- 6:27:47сможем понять, что именно из этого всего
- 6:27:50мы будем разрабатывать. А на Голенге
- 6:27:52разрабатывается именно бэкэнд, именно
- 6:27:55вот эта часть нашего сетевого
- 6:27:56приложения, которое принимает клиентские
- 6:27:58запросы, который сохраняет изображение в
- 6:28:00базу данных. пользовательские переписки,
- 6:28:03которая всю эту информацию как-то
- 6:28:04обрабатывает и отдаёт обратно
- 6:28:05пользователям. И что самое крутое,
- 6:28:07навыки эээнд-разработки помогут вам
- 6:28:10разрабатывать абсолютно разные
- 6:28:12приложения. Вы можете разрабатывать как
- 6:28:14эээнд для какого-нибудь Яндекста такси,
- 6:28:16так и ээкэнд для ВК музыки, так и
- 6:28:18эээкэнд для Ютуба или для Телеграма,
- 6:28:20вообще для чего угодно. Ведь суть будет
- 6:28:23оставаться той же. У нас есть какие-то
- 6:28:25клиентские устройства, они от нас что-то
- 6:28:27хотят, они делают нам какие-то сетевые
- 6:28:29запросы. Мы эти запросы как-то
- 6:28:31перевариваем, понимаем, что от нас хочет
- 6:28:33пользователь, лазим в своих базах данных
- 6:28:35и отдаём пользователю ответ. И это всё
- 6:28:37прекрасно. Но к чему вообще относится
- 6:28:39тогда HTTP? А если помните, когда мы с
- 6:28:41вами только что разбирали вообще, что из
- 6:28:43себя представляет сетевой запрос, то
- 6:28:45можно было сделать абсолютно
- 6:28:46обоснованный вывод о том, что сетевой
- 6:28:48запрос - это очень сложно, потому что
- 6:28:50нам нужно как-то достучаться до роутера,
- 6:28:53потом как-то передать по нужному кабелю
- 6:28:55информацию в нужную сотовую вышку,
- 6:28:57именно там, где у нас таксист, а не в
- 6:28:59какую-нибудь другую. Потом эта сотовая
- 6:29:00вышка должна как-то по воздуху передать
- 6:29:02информацию в приложение таксиста, а
- 6:29:04потом ещё весь этот путь обратно, когда
- 6:29:06таксист, например, подтвердил заказ. И
- 6:29:09что мы должны как-то изучать строение
- 6:29:10роутера, мы должны изучать строение
- 6:29:12сотовых вышек, чтобы быть
- 6:29:13ээнд-разработчиком. Мы должны изучать
- 6:29:15строение сетевых модулей в каждом
- 6:29:17смартфоне, чтобы как-то уметь делать эти
- 6:29:19сетевые запросы, отправлять их
- 6:29:20принимать. Нет, эволюция IT-отрасли нас
- 6:29:23с вами избавила от этой необходимости.
- 6:29:26Нам теперь не нужно задумываться о том,
- 6:29:29как устроен роутер. Нам не нужно
- 6:29:30задумываться о том, как устроена сотовая
- 6:29:32вышка. Для нас предоставляется
- 6:29:34инструмент, который называется HTTP.
- 6:29:37HTTP - это сетевой протокол, благодаря
- 6:29:39которому мы можем делать сетевые запросы
- 6:29:41между двумя разными устройствами,
- 6:29:43совершенно не задумываясь о том, какой
- 6:29:45путь пройдёт наш сетевой запрос через
- 6:29:47все эти провода и железяки. Получается,
- 6:29:50мы со своего клиентского устройства
- 6:29:52сможем сделать сетевой запрос на наш
- 6:29:54ээнд, вообще не задумываясь о том, что
- 6:29:56там между нами находится, сколько
- 6:29:58сотовых вышек, сколько Wi-Fi-роутеров и
- 6:30:00сколько проводов. Нас это вообще не
- 6:30:02интересует, потому что HTTP - это
- 6:30:04протокол прикладного уровня, так
- 6:30:06называемый. Это значит, что мы можем
- 6:30:08просто брать его и пользоваться.
- 6:30:10Повторюсь, совершенно не задумываясь о
- 6:30:12том, как наш сигнал будет идти вообще в
- 6:30:15этом мире. Поэтому нас всё это на данный
- 6:30:17момент вообще не интересует. Так вот,
- 6:30:20давайте рассмотрим, из чего вообще
- 6:30:21состоит HTTP запрос и какая информация
- 6:30:24нам нужна, чтобы этот самый запрос
- 6:30:25совершить. Ну вот представим, мы хотим
- 6:30:27заказать такси. И согласно вот этой
- 6:30:29нашей продвинутой схеме, всё, что нам
- 6:30:31требуется со стороны пассажирского
- 6:30:32устройства - это просто сделать запрос
- 6:30:34на ээкэнд. Дальше ээкэнд уже сам
- 6:30:36разберётся, что кому куда делать
- 6:30:38запросы. С нашей точки зрения, это всё
- 6:30:40не имеет значения. Мы просто сделали
- 6:30:41запрос и будем ждать ответ. Хорошо, но
- 6:30:44ведь у нас может быть много бэкэндов. На
- 6:30:46каком-то бэкэнде развёрнут YouTube, на
- 6:30:48каком-то бэкэнде развёрнуто Яндекста
- 6:30:49такси, на каком-то бэкэнде развёрнут
- 6:30:51Telegram, на каком-то бэкэнде развёрнута
- 6:30:53ВК-музыка. И мы, допустим, хотим с вами
- 6:30:55заказать такси. Нам же нужно понимать, в
- 6:30:58какой именно эээнд мы будем делать
- 6:31:00запрос. Если мы захотим заказать такси,
- 6:31:02а сделаем запрос на сервер с Ютубом, то
- 6:31:04в лучшем случае у нас просто откроется
- 6:31:06какой-нибудь видеоролик на Ютубе. В
- 6:31:07худшем случае мы с вами не сможем
- 6:31:09заказать такси и опоздаем в то место,
- 6:31:11куда мы хотели доехать вовремя. Или же
- 6:31:13если мы вместо заказа такси сделаем
- 6:31:14запрос в ВК-музыку. Ну, у нас вместо
- 6:31:16приехавшего во двор таксиста включится
- 6:31:17музыка. Ну, как бы хорошо, но, наверное,
- 6:31:19не то, что мы хотели. Получается, каждый
- 6:31:21раз, когда мы делаем какой-то сетевой
- 6:31:23запрос, первый вопрос, который
- 6:31:25становится перед нами, как перед
- 6:31:27клиентом в этом запросе - это куда
- 6:31:30делать запрос. И вот на этот вопрос,
- 6:31:32куда отвечает IP-адрес сервера? У
- 6:31:36каждого сервера есть свой уникальный
- 6:31:38IP-адрес. По сути, IP-адрес - это адрес
- 6:31:41какого-то конкретного сервера. У каждого
- 6:31:43сервера есть свой уникальный IP-адрес.
- 6:31:46Это можно сравнить с адресом дома. То
- 6:31:48есть каждый сервер - это какой-то дом, а
- 6:31:50каждый IP-адрес - это адрес этого дома.
- 6:31:52И благодаря этому адресу, этому
- 6:31:54IP-адресу, мы можем регулировать, куда
- 6:31:57именно мы будем делать наш сетевой
- 6:31:59запрос. Если мы захотим сделать запрос
- 6:32:01на youtube.com, нам нужно взять IP-адрес
- 6:32:05Ютуба и сделать на него свой запрос.
- 6:32:08Если мы захотим сделать запрос в
- 6:32:09Яндекста такси, нам нужно взять IP-адрес
- 6:32:12этого самого сервера, на котором
- 6:32:14работает Яндекстаси, и по этому
- 6:32:16IP-адресу мы сможем сделать свой сетевой
- 6:32:18запрос. Но откуда мы с вами узнаем
- 6:32:20IP-адрес Ютуба? Мы же в браузере просто
- 6:32:23вводим youtube.com
- 6:32:27и всё, и нас сразу перенаправляет на
- 6:32:28YouTube. Как это происходит на самом
- 6:32:30деле? И каждый раз, когда вы в браузере
- 6:32:32вводите не IP-адрес, а какое-то
- 6:32:34название, например, vk.com,
- 6:32:38тогда ваше устройство делает изначально
- 6:32:41запрос в так называемый DNS. DNS - это
- 6:32:44domain name system. Это система, которая
- 6:32:47преобразует домен, то есть vk.com или
- 6:32:50youtube.com или там yandex.ru или
- 6:32:53telegram.org, что угодно, какой угодно
- 6:32:56домен преобразует в IP. То есть мы
- 6:32:58делаем запрос сначала в этот DNS, кидаем
- 6:33:01туда домен youtube.com. Этот DNS нам
- 6:33:03возвращает IP-адрес Ютуба, он его знает.
- 6:33:07И потом мы уже по этому IP-адресу можем
- 6:33:09спокойно делать запрос на сервера Ютуба.
- 6:33:11Это первый вариант. Либо второй вариант,
- 6:33:13мы сразу же можем делать запросы на
- 6:33:16какой-то айпишник. Но нам для этого
- 6:33:18нужно знать этот айпишник. Если мы
- 6:33:19айпишник не знаем, а мы знаем лишь
- 6:33:21домен, ну, опять же повторюсь, что такое
- 6:33:23домен? Это vk.com, это youtube.com, это
- 6:33:26yandex.ru и так далее, и так далее, и
- 6:33:27так далее. Если мы знаем просто домен,
- 6:33:29но не знаем IP, то мы можем делать HTTP
- 6:33:32запрос просто на этот самый домен. Но
- 6:33:34наше устройство под капотом сходит в
- 6:33:36этот ДНС, преобразует домен в конкретный
- 6:33:39IP-адрес и будет знать, на какой сервер
- 6:33:43нужно делать запрос, потому что будет
- 6:33:44знать его IP-адрес. А зная IP-адрес
- 6:33:46сервера, мы сможем сделать в него
- 6:33:48сетевой запрос. Хорошо, получается,
- 6:33:50первое, что нам с вами необходимо знать
- 6:33:52до того, как делать сетевой запрос - это
- 6:33:54домен или же IP-адрес сервера, на
- 6:33:56который мы с вами будем делать запрос.
- 6:33:58Если у нас сразу же есть IP-адрес, можем
- 6:34:01делать сразу по этому IP-адресу запрос.
- 6:34:03Если же у нас есть только домен, то наше
- 6:34:05устройство за нас, мы про это можем даже
- 6:34:07не знать, сходит в DNS-сервер и по
- 6:34:10домену получит нужный IP-адрес и потом
- 6:34:12уже по этому IP-адресу также сделает
- 6:34:14запрос. Смотрим дальше. Сразу скажу, для
- 6:34:16опытных ребят я сейчас буду очень сильно
- 6:34:19упрощать, просто для того, чтобы было
- 6:34:21понимание того, о чём идёт речь.
- 6:34:23Понятное дело, что в реальной жизни оно
- 6:34:24всё устроено немножко по-другому.
- 6:34:26Просьба понимать, что всё настолько
- 6:34:27упрощено для того, чтобы новичкам
- 6:34:29донести мысль, донести понимание. Ну,
- 6:34:32допустим, мы с вами хотим сделать
- 6:34:33сетевой запрос на сервер Сбера. Мы знаем
- 6:34:35айпишник этого сервера. А если и не
- 6:34:37знаем, мы сходим в DNS-сервер. Нам под
- 6:34:39домену сberbank.ru
- 6:34:41вернётся IP-адрес нужный, и мы уже с
- 6:34:43помощью этого IP-адреса сделаем запрос
- 6:34:45на нужный сервер, именно Сбербанка. Но
- 6:34:47тут в чём прикол? У Сбербанка на одном
- 6:34:50сервере может быть развёрнуто множество
- 6:34:52разных приложений. То есть далеко не
- 6:34:54обязательно для каждого отдельного
- 6:34:56приложения запускать отдельный сервер.
- 6:34:58Это то же самое, как нам. Если бы нужно
- 6:35:00было запустить браузер и редактор кода
- 6:35:02на одном компьютере, мы бы с вами
- 6:35:04покупали два разных компьютера. Ну нет,
- 6:35:06мы как бы несколько приложений, если
- 6:35:08позволяют ресурсы, можем запустить
- 6:35:10физически на одном устройстве. Тут то же
- 6:35:12самое. Мы на одном сервере, а сервер -
- 6:35:14это, по сути, самый обычный компьютер,
- 6:35:16просто на котором установлено
- 6:35:17программное обеспечение для обработки
- 6:35:19сетевых запросов и хранения каких-то
- 6:35:21данных пользовательских. Но так это
- 6:35:22самый обычный компьютер, скорее всего,
- 6:35:24вообще на Линуксе. Да, вы у себя дома
- 6:35:25можете такой поставить и в качестве
- 6:35:27десктопной операционной системы
- 6:35:29пользоваться, смотреть видео, фильмы,
- 6:35:31играть в игры. А у нас сервера на таких
- 6:35:32операционных системах работают. То есть
- 6:35:34сервер - это, по сути, самый обычный
- 6:35:36компьютер, просто может быть мощный для
- 6:35:37того, чтобы обрабатывать сразу множество
- 6:35:39пользователей. И разработчики Сбербанка,
- 6:35:41они тоже подумали: "А зачем нам для
- 6:35:43каждого нашего отдельного приложения
- 6:35:45покупать отдельный сервер? Это же не
- 6:35:47выгодно. Это нужно платить за железки.
- 6:35:49Это нужно следить за качеством кабелей,
- 6:35:51чистить этот сервер, чтобы он не
- 6:35:53запылился, не перегревался, следить за
- 6:35:55качеством его комплектующих, чтобы
- 6:35:56жёсткие диски из строя не выходили. Нет,
- 6:35:58зачем? Мне позволяют ресурсы, я могу
- 6:36:00сразу несколько приложений запустить на
- 6:36:02одном сервере. Получается, у нас у
- 6:36:04Сбербанка на одном сервере одновременно
- 6:36:06могут быть запущены сразу несколько
- 6:36:08приложений. Например, три приложения.
- 6:36:10Первое - это, собственно, сам банк,
- 6:36:11через который мы можем проводить
- 6:36:13денежные операции. Второе - это
- 6:36:14сберзвук, через который мы можем слушать
- 6:36:16музыку. И третьей - это Сбермаркет,
- 6:36:18через который мы можем заказывать
- 6:36:19продукты. И все эти три приложения, они
- 6:36:21могут быть запущены на одном сервере.
- 6:36:24О'кей, мы с вами выяснили IP-адрес и
- 6:36:26делаем сетевой запрос. Но я хочу просто
- 6:36:28заказать еду через бермаркет. Я не хочу
- 6:36:30делать денежные операции. Я не хочу
- 6:36:32слушать музыку, я хочу заказать
- 6:36:33продуктов. Как мне сделать такой запрос,
- 6:36:37чтобы он пошёл именно в то приложение,
- 6:36:39которое мне нужно? Чтобы этот запрос не
- 6:36:41пошёл в Сбербанк деньги списывать, чтобы
- 6:36:43этот запрос не пошёл в Сберзвук, музыку
- 6:36:45слушать? Нет, мне нужно, чтобы этот
- 6:36:46запрос пошёл именно в Сбермаркет. Для
- 6:36:48решения этой задачи давным-давно сетевые
- 6:36:51инженеры придумали такое понятие, как
- 6:36:53порты. Это на рисунке схематически
- 6:36:55изображено три порта сервера Сбера. На
- 6:36:58первом порту у нас висит приложение
- 6:37:01самого Сбербанка. На втором порту у нас
- 6:37:03висит приложение Сберзвук. На третьем
- 6:37:05порту у нас висит приложение Сбермаркет.
- 6:37:08А портов у нас может быть не один, не д,
- 6:37:10ни три, а 65.500.
- 6:37:13Если быть точным. Там что-то вроде
- 6:37:1465.535
- 6:37:16портов. Какие-то из этих портов заняты
- 6:37:19операционной системой. Какие-то из этих
- 6:37:21портов заняты какими-то другими
- 6:37:22приложениями. Но в большинстве своём все
- 6:37:25эти порты открыты. И мы, как
- 6:37:27разработчики разных ээнд- приложений,
- 6:37:29можем на эти порты завязываться. Что это
- 6:37:31нам даст? Когда пользователь сделает
- 6:37:34запрос на вот этот айпишник, ему
- 6:37:36дополнительно, чтобы попасть в какой-то
- 6:37:38конкретный порт, нужно указать этот
- 6:37:40порт. Порты у нас нумеруются с одного до
- 6:37:4265.000. Допустим, это первый порт, это
- 6:37:45второй порт, это третий порт. Мне для
- 6:37:47того, чтобы сделать запрос именно в
- 6:37:49Сбермаркет, мне нужно в моём HTTP
- 6:37:51запросе помимо айпишника сервера, на
- 6:37:54который я делаю запрос, ещё и указать
- 6:37:56конкретный порт, на который этот запрос
- 6:37:58придётся. Получится следующая картина. Я
- 6:38:01беру IP-адрес сервера, на котором
- 6:38:03расположено нужное мне приложение. Вот
- 6:38:05он, этот IP-адрес. Потом через двоеточие
- 6:38:08я указываю порт, который мне нужен, на
- 6:38:11котором висит нужное мне приложение.
- 6:38:13Получается, я обращаюсь вот по этому вот
- 6:38:15самому IP-адресу, я могу просто в конце
- 6:38:18менять порт, на который я обращаюсь. Тем
- 6:38:20самым я буду обращаться в разные
- 6:38:22приложения, которые запущены на этом
- 6:38:23ээ-сервере. Передав в запросе следующий
- 6:38:25IP-адрес и следующий порт, я сделаю
- 6:38:27запрос на ээкэнд сервер Сбера и потом
- 6:38:30отсюда я сделаю выбор в пользу
- 6:38:32Сбербанка, то есть именно списание
- 6:38:33денежных средств. Если тут я передам
- 6:38:36второй порт, то я уже сделаю выбор в
- 6:38:38пользу Сберзвука и буду слушать музыку.
- 6:38:40Если я тут передам третий порт, тогда я
- 6:38:42сделаю выбор в пользу Сбермаркета и буду
- 6:38:44заказывать продукты. Опять же, для чего
- 6:38:46сделан механизм портов? Почему у нас не
- 6:38:48может быть так, что на одном сервере
- 6:38:49доступен только один порт? Да потому что
- 6:38:51это просто не оптимально. Зачем нам
- 6:38:53покупать ещё один сервер, покупать ещё
- 6:38:55один компьютер, в него материнскую
- 6:38:56плату, процессор, оперативную память,
- 6:38:59накатывать на него операционную систему,
- 6:39:00накатывает на него куча программного
- 6:39:02обеспечения. Если я просто могу
- 6:39:04воспользоваться уже имеющимся сервером,
- 6:39:06просто на дополнительном порту я
- 6:39:08разверну дополнительное приложение, и у
- 6:39:10меня не будет никакой необходимости
- 6:39:12покупать новый сервер, куча
- 6:39:13комплектующих для него и программного
- 6:39:15обеспечения. Я воспользуюсь уже
- 6:39:17имеющимся сервером, просто скажу
- 6:39:19пользователю, что нужно идти вот на
- 6:39:20такой-то порт, и тогда он попадёт в
- 6:39:22такое-то приложение. Для этого нам и
- 6:39:23нужны порты. По сути, тут можно провести
- 6:39:26такую аналогию, что-сервер - это
- 6:39:27какая-то большая торговая пристань, а
- 6:39:30каждый отдельный порт - это отдельный
- 6:39:31заезд для какого-нибудь торгового судна.
- 6:39:34И каждый раз, когда какое-то торговое
- 6:39:35судно будет приходить в нашу пристань,
- 6:39:37ему сначала нужно выбрать конкретный
- 6:39:39заезд, в который он приедет торговать.
- 6:39:41Ну, это такая довольно высокоуровневая
- 6:39:43аналогия, она, наверное, мало понимания
- 6:39:44даёт, но что-то общее у нас с
- 6:39:47ээндсерверами и их портами. Повторюсь,
- 6:39:49для того, чтобы у нас не было
- 6:39:51необходимости на каждое новое приложение
- 6:39:53покупать себе новый сервер, мы можем
- 6:39:55пользоваться механизмом портов, что у
- 6:39:57нас на одном сервере может быть запущено
- 6:39:59сразу несколько совершенно разных
- 6:40:02приложений. И ко всем ним у нас будет
- 6:40:04доступ по сети благодаря тому, что они
- 6:40:07на разных портах у нас работают. Один и
- 6:40:09тот же IP-адрес у них у всех будет, но
- 6:40:11разные порты. Таким образом, нам для
- 6:40:13HTTP запроса, для нашего сетевого
- 6:40:15запроса, помимо домена или IP-адреса,
- 6:40:18нужно знать ещё и конкретный порт, на
- 6:40:20котором развёрнуто нужное нам
- 6:40:22приложение. Откуда мы с вами узнаем этот
- 6:40:24порт? На самом деле всё довольно просто.
- 6:40:27Для сетевых запросов, которые у нас
- 6:40:28происходят по HTTP, дефолтный сетевой
- 6:40:31порт восьмидесятый. А для сетевых
- 6:40:33запросов, которые у нас происходят по
- 6:40:34HTTPS, HTTPS - это то же самое, что
- 6:40:37HTTP. Вот всё то же самое, что мы сейчас
- 6:40:39с вами обсуждали. Но вот эта буковка S
- 6:40:41означает Security, то есть это
- 6:40:43зашифрованный протокол безопасный, по
- 6:40:45которому можно безопасно передавать
- 6:40:46всякие чувствительные данные типа
- 6:40:48логинов, паролей, номеров карт и так
- 6:40:50далее. Так вот, стандартный порт для
- 6:40:52HTTP - это 80, а стандартный порт для
- 6:40:54https - это 443. Получается, когда мы
- 6:40:57будем в браузере вводить какой-нибудь
- 6:40:59запрос, например, youtube.com
- 6:41:02yandex.ru, например, то в этот момент
- 6:41:05наше устройство сделает запрос в DNS, то
- 6:41:07есть в domain name system, в систему
- 6:41:09доменных имён. с тем доменом, который мы
- 6:41:11впили в браузере. В ответ нам придёт
- 6:41:13IP-адрес сервера, на котором расположено
- 6:41:16нужное нам приложение. А потом уже мы
- 6:41:18будем делать запрос на этот полученный
- 6:41:20IP-адрес. И сначала браузер по умолчанию
- 6:41:23попытается сделать запрос на 443 порт,
- 6:41:26потому что это стандартный порт для
- 6:41:28https. Если же не получится сделать по
- 6:41:30защищённому соединению запрос, тогда
- 6:41:32браузер автоматически попробует на
- 6:41:34восьмидесятый HTTP незащищённый порт
- 6:41:37сделать запрос. Мы также можем в запросе
- 6:41:39передавать свои кастомные порты, как мы
- 6:41:41это делали на этом примере. Но если
- 6:41:42никаких портов в запросе не будет
- 6:41:44указано, тогда по умолчанию попробуется
- 6:41:46сначала 443 порт, потом восьмидесятый.
- 6:41:49Благодаря этому факту нам не обязательно
- 6:41:51знать, на каком именно порту у нас
- 6:41:53развёрнут YouTube. Мы просто пишем
- 6:41:55youtube.com, наше устройство само
- 6:41:57получит IP-адрес сервера, на котором
- 6:41:59развёрнут YouTube, и само автоматически
- 6:42:01сделает запрос на 443 порт. Разработчики
- 6:42:03Ютуба это, конечно же, понимали, поэтому
- 6:42:05тоже со своей стороны своё приложение
- 6:42:07развернули на 443 порту. Благодаря всему
- 6:42:10этому нам не обязательно знать
- 6:42:11конкретные порты, на которых развёрнуты
- 6:42:13приложения, в которые мы делаем запросы
- 6:42:15из браузера. Но ведь сетевые запросы
- 6:42:17могут быть не только из браузера или из
- 6:42:18мобильного приложения. Сетевые запросы в
- 6:42:20качестве клиента может производить и
- 6:42:23сервер. На самом деле инициатором
- 6:42:25сетевого запроса может быть не только
- 6:42:26живой пользователь. Инициатором сетевого
- 6:42:28запроса могут быть другие программы, в
- 6:42:31том числе программы, которые крутятся на
- 6:42:33наших бэкэндах. Получается, в
- 6:42:34клиентсерверной архитектуре не только
- 6:42:36клиент может делать запрос на сервер и
- 6:42:38получать ответ с этого сервера. Наш
- 6:42:40сервер тоже может выступать в роли
- 6:42:42клиента и делать запросы на другие
- 6:42:44сервера и получать с них ответы.
- 6:42:46Получается, несмотря на то, что для
- 6:42:48наших пользователей наш бэкэнд - это
- 6:42:50сервер, и они на этот сервер будут
- 6:42:52делать запросы, с нашей точки зрения,
- 6:42:54наш сервер может быть и сервером для
- 6:42:56обработки клиентских запросов, и, в свою
- 6:42:59очередь, клиентом для создания запросов
- 6:43:01на другие сервера. Как это вообще может
- 6:43:03работать в адекватном мире? Рассмотрим
- 6:43:05следующий сценарий. Мы опять же
- 6:43:06разрабатываем с вами Яндекстаси, и у нас
- 6:43:09пассажир захотел сделать заказ. О'кей.
- 6:43:12Первое, что он делает - это нажимает у
- 6:43:14себя там на телефоне кнопочку "Сделать
- 6:43:15заказ". И это приложение, оно под
- 6:43:17капотом, уже знает IP-адрес сервера
- 6:43:19нашего, где у нас крутится эээнд, и по
- 6:43:22этому IP-адресу делает запрос. Также
- 6:43:24наше клиентское приложение уже знает, на
- 6:43:26каком из портов развёрнуто эээнд
- 6:43:28приложение, которое отвечает за такси и
- 6:43:30делает запрос на него. В сумме запрос
- 6:43:32имеет IP-адрес ээ сервера Яндекс и порт
- 6:43:35конкретно приложение Яндекстаси. Если бы
- 6:43:37тут в запросе был порт, например, 2,
- 6:43:40тогда у нас запрос уже был бы в
- 6:43:41Яндекс-музыку. Вот сюда вот. Если бы был
- 6:43:43порт 3, вот запрос был бы в Яндекс
- 6:43:45Market. Но нет, у нас порт один, потому
- 6:43:47что у нас заказ такси. И нам нужно на
- 6:43:49сервера Яндекса сделать запрос именно в
- 6:43:51приложение такси. Поэтому тут у нас порт
- 6:43:53один в нашем запросе. О'кей, мы сделали
- 6:43:56запрос, там сервера Яндекса как-то
- 6:43:58повзаимодействовали с таксистом. В итоге
- 6:43:59таксист принял этот заказ и отвёз нас на
- 6:44:02нужную точку. Мы ему сказали: "Спасибо,
- 6:44:04до свидания". Вышли и таксист у себя
- 6:44:06нажал кнопку, что заказ окончен. Тем
- 6:44:08самым он спровоцировал сетевой запрос на
- 6:44:10сервера Яндекса, конкретно на порт с
- 6:44:12приложением Яндектаси. Яндекста такси
- 6:44:15посмотрела: "Ага, таксист довёз
- 6:44:17пассажира". Что это значит? Это значит,
- 6:44:19что с банковской карты нашего пассажира
- 6:44:22нужно списать стоимость заказа. А кому
- 6:44:24мы можем доверить эту операцию списания
- 6:44:27денег? Приложению таксиста мы не можем.
- 6:44:29Вдруг он какой-нибудь пиратский клиент
- 6:44:31себе установит и нафиг наших
- 6:44:32пользователей обанкротит, обворует.
- 6:44:34Также мы не можем доверить эту операцию
- 6:44:36устройству пользователя. Потому что на
- 6:44:38нём тоже могут быть вирусы или
- 6:44:39пользователь может намеренно какое-то
- 6:44:41пиратское приложение установить, которое
- 6:44:43бы обманывало систему и как-то не так
- 6:44:45списывала деньги или, например,
- 6:44:46наоборот, пользователю переводила деньги
- 6:44:48со счетов Яндекса. Это было бы очень
- 6:44:50странно. Поэтому мы, как Яндекс,
- 6:44:52доверяем перевод денег, например,
- 6:44:54Сбербанку. Но как после того, как
- 6:44:57таксист нажал кнопку и нам в приложении
- 6:44:59Яндекста такси пришло уведомление о том,
- 6:45:00что нужно списывать деньги с пассажира,
- 6:45:02собственно, деньги с пассажира списать.
- 6:45:04А мы берём и изнутри приложения Яндекста
- 6:45:06такси делаем запрос на сервера
- 6:45:09Сбербанка. Делаем запрос на сервера
- 6:45:10Сбербанка именно на тот порт, который
- 6:45:13нам нужен для того, чтобы списывать
- 6:45:14деньги, и попадаем в приложение
- 6:45:16Сбербанка и просим его списать деньги с
- 6:45:18переданного пользователя. После того,
- 6:45:20как деньги списались, мы получим ответ в
- 6:45:22наше приложение Яндекста такси, что
- 6:45:24деньги с пользователя реально списались,
- 6:45:26мы можем спокойно завершать заказ. В то
- 6:45:28время сам Сбербанк уже также может на
- 6:45:30себя взять обязательство уведомить
- 6:45:32пользователя о том, что у него списались
- 6:45:33деньги. Тем самым пользователю тут
- 6:45:35придёт уведомление, что у вас списались
- 6:45:37деньги там столько-то, столько-то за
- 6:45:38поездку Яндек-такси. И на этом примере
- 6:45:40мы с вами рассмотрели, когда эээкэнд
- 6:45:42может служить, в том числе и клиентом
- 6:45:45для запроса в другие бэкнды. Это может
- 6:45:48даже доходить до того, что на одном
- 6:45:50ээкнд-сервере развёрнута куча разных
- 6:45:52приложений, и все эти приложения между
- 6:45:55собой по сети общаются и дают какие-то
- 6:45:57ответы. Это особенно распространено при
- 6:45:59микросервисной архитектуре, но мы к
- 6:46:01этому вернёмся гораздо позже.
- 6:46:03Получается, в сетевом запросе не
- 6:46:04сильно-то важно, что ты из себя
- 6:46:06представляешь. Важен лишь факт, что если
- 6:46:08ты делаешь, инициируешь запрос, то ты
- 6:46:11клиент, а если ты этот запрос
- 6:46:13принимаешь, то ты сервер. Но в следующую
- 6:46:16секунду вот эта сущность может начать
- 6:46:18делать запрос. Тогда она уже будет
- 6:46:20клиентом, а эта сущность будет сервером.
- 6:46:23Вообще HTTP протокол, HTTP запросы, они
- 6:46:25так устроены, что когда мы делаем запрос
- 6:46:27на какой-то сервер, мы незамедлительно
- 6:46:29ждём ответ. Мы можем подождать несколько
- 6:46:31миллисекунд, мы можем подождать
- 6:46:33несколько секунд, но если у нас не
- 6:46:35приходит никакого ответа больше
- 6:46:37условного тайм-аута, он может
- 6:46:38регулироваться. Ну, допустим, это 30
- 6:46:40секунд стандартные. Если мы за 30 секунд
- 6:46:42не получим ответа, этот наш запрос будет
- 6:46:45считаться неуспешным. Это правило вшито
- 6:46:47в сам протокол HTTP, и с ним нужно
- 6:46:49считаться, что если мы делаем сетевой
- 6:46:51запрос по HTTP, мы незамедлительно ждём
- 6:46:53сетевой ответ. И мы тут, когда делаем
- 6:46:55запрос, мы не сможем идти дальше, пока
- 6:46:57не дождёмся ответа. Также со стороны
- 6:46:59сервера, когда мы принимаем запрос, мы
- 6:47:01не можем на когда-то потом отложить
- 6:47:03ответ. Мы должны как можно быстрее
- 6:47:05обработать входящий запрос, сделать всё,
- 6:47:07что от нас нужно, и вернуть клиенту
- 6:47:09ответ, который он запрашивал. Ну,
- 6:47:11например, у нас клиент - это человек,
- 6:47:13который запускает из браузера ВКонтакте,
- 6:47:15пишет там vk.com, получает айпишник,
- 6:47:17идёт по 443 порту и делает запрос на
- 6:47:19бэкэнд сервер ВКонтакте. ВКонтакте
- 6:47:22должен сразу же посмотреть, что это за
- 6:47:23пользователь. должен сразу же в своих
- 6:47:25базах данных найти всю информацию по
- 6:47:27этому пользователю, все его фотографии,
- 6:47:29переписки и так далее. И должен потом
- 6:47:31как можно скорее вернуть пользователю
- 6:47:33ответ. То есть сервер ВКонтакте не может
- 6:47:36в этом сценарии затянуть с ответом там,
- 6:47:38например, на полчаса, да, потому что там
- 6:47:39нагрузка большая, я не хочу сейчас
- 6:47:41перегружаться. Нет, мы должны со стороны
- 6:47:44бэкэнда, со стороны сервера, который
- 6:47:45принимает HTTP запрос, ответить как
- 6:47:48можно скорее. То есть, резюмируя,
- 6:47:50протокол HTTP, он всегда предполагает,
- 6:47:53что у нас есть какой-то клиент и есть
- 6:47:55какой-то сервер. Вот у нас тут на
- 6:47:57картинки есть один HTTP-клиент и два
- 6:47:59HTTP сервера. Первый сервер и второй
- 6:48:02сервер. Когда мы с вами используем
- 6:48:04протокол HTTP, у нас всегда клиент
- 6:48:06делает изначально какой-то запрос на
- 6:48:08сервер. То есть это первая часть вообще
- 6:48:10взаимодействия при HTTP протоколе. Это
- 6:48:13запрос с HTTP-клиента на HTTP сервер.
- 6:48:16Вот это запрос. После запроса, когда
- 6:48:18сервер принял этот запрос, сервер
- 6:48:21незамедлительно начинает этот запрос
- 6:48:22обрабатывать. И как только запрос
- 6:48:24обработан, то идёт незамедлительный
- 6:48:27ответ клиенту. Что это может быть за
- 6:48:29запрос и что это может быть за ответ? То
- 6:48:31есть, допустим, этот HTTP сервер - это
- 6:48:33какой-то удалённый сервер для
- 6:48:34какого-нибудь нашего мессенджера. Ну, к
- 6:48:36примеру, Telegram или ВКонтакте. На этом
- 6:48:38удалённом сервере хранятся всякие
- 6:48:39переписки. И тут вдруг внезапно какой-то
- 6:48:42клиент, какой-то наш пользователь
- 6:48:43открывает веб-страничку нашего
- 6:48:45мессенджера. Тут у него должны появиться
- 6:48:47всякие разные переписочки, но откуда они
- 6:48:48возьмутся, если они хранятся на
- 6:48:49удалённом сервере? А клиент просто
- 6:48:51посети при помощи HTTP сделает запрос,
- 6:48:54мол, пожалуйста, сервер, дай мне все мои
- 6:48:55переписки, а сервер эти все переписки
- 6:48:57достанет из базы данных, там поищет
- 6:48:59вообще у себя по сусем, так сказать, всю
- 6:49:01необходимую информацию и потом эту самую
- 6:49:03информацию вернёт в ответе. То есть мы
- 6:49:05тут должны со стороны сервера как можно
- 6:49:07скорее отдать свой ответ. Мы не можем
- 6:49:09отдать ответ когда-то потом. То есть
- 6:49:11клиент, что будет сидеть и полчаса
- 6:49:12смотреть на пустой экран? Нет, ему нужно
- 6:49:14увидеть все свои переписки. Поэтому мы
- 6:49:16на стороне сервера, как только приняли
- 6:49:18запрос, мы должны как можно скорее всю
- 6:49:20информацию собрать и дать HTTP ответ.
- 6:49:23Получается, протокол HTTP всегда
- 6:49:25предполагает, что есть какой-то клиент,
- 6:49:27есть какой-то сервер. Клиент делает
- 6:49:29запрос на сервер и обязательно должен
- 6:49:32получить ответ от сервера. Клиент, когда
- 6:49:34делает запрос на сервер, он будет ждать
- 6:49:36и будет ждать до тех пор, пока не
- 6:49:38получит ответ от сервера. Если ответа от
- 6:49:40сервера нет, это считается ошибкой.
- 6:49:42Тогда, получается, мы не смогли
- 6:49:44корректно обработать наш HTTP запрос. В
- 6:49:46то же время какой-то HTTP-сервер в
- 6:49:48какие-то моменты времени может выступать
- 6:49:50и HTTP-клиентом.
- 6:49:53То есть это какая-то программа у нас,
- 6:49:55да, развёрнутая на удалённом сервере. И
- 6:49:57когда эта программа обрабатывает
- 6:49:58входящие HTTP запросы и даёт на эти HTTP
- 6:50:01запросы HTTP ответы, тогда эта программа
- 6:50:04выступает в роли HTTP сервера. Но если
- 6:50:06эта же самая программа в следующий
- 6:50:08момент времени уже делает HTTP запрос
- 6:50:10сама и уже сама ждёт HTTP ответ от
- 6:50:13какого-то другого сервера, тогда эта
- 6:50:15самая программа в данный момент времени,
- 6:50:16в данном сценарии выступает HTTP
- 6:50:19клиентом. Ну, это то, что мы с вами
- 6:50:20рассматривали на примере Яндекстаси. То
- 6:50:22есть у нас, когда клиент делает запрос
- 6:50:25на Яндекс-сервер в приложение Яндекс Go,
- 6:50:27то в этот момент приложение Яндек
- 6:50:29выступает в роли HTTP сервера. Но когда
- 6:50:31у нас таксист сигнализирует о том, что
- 6:50:33поездка закончена и приложение Яндекси
- 6:50:35делает запрос на сервер Сбера, чтобы
- 6:50:37списать деньги, то уже в этом сценарии
- 6:50:40приложение Яндекста такси выступает не в
- 6:50:42роли HTTP-сервера, а в роли HTTP
- 6:50:44клиента, который отправляет запрос и
- 6:50:46ждёт какой-то ответ. Более подробно о
- 6:50:48том, как посылать HTTP запросы, как их
- 6:50:50на уровне сервера принимать, как их
- 6:50:52обрабатывать, как давать HTTP ответы, мы
- 6:50:54с вами рассмотрим далее. Но сейчас для
- 6:50:56нас уже хорошо понимать, что в рамках
- 6:50:58протокола HTTP всегда есть клиент, есть
- 6:51:00сервер. Клиент делает запрос на сервер,
- 6:51:02сервер обрабатывает запрос и должен
- 6:51:03незамедлительно потом, как только он
- 6:51:05запрос обработал, отдать ответ. Ну и
- 6:51:06также, что если какая-то программа
- 6:51:08принимает входящее HTTP подключения, это
- 6:51:10не значит, что она не может генерировать
- 6:51:12свои HTTP запросы. Может, просто в этом
- 6:51:14сценарии она уже будет считаться не
- 6:51:16сервером, а клиентом. Клиент - это тот,
- 6:51:18кто отправляет запросы. Сервер - это
- 6:51:20тот, кто запросы принимает и даёт
- 6:51:22ответы. Ну, таким образом мы с вами
- 6:51:24базово и высокоуровнево разобрали теорию
- 6:51:26вообще сетевого взаимодействия в
- 6:51:27современных бэкон-системах. Это лишь
- 6:51:29малая часть всего разнообразия сетевого
- 6:51:31взаимодействия, которое в наше время
- 6:51:33существует. Но тем не менее HTTP - это
- 6:51:37одна из самых базовых основ сетевого
- 6:51:39взаимодействия, которая встречается чаще
- 6:51:42остальных, особенно на прикладном
- 6:51:43уровне, на уровне программиста, который
- 6:51:45эти запросы будет генерировать. Таким
- 6:51:47образом, мы с вами разобрали один из
- 6:51:49самых основных аспектов сетевого
- 6:51:51взаимодействия в современных системах.
- 6:51:53Это HTTP-запросы и клиентсерверное
- 6:51:56взаимодействие. Мы разобрали, что для
- 6:51:58нашего HTTP запроса нам нужно знать либо
- 6:52:00домен, либо IP-адрес сервера, на котором
- 6:52:02развёрнуто нужное нам приложение. Также
- 6:52:04для сетевого запроса, помимо IP-адреса
- 6:52:06или домена, нам нужно знать порт, на
- 6:52:08который мы делаем запрос. Если
- 6:52:10конкретный порт мы не указываем, то по
- 6:52:12умолчанию будет пробоваться сделаться
- 6:52:13запрос либо на 443 порт, либо на
- 6:52:16восьмидесятый. Есть ещё, скажем так,
- 6:52:18тёмная лошадка в этом HTTP запросе,
- 6:52:20которую мы не разобрали, но мы этот
- 6:52:22третий пункт разберём, когда уже начнём
- 6:52:23писать код. Мы с вами узнали, почему в
- 6:52:25современном мире взаимодействие между
- 6:52:27разными устройствами происходит именно
- 6:52:29через, а не каждое устройство общается с
- 6:52:31каждым. Мы с вами поговорили, от каких
- 6:52:33проблем нас вообще избавляет протокол
- 6:52:34HTTP. Ну и теперь мы можем приступить к
- 6:52:36написанию кода и пощупать этот самый
- 6:52:38HTTP руками.
- 6:52:40[музыка]
- 6:52:45Первым делом мы с вами опишем
- 6:52:46простенькую программку на Goang, которая
- 6:52:48будет из себя представлять HTTP сервер.
- 6:52:51То есть мы сможем принимать какие-то
- 6:52:52входящие запросы и запустим мы эту нашу
- 6:52:55программку на 9.091
- 6:52:57порту. На самом деле выбор этого порта,
- 6:52:59он довольно случайный. Вы можете
- 6:53:00развернуть его на восьмидесятом порту,
- 6:53:02можете развернуть своё приложение на
- 6:53:0510.151 порту. Если он будет не занят, то
- 6:53:08вы спокойно сможете запустить своё
- 6:53:10приложение, и оно будет слушать входящее
- 6:53:12подключение на данном порту. Хорошо.
- 6:53:14Допустим, мы с вами описали наш
- 6:53:15HTTP-сервер. Мы умеем принимать входящие
- 6:53:18запросы на 90.91 порту. Эти входящие
- 6:53:21запросы будут приходить к нам в
- 6:53:22приложение. Мы умеем их даже
- 6:53:24обрабатывать и умеем как-то отдавать
- 6:53:25ответы. Но теперь вопрос. Это самое
- 6:53:28приложение у нас запущено на нашем
- 6:53:30компьютере. Как нам сделать в наш
- 6:53:33компьютер сетевой запрос? Мы же
- 6:53:35привыкли, что сетевой запрос - это когда
- 6:53:37какое-то удалённое устройство делает
- 6:53:39запрос на наш айпишник, да, у нас есть
- 6:53:42какой-то айпишник, нам на этот айпишник
- 6:53:44делают запрос, и потом ещё у нас куча
- 6:53:47разных портов может быть в теории
- 6:53:48доступно, и нам выбирают именно порт с
- 6:53:51нашим приложением, и тогда достучаться
- 6:53:52до нашего приложения. Хорошо, но у нас
- 6:53:54же нету как бы второго компьютера. А
- 6:53:56может и есть, но это слишком неудобно
- 6:53:57будет на двух компьютерах сразу
- 6:53:58разрабатывать. Каким образом нам сделать
- 6:54:00вот сюда вот сетевой запрос? А мы можем
- 6:54:02со своего же компьютера начать делать
- 6:54:05сетевой запрос на айпишник 127.0.0.1.
- 6:54:12Что это за айпишник? Когда мы с вами
- 6:54:14будем делать запрос на вот этот
- 6:54:15айпишник, то наше устройство поймёт, что
- 6:54:17запрос нужно делать на самого себя. Ну,
- 6:54:21ровно так же, как в случае с обычными
- 6:54:22сетевыми запросами. Нам помимо айпишника
- 6:54:25запроса нужно ещё и порт передать. Но
- 6:54:27порт мы знаем, это 9.091 порт.
- 6:54:30Соответственно, тут 127001 двоеточие и
- 6:54:33пишем 9.000 91 порт. Таким образом, мы
- 6:54:37из всех возможных портов, которые у нас
- 6:54:39на компьютере, могут быть открыты, мы об
- 6:54:40этом даже можем не подозревать, потому
- 6:54:42что они используются операционной
- 6:54:43системой, мы из всех них выберем именно
- 6:54:45порт, на котором у нас развёрнуто наше
- 6:54:47написано Goeng приложение. Таким
- 6:54:49образом, делая на вот этот вот айпишник
- 6:54:51запросы, мы можем тестировать свои же
- 6:54:53написанные приложения, которые у нас
- 6:54:54запущены локально на нашем же
- 6:54:56компьютере. Ну, ещё стоит добавить, что
- 6:54:58у этого айпишника есть свой домен.
- 6:55:01Выглядит он следующим образом. Это local
- 6:55:03host. То есть мы можем делать как
- 6:55:05запросы сразу же на айпишник 127001, и
- 6:55:07тогда мы попадём из своего компьютера
- 6:55:09обратно в свой же компьютер. Либо мы
- 6:55:11можем делать запросы по домену local
- 6:55:13host. Тогда этот домен через ДНС будет
- 6:55:16преобразован автоматически в айпишник
- 6:55:18127001. И опять запрос будет
- 6:55:20производиться обратно в наш компьютер.
- 6:55:22То есть тут по желанию мы уже в запросе
- 6:55:24можем указывать как айпишник и через
- 6:55:26двоеточие порт конкретного приложения, в
- 6:55:28котором мы хотим сделать запрос, либо
- 6:55:30вместо этого айпишника мы можем
- 6:55:31указывать домен, но всё равно через
- 6:55:33двоеточие нам необходимо указать порт,
- 6:55:35на который мы делаем запрос. 90.091.
- 6:55:38Таким образом, этот домен local host
- 6:55:40через DNS, через domain name system,
- 6:55:42через систему доменов автоматически
- 6:55:44преобразуется из текста конкретный
- 6:55:46айпишник в конкретно 127001. Этот
- 6:55:49айпишник будет значить то, что нужно
- 6:55:51делать запросы не на какое-то там
- 6:55:52удалённое устройство, а то, что нужно
- 6:55:54делать запросы в самого себя. Ну и потом
- 6:55:57через двоето иточие мы что в случае с
- 6:55:59доменом, что в случае с айпишником,
- 6:56:01должны указать конкретный порт, на
- 6:56:02который будет производиться наш запрос.
- 6:56:04Это порт 90.91. Ну и, соответственно, в
- 6:56:07этот порт попадёт наш запрос. Таким
- 6:56:08образом, мы сможем тестировать наши
- 6:56:10приложения, написанные и запущенные
- 6:56:12локально у нас на компьютере, просто со
- 6:56:13своего же компьютера. Это очень удобно и
- 6:56:15очень часто используется. Ну, просто
- 6:56:17потому что, представьте, нам, чтобы
- 6:56:18протестировать какую-то приложуньку,
- 6:56:20нужно было бы два компьютера иметь слева
- 6:56:22и справа. Два физически разных
- 6:56:24компьютера. Ну, это было бы очень
- 6:56:25неудобно. На обоих этих системах нужно
- 6:56:27было бы поддерживать актуальную версию
- 6:56:29программного обеспечения и очень много
- 6:56:30неудобств, которые с этим связаны. А так
- 6:56:32мы можем со своего же компьютера делать
- 6:56:34запросы в себя же в целях тестирования.
- 6:56:36Тут может встать вопрос, что нам в итоге
- 6:56:38лучше использовать? Напрямую через
- 6:56:40айпишник делать запрос или через
- 6:56:41доменное имя. На самом деле, тут уже
- 6:56:43просто ваша вкусовщина. Обычно принято.
- 6:56:46Если делается запрос из своего
- 6:56:48устройства обратно на своё же
- 6:56:49устройство, тогда используется домен
- 6:56:51local Local host. Просто потому что его
- 6:56:53проще запомнить и проще написать. Каждый
- 6:56:54раз писать 127001 не очень удобно, а вот
- 6:56:57написать local host гораздо удобнее.
- 6:56:59Поэтому обычно просто пишут local host и
- 6:57:00через двоеточие пишут конкретный порт,
- 6:57:02на который нужно сделать запрос. Хорошо,
- 6:57:04мы всё поняли. то, что нам нужно
- 6:57:06написать какое-то там приложение,
- 6:57:08которое будет на 9.0 девяносто перво
- 6:57:09порту слушать входящие подключения. Нам
- 6:57:11для того, чтобы это самое приложение
- 6:57:12протестировать, нужно со своего
- 6:57:14компьютера делать запросы на local host.
- 6:57:16Тем самым мы будем делать запросы
- 6:57:17обратно на свой же компьютер. О'кей,
- 6:57:19допустим, мы с вами разберёмся, как
- 6:57:21написать приложение. А как запросы эти
- 6:57:23делать, как их генерировать, ну, вообще
- 6:57:25непонятно. Для этого есть огромное
- 6:57:26множество разных утилит, как тех,
- 6:57:28которые работают только в терминале, так
- 6:57:30и утилит с графическим интерфейсом. Но
- 6:57:33одна из самых таких распространённых и
- 6:57:35удобных утилит, которые особенно часто
- 6:57:37используются в тестировании - это
- 6:57:38Postman. Мы с вами скачаем и установим
- 6:57:41Postman на наш с вами компьютер. И
- 6:57:42благодаря этому постману мы уже будем
- 6:57:44генерировать сетевые запросы. И потом
- 6:57:46уже эти сетевые запросы, сгенерированные
- 6:57:48постманом, будут доходить до нашего
- 6:57:49написанного на Goeng приложения. Таким
- 6:57:51образом, во всём этом взаимодействии
- 6:57:53наше приложение, написанное на Goang,
- 6:57:55будет считаться HTTP сервером,
- 6:57:58а Postman в этой всей схеме будет
- 6:58:00считаться HTTP-клиентом. То есть это
- 6:58:02клиент, который делает свой запрос, а
- 6:58:05это сервер, который HTTP запросы
- 6:58:07принимает, обрабатывает и даёт свои
- 6:58:09ответы. По сути, мы с вами уже будем
- 6:58:11писать, начинать приложение, потому что
- 6:58:14в данном случае, хоть это и очень
- 6:58:15простое, но всё равно приложение.
- 6:58:17Почему? Потому что оно принимает и
- 6:58:19обрабатывает сетевые запросы. Да, мы
- 6:58:21пока ещё изнутри своего эээнд приложения
- 6:58:24не делаем куда-то ещё запросы в какие-то
- 6:58:26другие ээнд- приложения, но тем не
- 6:58:27менее, даже просто смотря на тот факт,
- 6:58:29что мы умеем слушать какой-то порт,
- 6:58:31принимать входящие HTTP запросы и давать
- 6:58:33на них HTTP-ответы, уже свидетельствуют
- 6:58:35о том, что мы написали приложение. Но
- 6:58:38повторюсь, первым делом нам нужно
- 6:58:39установить Postman. Для того, чтобы
- 6:58:41скачать Postman на вашу операционную
- 6:58:43систему, смотрите в тайм-коды. Там я
- 6:58:45показываю, как скачать Postman на
- 6:58:46Window, на MacOS и на Убунту. И после
- 6:58:49того, как установите Postman, можете
- 6:58:51сразу прыгать на таймкод первый HTTP
- 6:58:53сервер. Там мы начнём писать наше первое
- 6:58:55миниend приложение.
- 6:58:58[музыка]
- 6:59:02Для того, чтобы скачать Postman на
- 6:59:04Windows, мы должны с вами перейти в
- 6:59:05браузер и тут в поисковой строке
- 6:59:07написать Postman install. Enter. И
- 6:59:11переходим по первой же ссылке. Нажимаем.
- 6:59:14Нас встречает такая вот страница. И тут
- 6:59:16есть жёлтая кнопка такая Windows 64 bit.
- 6:59:18Нам нужно на неё нажать.
- 6:59:21Автоматически начинается загрузка
- 6:59:23приложения. Ждём, пока оно скачается.
- 6:59:26Нажимаем и всё готово. У нас скачано,
- 6:59:28установлено приложение Postman. Для
- 6:59:30того, чтобы продолжить, здесь вот в
- 6:59:31самом низу нужно нажать такую еле
- 6:59:32заметную кнопочку Continue Visout and
- 6:59:34Account. Ну или же продолжить без
- 6:59:35аккаунта. Нам тут ещё раз Постон
- 6:59:38предлагает авторизироваться, да, создать
- 6:59:40аккаунт, но мы всё ещё можем нажать
- 6:59:41кнопку Open Lightweight API client
- 6:59:43Client и продолжить без аккаунта. Для
- 6:59:45чего вообще нужен аккаунт в Постмене?
- 6:59:46Ну, самое большое преимущество, которое
- 6:59:48даёт нам аккаунт - это то, что мы можем
- 6:59:50описать сразу несколько HTTP запросов.
- 6:59:52Мы пока ещё этого не умеем делать, но
- 6:59:53скоро научимся. А потом мы сможем их
- 6:59:56сохранять и потом, например, делиться с
- 6:59:57нашими коллегами. То есть, как вот тут
- 6:59:59изображено, мы целую папочку с нашими
- 7:00:01запросами можем сохранить, потом мы их
- 7:00:03можем экспортировать и передать нашему
- 7:00:04коллеге, который тоже захочет как-то
- 7:00:06протестировать наш сервис, а у него уже
- 7:00:08будут готовы описаны эти запросы. Но
- 7:00:10пока в целях обучения нам аккаунт не
- 7:00:12нужен. Если кто-то хочет, может его себе
- 7:00:13спокойно создать. Я же нажимаю кнопку
- 7:00:15Open Lightweight API Client и продолжаю
- 7:00:17как бы без аккаунта. Всё готово. Мы с
- 7:00:19вами скачали и установили Postman на
- 7:00:21Windows. И как раз-таки благодаря
- 7:00:22Постману мы будем описывать наши HTTP
- 7:00:24запросы, будем эти HTTP запросы
- 7:00:26отправлять и получать HTTP ответы,
- 7:00:28благодаря чему и будем взаимодействовать
- 7:00:30с нашим бэкэнд приложением по сети.
- 7:00:33[музыка]
- 7:00:38Для того, чтобы установить Postman на
- 7:00:39MacOS, нам нужно написать в браузерной
- 7:00:41строке Brew install Postman. Переходим
- 7:00:44по первой же ссылочке.
- 7:00:47И тут есть команда, которую нам
- 7:00:48необходимо выполнить, чтобы самый этот
- 7:00:50постман скачать. Копируем эту команду и
- 7:00:52переходим в терминал. Вставляем команду
- 7:00:54и нажимаем Enter. Ждём, пока у нас всё
- 7:00:57установится. Всё, поздравляю, у нас наш
- 7:00:59постма скачался. Теперь мы можем зайти в
- 7:01:01launchpad, начать писать Postman. И вот
- 7:01:04он наш Pstman. Запускаем его. Разрешаем
- 7:01:07открытие. Он нам тут предлагает
- 7:01:09авторизироваться. Это нужно для того,
- 7:01:11чтобы ваши запросы, которые вы опишите,
- 7:01:13вы могли сохранять в папочке и потом
- 7:01:15запускать на других устройствах или
- 7:01:16делиться с коллегами. Особенно удобно,
- 7:01:18когда вы заранее описали несколько HTTP
- 7:01:20запросов, которые нужны, чтобы
- 7:01:21протестировать ваше приложение, и потом
- 7:01:23хотите этими самыми HTTP запросами
- 7:01:25поделиться с вашими коллегами. Но нам
- 7:01:27это пока не нужно. Мы нажимаем вот здесь
- 7:01:29вот внизу такая незаметная кнопочка
- 7:01:30продолжить без создания аккаунта. Ещё
- 7:01:32раз нажимаем Open Lightweight API
- 7:01:34Client. И уаля, мы с вами скачали и
- 7:01:37запустили наш Postman. Мы вернёмся сюда
- 7:01:39попозже. Теперь мы просто скачали
- 7:01:41утилиту, благодаря которой мы с вами
- 7:01:42можем генерировать сетевые запросы. Но
- 7:01:44пока что нам их просто некуда
- 7:01:45генерировать. Нам нужно с вами написать
- 7:01:47приложение, которое эти самые сетевые
- 7:01:49запросы будет принимать.
- 7:01:51[музыка]
- 7:01:56Как скачать Postman на Linux? Если
- 7:01:58честно, очень много у нас с вами
- 7:01:59различных дистрибутивов. Везде это можно
- 7:02:02сделать десятками различных способов.
- 7:02:04где-то скачать архив с постманом, потом
- 7:02:06разархивировать его, потом создать
- 7:02:07какие-нибудь символические ссылки, потом
- 7:02:09скачать ещё дополнительную иконку, всё
- 7:02:11это настраивать. Если вы скачали
- 7:02:12какой-то такой необычный дистрибутив
- 7:02:14Линкса, там типа арча или какую-нибудь
- 7:02:16гинту, тогда, конечно, я вам советую вот
- 7:02:19сесть и разобраться, как скачать
- 7:02:20Postman, потому что сейчас у нас нет
- 7:02:22времени на каждый прямо дистрибутив
- 7:02:23показывать, как скачать Postman. Но так
- 7:02:25как у меня у Бунту, у меня всё крайне
- 7:02:27просто. В бунту есть пакетный менеджер
- 7:02:29Snap. Я просто им и воспользуюсь. Пишу
- 7:02:32суюда snap install postman Enter. Пароль
- 7:02:36ввожу.
- 7:02:38У меня скачивается мой постма. И всё.
- 7:02:40Постма на убнту скачался. Мы можем
- 7:02:42перейти в наше приложение В поиск и тут
- 7:02:45вбить Postman. И вот он наш с вами
- 7:02:47Postman. Открываем его и нас
- 7:02:49приветствует в следующее окно. Тут он
- 7:02:51нам предлагает завести аккаунт и войти в
- 7:02:53него. Либо можно нажать кнопку Open
- 7:02:55Lightweight API cliли и продолжить без
- 7:02:57аккаунта. Для чего вообще в Postman
- 7:02:59аккаунт? Ну, тут как раз-таки
- 7:03:00перечислены плюсы того, что вы создадите
- 7:03:02себе аккаунт и будете в него входить. Но
- 7:03:04один из самых таких больших, наверное, и
- 7:03:06понятных нам всем плюсов - это то, что
- 7:03:07мы можем не только описывать HTTP
- 7:03:09запросы, мы пока ещё этого не умеем,
- 7:03:11скоро научимся, не только описывать HTTP
- 7:03:13запросы, но и собирать их в папочки,
- 7:03:15сохранять и потом делиться с нашими
- 7:03:17коллегами. Например, если наш коллега
- 7:03:18хочет протестировать описанное нами
- 7:03:20ээнд- приложение, мы просто ему можем
- 7:03:22уже готовые запросы объединить в папочку
- 7:03:24и скинуть там в личные сообщения,
- 7:03:25например, и он уже у себя на компьютере
- 7:03:27откроет постмен, и у него будут эти
- 7:03:29готовые описанные HTTP запросы. Но пока
- 7:03:31в целях обучения я не буду создавать
- 7:03:32себе аккаунт. Если кто-то хочет, может
- 7:03:34это, в принципе, сделать, это бесплатно,
- 7:03:35но я нажимаю кнопку Open Lightweight API
- 7:03:37Client. Всё готово. Мы с вами скачали,
- 7:03:40установили Postman на Убунту. И теперь
- 7:03:42мы с вами сможем описывать наши HTTP
- 7:03:44запросы, отправлять наши HTTP запросы и
- 7:03:47получать HTTP ответы.
- 7:03:50[музыка]
- 7:03:54Хорошо, давайте начнём писать,
- 7:03:56собственно, наш код, писать наше миниend
- 7:03:58приложение, которое бы обрабатывало
- 7:04:00входящие сетевые запросы. Сейчас будет
- 7:04:02очень много непонятного кода, но мне
- 7:04:04нужно сначала этот код написать и лишь
- 7:04:06потом я вам смогу его объяснить. Для
- 7:04:08начала нам нужно обратиться в пакет HTTP
- 7:04:11и там воспользоваться функцией handle
- 7:04:13fun. Что такое handle funk? Handle
- 7:04:16принимает два каких-то аргумента. Первый
- 7:04:18- это строковый аргумент, который
- 7:04:20называется паттерн. Что за паттерн? Пока
- 7:04:23вообще непонятно, что за паттерн. Потом
- 7:04:25какой-то хендлер. Что это вообще за
- 7:04:27хендлер, тоже непонятно. Ну хорошо,
- 7:04:29давайте пока на месте паттерна я оставлю
- 7:04:31пустую строку, а на месте хендлера я
- 7:04:33напишу то, что мне посоветует редактор
- 7:04:35кода. Я могу начать писать fun. И тут
- 7:04:37сразу же такая подсказочка появляется. Я
- 7:04:39на неё нажимаю, и у меня появляется
- 7:04:42какая-то сигнатура функции, в том числе
- 7:04:44тело этой функции. Вот оно между
- 7:04:46скобочек. Получается у меня в этот
- 7:04:48handle fun. То есть handlef - это
- 7:04:50функция из пакета HTP. Я в этот handle
- 7:04:52fun в качестве аргумента передаю ещё
- 7:04:55одну функцию. То есть в Гошке мы можем в
- 7:04:57качестве аргументов функций передавать
- 7:04:59другие функции. Да, так это и работает.
- 7:05:01О'кей. Что тут вообще происходит? Я
- 7:05:03говорю Гошке, что я хочу воспользоваться
- 7:05:06HTTP и я хочу принимать сетевые запросы
- 7:05:09и уметь их обрабатывать. Сам обработчик
- 7:05:12сетевых запросов - это хендлер. Вот как
- 7:05:14раз-таки вот эта вот функция. Для того,
- 7:05:16чтобы не путаться, мы этот самый хендлер
- 7:05:18можем из анонимной функции вынести в
- 7:05:20именованную. Я так и назову эту функцию
- 7:05:22хендлер и просто перекопирую её
- 7:05:24сигнатуру, все её входящие аргументы. И
- 7:05:27теперь я смогу этот самый хендлер
- 7:05:28передавать сюда заместо вот этой вот
- 7:05:30анонимной функции. Это я всё стираю и
- 7:05:33пишу сюдах handlerр. Всё. То есть я
- 7:05:35сказал Гошке, что я хочу по HTTP
- 7:05:38принимать какие-то входящие подключения
- 7:05:40и обрабатывать их в функции handler.
- 7:05:43Входящие подключения будут
- 7:05:44обрабатываться в функции handler. О'кей.
- 7:05:46Что у нас тут есть в принимаемых
- 7:05:47аргументах этой функции? Первое - это
- 7:05:49какой-то response writer из пакета HTTP.
- 7:05:52На самом деле responsр нам нужен для
- 7:05:54того, чтобы на входящие сетевые запросы
- 7:05:56мы могли писать сетевые ответы. Как
- 7:05:59раз-таки для этого респонс вратер нужен.
- 7:06:01Можем посмотреть, какие у него есть
- 7:06:02методы. У него есть методы header, пока
- 7:06:03непонятно, что это такое. Wrght header,
- 7:06:06тоже пока непонятно, но есть метод WR.
- 7:06:08WR принимает какой-то слайс байт, тоже
- 7:06:11непонятно, почему слайс byйт, что это
- 7:06:13вообще такое, и возвращает целое число и
- 7:06:15ошибку. Пока непонятно, что он вообще
- 7:06:17принимает, но мы можем увидеть, что
- 7:06:19write, то есть этот метод, он записывает
- 7:06:21какую-то информацию в HTTP ответ. Вот
- 7:06:24HTTP reply. Получается, при помощи этого
- 7:06:26спонсрайтера мы можем как-то наполнять
- 7:06:28наши вот эти вот HTTP ответы. И тем
- 7:06:30самым тому, кто сделал изначально HTTP
- 7:06:33запрос, мы можем давать какой-то HTTP-
- 7:06:35ответ. Хорошо, с репонсрайтером
- 7:06:37разобрались. Теперь у нас есть какой-то
- 7:06:39HTTP request. Ну а HTTP requкст - это
- 7:06:41уже, собственно, вся та информация,
- 7:06:43которая нам пришла в HTTP запросе. Пока
- 7:06:46что мы не совсем понимаем, что за
- 7:06:47информация она может прийти в HTTP
- 7:06:48запросе, но на самом деле там может быть
- 7:06:51достаточно много информации. Вместе с
- 7:06:52этим HTTP запросом тот, кто этот запрос
- 7:06:55отправляет, может отправлять ещё
- 7:06:56дополнительную всякую информацию. Мы это
- 7:06:57всё разберём попозже, пока для нас это
- 7:06:59пустой звон. Но это неотъяблемая часть
- 7:07:01HTTP запроса. О'кей. То есть благодаря
- 7:07:03респонсрайтеру мы можем писать ответы на
- 7:07:06входящие HTTP запросы. А вот этот HTTP
- 7:07:08requкст - это как раз-таки структура, в
- 7:07:10которой содержится вся информация о
- 7:07:11входящем запросе. Пока мы не знаем, что
- 7:07:13это за информации, как с ней работать,
- 7:07:14но просто факт, что в этом аргументе нам
- 7:07:16приходит информация о входящем HTTP
- 7:07:18запросе. Хорошо, что я захочу сделать? А
- 7:07:21я захочу просто в этот responsть Hello
- 7:07:23World, чтобы тот, кто делал запрос в
- 7:07:25наше приложение, он получал ответ Hello
- 7:07:28World. Хорошо, как мне это сделать? WR
- 7:07:30метод responsс writра принимает с by, но
- 7:07:33я хочу записать hello world, то есть
- 7:07:34строку. Для этого я сначала создам
- 7:07:37строку Hello World. После этого я
- 7:07:41преобразую её в с. Выглядит это на самом
- 7:07:44деле довольно просто. Я пишу сй байт и в
- 7:07:46скобочках передаю строку. Эта строка
- 7:07:48будет разобрана на байты и положена в
- 7:07:51переменную B. После этого я эту самую
- 7:07:53переменную B смогу передать. Тем самым я
- 7:07:55запишу Hello World в http ответ через
- 7:07:58метод WR respons writтера. Но у нас
- 7:08:00метод WR возвращает два значения. Первое
- 7:08:02- это integer, второе - это ошибка. Вот
- 7:08:04этот самый integer - это количество
- 7:08:06байт, которое нам по факту удалось
- 7:08:08записать. На самом деле практически
- 7:08:10никто и никогда не смотрит на вот это
- 7:08:11значение. Все смотрят на ошибку. Была ли
- 7:08:14ошибка во время записи ответа. Поэтому я
- 7:08:17на место этого интеджера поставлю нижний
- 7:08:18прочерк, а вот ошибку я буду получать.
- 7:08:21То есть я благодаря вот этой записи
- 7:08:22сказал, что я, в принципе, не хочу
- 7:08:23получать вот этот вотджер. Мне неважно,
- 7:08:25сколько я там байт записал на самом
- 7:08:26деле, потому что если я что-то не
- 7:08:28дозапишу, в любом случае эта информация
- 7:08:30мне придёт в ошибке. Ну и,
- 7:08:31соответственно, после этого проверю, что
- 7:08:32если у меня ошибка нел, то есть какая-то
- 7:08:35ошибка была, тогда я просто выведу на
- 7:08:37экран во время записи HTTP ответа
- 7:08:43произошла ошибка. Ну и буду саму эту
- 7:08:46ошибку тоже выводить, её текст. Хорошо.
- 7:08:49Иначе, если ошибок не было, то я напишу.
- 7:08:51Я корректно обработал HTTP запрос.
- 7:08:57Всё, казалось бы, что ещё нам надо? Мы
- 7:09:00через пакет HTTP говорим Гошке, что я
- 7:09:02хочу обрабатывать входящие сетевые
- 7:09:04запросы в функции handler. Ну, давайте
- 7:09:07попробуем запустить, наверное,
- 7:09:08посмотрим, что вообще будет. Я пишу
- 7:09:11main.
- 7:09:13И у нас какая-то паника. Что пишет
- 7:09:15инвалид паттерн? Нам даже строчка тут
- 7:09:17указана двадцать первая. И как раз-таки
- 7:09:19тут мы на место паттерна передали пустую
- 7:09:21строку. Получается, пустая строка нам не
- 7:09:24подходит. Что же тогда вообще такое этот
- 7:09:26самый паттерн? А паттерн - это та самая
- 7:09:29третья вот эта тёмная лошадка, которая
- 7:09:32нам нужна для того, чтобы совершить HTTP
- 7:09:34запрос. Ведь давайте подумаем, если я
- 7:09:36буду разрабатывать какое-то более
- 7:09:37сложное приложение, я, наверное, захочу
- 7:09:39иметь не один хендлер, а несколько. Ну,
- 7:09:41к примеру, я разрабатываю ээнд для
- 7:09:43Сбербанка, и я хочу уметь производить
- 7:09:45разные действия над денежными
- 7:09:47операциями. В каком-то хендлере я захочу
- 7:09:49обрабатывать новые оплаты. Я это назову
- 7:09:51payх handler, и он тоже будет принимать
- 7:09:53у меня
- 7:09:55respons writer и http request. Потом в
- 7:09:58другом хендлере я захочу научиться
- 7:10:00отменять эти оплаты. Я назову это
- 7:10:02councel handler, и он точно также у меня
- 7:10:04будет принимать responsкст
- 7:10:09следующим образом. О'кей. Но каким
- 7:10:11образом мне во время моего HTTP запроса
- 7:10:14указать, какое именно действие я хочу
- 7:10:17производить? Для этого и создан вот этот
- 7:10:20вот паттерн. Что это вообще такое? То
- 7:10:22есть у нас, когда приходит запрос в наше
- 7:10:24приложение, на самом деле внутри
- 7:10:26приложения у нас есть ещё дополнительный
- 7:10:28выбор, какую именно функцию, какой
- 7:10:31именно хендлер я хочу вызвать, передав
- 7:10:33тот или иной HTTP запрос.
- 7:10:36То есть я во время отправки HTTP запроса
- 7:10:39в моё приложение могу на самом-то деле
- 7:10:40хотеть со своим приложением сделать
- 7:10:42разные вещи. В одном запросе я хочу
- 7:10:44произвести новую оплату, если у меня
- 7:10:45ээкэнд для Сбербанка, например. Во
- 7:10:47втором запросе в наше приложение я
- 7:10:49захочу произвести отмену этой платёжной
- 7:10:51операции. В третьем запросе я вообще
- 7:10:53захочу просто получить всю информацию о
- 7:10:55уже пройденных операциях. Просто
- 7:10:57посмотреть вообще, что пользователь
- 7:10:58купил за сегодня. И это всё
- 7:11:00обрабатывается одним приложением. Почему
- 7:11:02одним? Ну, потому что представьте, если
- 7:11:04бы у нас для каждого приложения был бы
- 7:11:06доступен только один хендлер, это
- 7:11:08получается, что нам нужно было бы для
- 7:11:09каждого отдельного хендлера писать
- 7:11:11дополнительное приложение и запускать
- 7:11:13его на дополнительном порту. И тогда бы
- 7:11:15у нас было, что если я захочу провести
- 7:11:17операцию оплаты, тогда я буду делать
- 7:11:19запрос на вот этот вот порт, и у меня
- 7:11:20вот это мини-приложение будет
- 7:11:22обрабатывать оплату. Потом, если я
- 7:11:23захочу отменить оплату, я буду делать
- 7:11:25запрос уже в это приложение, и оно будет
- 7:11:27отменять оплату. Если я захочу получить
- 7:11:29информацию о всех произведённых оплатах,
- 7:11:31я буду делать запрос вот в это
- 7:11:32приложение, и оно уже будет мне собирать
- 7:11:34информацию о всех произведённых оплатах
- 7:11:36и отдавать HTTP ответ. У этого подхода
- 7:11:38есть огромное количество минусов.
- 7:11:39Например, этих самых хендлеров у нас
- 7:11:41может быть не три, а 50, например, или
- 7:11:44100. И получается, мы должны 100 портов
- 7:11:46нашего компьютера занимать вот этими
- 7:11:48микроприложениями. Это уже может давать
- 7:11:50некоторую нагрузку на сеть на нашем
- 7:11:52устройстве, что увеличит время обработки
- 7:11:54пользовательских запросов, пользователи
- 7:11:55могут остаться недовольны. Это первое.
- 7:11:57Второе - это просто неудобно
- 7:11:59разрабатывать. Мне бы было гораздо
- 7:12:00удобнее, если бы все хендлеры, которые
- 7:12:02отвечают за оплату, были бы в одной моей
- 7:12:05программе. Я бы их всех видел, они бы у
- 7:12:06меня просто были в разных папочках. Мне
- 7:12:08с этим было бы удобно работать. Но если
- 7:12:09у меня каждый отдельный хендлер - это
- 7:12:11отдельное приложение, то мне просто это
- 7:12:13неудобно будет разрабатывать. Мне
- 7:12:14постоянно придётся бегать из одного
- 7:12:16приложения в другое. И тут, на самом
- 7:12:17деле, есть ещё очень большое количество
- 7:12:19причин, которые мы пока в силу своей
- 7:12:20неопытности понять не сможем. Но, говоря
- 7:12:22общим языком, такой подход к разработке
- 7:12:25- это максимально неэффективное
- 7:12:26использование ресурсов, которые нам
- 7:12:28доступны. В первую очередь это ресурсы
- 7:12:30наших железок, на которых крутится наш
- 7:12:32бэкэнд. Во вторую очередь, это ресурс
- 7:12:34наших разработчиков, которые за это же
- 7:12:35самое время могли быстренько накидать
- 7:12:37одно приложение, которое умеет
- 7:12:38обрабатывать несколько хендлеров. Эти же
- 7:12:39самые разработчики вместо этого будут
- 7:12:41разрабатывать большое количество
- 7:12:42различных приложений. Это всё за собой
- 7:12:44тянет очень большую сложность в том,
- 7:12:46чтобы это всё задеплоить на удалённый
- 7:12:48сервер, потому что гораздо проще
- 7:12:49контролировать выполнение одной
- 7:12:51программки, чем сразу кучи программ.
- 7:12:53Допустим, мы захотим какую-то новую
- 7:12:55версию выкатить нашего приложения,
- 7:12:56которое как-то по-новому сохраняет наши
- 7:12:58оплаты и как-то по-новому отдаёт
- 7:13:00информацию об этих оплатах. И тут сразу
- 7:13:01вопрос: а как нам это обновление
- 7:13:02выкатывать? Допустим, я случайно сначала
- 7:13:04выпущу вот это приложение, которое у
- 7:13:06меня отдаёт новую информацию об оплате,
- 7:13:08но у меня ещё нету новой информации об
- 7:13:09оплате, потому что я не выкатил другое
- 7:13:11приложение, которое отвечает за эту
- 7:13:13новую информацию об оплате, за её
- 7:13:14сохранение. И очень долго тут можно на
- 7:13:16эту тему разговаривать. Общий вектор
- 7:13:18мысли, я думаю, вы поняли. Это просто
- 7:13:20неэффективно. Зачем мне под каждый
- 7:13:22хендлер описывать новое приложение, если
- 7:13:25я все эти хендлеры могу создать в одном
- 7:13:28приложении и потом уже выборочно
- 7:13:30обращаться либо к этому хендлеру, либо к
- 7:13:32этому хендлеру, либо к этому хендлеру.
- 7:13:34Ну да, это действительно гораздо проще,
- 7:13:35удобней и надёжней. Но теперь вопрос,
- 7:13:38как мне во время вот этого моего
- 7:13:39сетевого запроса выбрать, в какой именно
- 7:13:42хендлер я буду делать запрос? За это и
- 7:13:44отвечает вот этот вот самый паттерн. Мы,
- 7:13:46когда с вами через вот этот хенфунк
- 7:13:48будем обрабатывать какой-то хендлер, мы
- 7:13:50должны указать, на каком паттерне этот
- 7:13:52хендлер будет вызываться. Давайте я
- 7:13:54продемонстрирую то, о чём я вам говорю.
- 7:13:56Первый хендлер у нас самый будет
- 7:13:58обрабатываться на паттерне.
- 7:14:01Когда я буду обращаться к этому паттерну
- 7:14:03default, я буду вызывать хендлер,
- 7:14:05который мне возвращает Hello World. Вот
- 7:14:07мы записываем наши байты для Хеллоу
- 7:14:09Worldда. Я вот это вот всё скопирую,
- 7:14:11вставлю во второй хендлер. Во втором
- 7:14:13хендлере я уже буду возвращать не Hello
- 7:14:15World, а оплата отменена. Ну и в конце,
- 7:14:18если всё было успешно, я напишу, что я
- 7:14:20корректно отменил оплату. Вот эта вот
- 7:14:23строка оплаты отменена, она нам будет
- 7:14:25возвращаться в http ответе. И мы это
- 7:14:27увидим в нашем постмае. А вот это вот я
- 7:14:29корректно отменил оплату, это будет
- 7:14:31просто привычный вывод нашей программы в
- 7:14:32консоли. Ну и давайте то же самое для
- 7:14:34нашего хендлера, который типа
- 7:14:36обрабатывает новые платежи. Буду
- 7:14:38записывать я строку Новый платёж
- 7:14:41обработан и выводить на экран я
- 7:14:43корректно совершил оплату.
- 7:14:47Хорошо, я описал три хендлера. Первый,
- 7:14:49второй и третий. Первый хендлер я
- 7:14:52сказал, что будет у меня обрабатываться
- 7:14:54на паттернефолт. Хорошо, теперь я хочу
- 7:14:56добавить другие хендлеры. Точно также из
- 7:14:58пакета HTTP я вызываю функцию handle
- 7:15:00funk. в неё передаю паттерн и хендр.
- 7:15:03Хендлер я сначала повешу payхнler,
- 7:15:05который типа проводит у нас оплаты. Payх
- 7:15:08handler, а паттерн для его вызова будет
- 7:15:10сpay. О'кей. И теперь нам осталось
- 7:15:12сделать то же самое, только с и у меня
- 7:15:15будет cancel handler. Хорошо, казалось
- 7:15:18бы, я добавил три обработчика. Я сказал
- 7:15:20нашему языку программирования Goeng
- 7:15:22через вот этот встроенный пакет HTTP,
- 7:15:24что если нам придут с HTTP запросом по
- 7:15:27паттерну сдефолт, тогда вызови вот этот
- 7:15:29вот хендлер и обработай входящий сетевой
- 7:15:31запрос. Потом я сказал, что если к нам
- 7:15:33придут с HTTP запросом по паттерну Pay,
- 7:15:35тогда вызови вот этот вот хендлер и
- 7:15:37обработай сетевой запрос следующим
- 7:15:38образом. Ну, если к нам придут HTTP
- 7:15:40запросы по паттерну/ccel, тогда вызови
- 7:15:42вот этот вот обработчик и выполни код,
- 7:15:44который в нём описан. Ну хорошо, давайте
- 7:15:46теперь попробуем запустить нашу
- 7:15:47программу.
- 7:15:49пишу go runmain.go,
- 7:15:52программа просто завершается. И так
- 7:15:54ничего и не случилось. Почему так?
- 7:15:56Потому что здесь, в этих трёх строчках,
- 7:15:58я лишь задал обработчики, сам HTTP
- 7:16:01сервер я не запустил. Для того, чтобы
- 7:16:03мне запустить HTTP сервер, мне нужно в
- 7:16:05пакет HTTP опять обратиться и тут
- 7:16:07вызвать функцию list and serve. Что эта
- 7:16:10функция делает? Она запускает HTTP
- 7:16:12сервер и блокирует на этой строчке
- 7:16:14выполнение нашей программы, потому что
- 7:16:16эта функция listen and serve где-то там
- 7:16:17под капотом в бесконечном цикле, на
- 7:16:19самом-то деле, просто слушает входящие
- 7:16:21сетевые подключения. И если к нам пришло
- 7:16:23сетевое подключение на patтерn/default,
- 7:16:26она вызовет этот хендлер. Если у нас
- 7:16:28пришло входящее подключение по
- 7:16:29паттерну/pay, вызовет этотх handlerр.
- 7:16:31Если по паттернуcel, вот этот handхler.
- 7:16:33Давайте посмотрим, какие аргументы у нас
- 7:16:35принимает наша функция listen and serve.
- 7:16:37Первое - это какой-то адрес. Что это за
- 7:16:39адрес? При помощи этого адреса мы сможем
- 7:16:41с вами как раз-таки задать порт, на
- 7:16:43котором наше приложение будет слушать
- 7:16:45входящие подключения. Выглядит это
- 7:16:47следующим образом. Я в строке пишу
- 7:16:49двоеточие и тут 9.091. Таким образом, я
- 7:16:52сказал, что моё приложение должно быть
- 7:16:54запущено на 9.091 порту и обрабатывать
- 7:16:57входящее подключение на этом порту.
- 7:16:58О'кей. И какой-то есть ещё второй
- 7:17:00аргумент, какой-то хендр непонятный, но
- 7:17:02на самом деле это нужно для более тонкой
- 7:17:04настройки вот этого роутинга наших
- 7:17:06входящих подключений. Но так как мы уже
- 7:17:08это всё настроили благодаря функции
- 7:17:10handle funk, мы уже задали по каким
- 7:17:11паттернам какие хендлеры будут
- 7:17:13вызываться, нам тут ничего передавать не
- 7:17:14нужно, мы можем написать л. О'кей. Также
- 7:17:17смотрим listen and serve возвращает
- 7:17:18какую-то ошибку. Ну, ошибку просто так
- 7:17:20терять мы не можем, поэтому мы будем её
- 7:17:21получать. И в конце смотреть, что если у
- 7:17:23нас ошибка не равна, тогда вывести на
- 7:17:25экран произошла ошибка.
- 7:17:28И, собственно, выведем саму эту ошибку.
- 7:17:32Ну давайте теперь попробуем запустить
- 7:17:33нашу программу. Мы описали хендлеры
- 7:17:35какие-то. Мы сказали, по каким паттернам
- 7:17:37эти хендлеры будут вызываться. И мы
- 7:17:39запустили наш HTTP-сервер. Давайте я вот
- 7:17:41здесь вот буду даже писать
- 7:17:43запускаю
- 7:17:45HTTP
- 7:17:47в сервер.
- 7:17:49А вот здесь вот я буду писать,
- 7:17:52что программа закончила своё выполнение.
- 7:17:57Давайте запустим нашу программу. Опа,
- 7:17:59запускаю HTTP сервер. И ничего дальше не
- 7:18:02выводится. О чём это говорит? Это
- 7:18:03говорит, что мы вывели вот эту вот
- 7:18:05строчку на наш экран и потом
- 7:18:07заблокировались в пятидесятой строчке,
- 7:18:08потому что, напоминаю, listen and serve
- 7:18:10будет блокировать выполнение нашей
- 7:18:11программы, потому что он в бесконечном
- 7:18:13цикле будет слушать входящие в сетевые
- 7:18:15подключения. Ну а конкретно это Listen
- 7:18:17and Surf из пакета HTTP, поэтому
- 7:18:18обрабатывать он будет именно HTTP
- 7:18:20подключение. О'кей, мы с вами описали и
- 7:18:22запустили программу, написанную на
- 7:18:24Goang, которая слушает входящие сетевые
- 7:18:26подключения на 9.091 порту и
- 7:18:29предоставляет три различных хендлера по
- 7:18:31трём различным паттернам. Теперь нам
- 7:18:33остаётся делать запросы в эту нашу самую
- 7:18:36программу. А для этого мы воспользуемся
- 7:18:38Постманом. Вот слева наша программа,
- 7:18:40справа Postman. Я нажимаю здесь на
- 7:18:41плюсик и создаю новый запрос, именно
- 7:18:44описание его. Пока что я никуда этот
- 7:18:46самый запрос не отправляю. Postman
- 7:18:48довольно плохо отображается в режиме на
- 7:18:49полэкрана. Как вы видите, тут какие-то
- 7:18:51кнопки съедены, но вы знаете, что здесь
- 7:18:54кнопка есть Sent, вот эта большая синяя,
- 7:18:56которая как раз-таки отвечает за то,
- 7:18:57чтобы запрос отправить. Мы сначала
- 7:18:59запрос опишем, потом нажмём на кнопку
- 7:19:01Sent, и запрос отправится. О'кей,
- 7:19:02давайте начнём описывать наш запрос.
- 7:19:04Помним, что нам нужно для нашего
- 7:19:05сетевого запроса. Первое - это домен или
- 7:19:07IP-адрес. Ну, я воспользуюсь доменом
- 7:19:09local host. Вот я тут пишу local host.
- 7:19:12О'кей, я выбрал свой домен. То есть я
- 7:19:14буду отправлять запросы сам себе на
- 7:19:15компьютер на вот эту вот запущенную
- 7:19:17программку. Далее нам нужно выбрать
- 7:19:19порт, на который мы будем делать
- 7:19:20запросы. Ну, мы явно указали, что
- 7:19:22запускаем нашу программу на 9.091 порту,
- 7:19:25поэтому я буду передавать этот самый
- 7:19:27порт 90.91.
- 7:19:29И как мы выяснили, нам ещё нужно указать
- 7:19:32паттерн, по которому мы будем отправлять
- 7:19:34наш запрос. У нас здесь на выбор есть
- 7:19:36три разных паттерна. Я начну с первого.
- 7:19:38Отправлю запрос на сdefault. Тут я так и
- 7:19:41напишу с/default. Ну всё, теперь мы с
- 7:19:44вами вполне можем отправлять наш запрос.
- 7:19:46Я нажимаю кнопочку вот эту синюю, send.
- 7:19:49Опа. И я получил ответ в нашем постнее:
- 7:19:52Hello world. И вижу в выводах в консоле,
- 7:19:55что был обработан HTTP запрос. А у нас
- 7:19:57этот вывод, я корректно обработал HTTP
- 7:19:59запрос, он именно на хендлере, который
- 7:20:02висит на паттернефол. О'кей? То есть мы
- 7:20:04благодаря тому, что в Постмане написали
- 7:20:06домен local Local host, мы ему сказали
- 7:20:08делать запрос не на какой-то там
- 7:20:09удалённый сервер, на какое-то удалённое
- 7:20:11устройство, а на самих себя. Потом через
- 7:20:14два иточие мы указали конкретный порт,
- 7:20:16на который нужно сделать запрос. То есть
- 7:20:18у нас на компьютере множество разных
- 7:20:19портов есть какие-то заняты уже другими
- 7:20:21приложениями, какие-то порты заняты
- 7:20:23операционной системой. Мы сказали, что
- 7:20:24делаем запрос именно на 99.90. девяносто
- 7:20:27первый порт. Именно вот сюда вот.
- 7:20:29Хорошо, наш запрос дошёл до нашего с
- 7:20:31вами устройства, дошёл до нашего с вами
- 7:20:33приложения. Теперь осталось выбрать,
- 7:20:35какой именно хендлер внутри нашего
- 7:20:36приложения я хочу вызвать. Для этого я
- 7:20:38передал паттерфолult, тем самым выбрав
- 7:20:41не pay, не CCEL, а именно default
- 7:20:44handler, который у меня лежит на вот
- 7:20:46этом вот паттерне. И мы видим вывод:
- 7:20:49"Hello world. Хорошо, давайте попробуем
- 7:20:50отправить запрос не на сфолт, а на сPay.
- 7:20:53Вот по вот этому уже паттерну отправляем
- 7:20:56запрос. Новый платёж обработан. Я вижу
- 7:20:59другой ответ. Я вижу другой вывод в
- 7:21:01консоли. А это значит, что у меня уже
- 7:21:02вызвался не вот этот вот хендлер,
- 7:21:04который по паттерну default, а вот этот
- 7:21:06вот хендлер, который у нас типа для
- 7:21:07проведения оплат. О'кей. А если я сделаю
- 7:21:10запрос по паттерну? Здесь вот я пишу не
- 7:21:12сpay, а с отправляю. Оплата отменена. То
- 7:21:17есть мы вызвали уже вот этот вотх hand
- 7:21:19хнler handler. Тут у нас выявилось, что
- 7:21:21типа какая-то там оплата отменена. и
- 7:21:23вывод на консоль, что я корректно
- 7:21:25отменил оплату. То есть опять же прошу
- 7:21:26обратить внимание, что ту строку,
- 7:21:28которую мы с вами пишем в responsто,
- 7:21:31она идёт в http ответ. То есть это
- 7:21:33увидит HTTP клиент. В нашем случае это
- 7:21:35Postman. А то, что мы через FMT PrintLN
- 7:21:37выводим на экран, это по старинке просто
- 7:21:39выводится в консоль. Но это никак не
- 7:21:41идёт в HTTP ответ. Но я могу нас с вами
- 7:21:44поздравить. Мы с вами описали первый в
- 7:21:46нашей жизни HTTP- сервер, описали первые
- 7:21:48в нашей жизни HTTP хендлеры, которые
- 7:21:50обрабатывают входящее подключение,
- 7:21:52разобрали три части HTTP запроса,
- 7:21:54которые в нём есть. Первое - это куда
- 7:21:55делать запрос, на какое устройство.
- 7:21:58Второе - это на какой порт этого
- 7:21:59устройства делать запрос. И третье - это
- 7:22:01по какому паттерну отправлять данный
- 7:22:03запрос, то есть какой именно хендлер
- 7:22:05внутри конкретного приложения мы хотим
- 7:22:07вызвать. И вспоминаем, что HTTP
- 7:22:09предполагает всегда запрос и после него
- 7:22:12ответ. Вот этот самый ответ мы и
- 7:22:14получали в Постмане. Также хотел бы
- 7:22:16дополнить, что мы помимо того, чтобы
- 7:22:18регистрировать наши хендлеры на какой-то
- 7:22:20паттерн, можем регистрировать их на
- 7:22:22пустой паттерн. Как это выглядит? Ну,
- 7:22:24давайте я заново опишу какой-нибудь HTTP
- 7:22:26handler, пишу Fun, называю его handler.
- 7:22:28И тут в скобочках я напишу WR, и VS-код
- 7:22:31поддерживает следующий SNIP. Оп, и сразу
- 7:22:33же у меня мои response Writter и HTTP
- 7:22:35request появляются в аргументах функции.
- 7:22:37Сохраняю файл. Тем самым автоматически
- 7:22:39импортируется пакет Net HTTP. И у меня
- 7:22:41готов заголовок моего хендлера. В этом
- 7:22:43хендлере я просто буду сразу же
- 7:22:45записывать в response writer hello
- 7:22:46world. То есть ничего особо сложного.
- 7:22:49Прямо на месте создаю с BY, который
- 7:22:51создаётся из на месте созданной строки
- 7:22:54Hello World. Ну и буду получать на
- 7:22:57всякий случай ошибку. Вдруг что-то
- 7:22:58пойдёт не так. Если ошибка у меня не
- 7:23:00равно, тогда я выведу об этом информацию
- 7:23:02на экран. Вот так вот. И ошибку саму
- 7:23:05выведу, точнее её текст. И завершу
- 7:23:07выполнение этого хендлера. Иначе просто
- 7:23:09выведу на экран, что хендлер отработал
- 7:23:12успешно.
- 7:23:14Всё. После этого я захочу его
- 7:23:15зарегистрировать. Обращаюсь в пакет
- 7:23:17HTTP. Функция handle funk. И тут как
- 7:23:20раз-таки то, о чём я говорил. Значит,
- 7:23:22хенр - это наш хендлер, а паттерн,
- 7:23:24давайте обратим внимание, раньше мы с
- 7:23:26вами регистрировали паттерн что-то типа
- 7:23:28сpay или сcel или сfault, но паттерн
- 7:23:32может быть пустой. Пустой не значит, что
- 7:23:34пустая строка, а пустой значит, что у
- 7:23:36нас есть один лишь только слэш. Это так
- 7:23:37называемый корневой паттерн. Если вы
- 7:23:39обратите внимание, он очень часто
- 7:23:40встречается, ну, например, когда мы с
- 7:23:42вами заходим на YouTube, вот
- 7:23:43youtube.com. И по факту, когда мы
- 7:23:46заходим на youtube.com, то мы заходим
- 7:23:47вот на этот вот корневой паттерн. И
- 7:23:49потом уже в зависимости от того, что мы
- 7:23:50нажмём, у нас уже может идти запрос на
- 7:23:52какой-нибудь другой паттерн, там
- 7:23:53сtrending
- 7:23:55или с/music. Но изначально у нас идёт
- 7:23:58запрос на корневой паттерн. Ну и мы в
- 7:24:00своих приложениях также можем описывать
- 7:24:01хендлеры, которые будут обрабатывать
- 7:24:03запросы на вот этот корневой паттерн.
- 7:24:04Ну, давайте я запущу HTP сервер Listen
- 7:24:06and Surf и на 90.91 порту.
- 7:24:11Тут нам второй аргумент пока не нужен.
- 7:24:12Буду получать ошибку. И если у меня
- 7:24:14ошибка не равна, тогда выведу на экран
- 7:24:16эту самую ошибку.
- 7:24:19О'кей, давайте запустим нашу программу.
- 7:24:22Всё, запустился наш HTTP сервер.
- 7:24:25Программа ждёт входящих подключений. И
- 7:24:26давайте опишем новый HTTP запрос. Делаем
- 7:24:29мы его, конечно же, сами на свой же
- 7:24:30компьютер, потому что у нас эта
- 7:24:31программа развёрнута на нашем же
- 7:24:33компьютере. Поэтому домен пишу host.
- 7:24:35Порт у нас 90.91. И учитывая, что я хочу
- 7:24:38сделать запрос на вот этот вот корневой
- 7:24:39паттерн, мне тут писать, в принципе,
- 7:24:41больше ничего не нужно. Я делаю send и
- 7:24:43получаю Hello World. Получается, когда у
- 7:24:45меня какой-то хендлер висит на корневом
- 7:24:47паттерне, то я могу делать запросы
- 7:24:48просто под домену плюс порт, но сам
- 7:24:50паттерн указывать не нужно.
- 7:24:51Автоматически будет вызван именно вот
- 7:24:53этот вот пустой паттерн. Ещё вот эти вот
- 7:24:55самые паттерны, их чаще называют
- 7:24:57endпоинты. То есть вот это корневой
- 7:24:58endpint, это pay endpint, это cancel
- 7:25:01endpint, ну и так далее. Тут, в
- 7:25:03принципе, зависит от того, какая
- 7:25:04терминология принята конкретно вашей
- 7:25:06команде, но, в принципе, не будет ошибка
- 7:25:07назвать это паттерном, так же, как и не
- 7:25:09будет ошибка назвать это энпоинтом.
- 7:25:10Просто по большому счёту вкусовщина. Ну
- 7:25:12и второе, о чём бы я вам хотел
- 7:25:13рассказать - это то, что когда мы делаем
- 7:25:15запрос на наше бэкэнд приложение,
- 7:25:17написанное на Голенге, то автоматически
- 7:25:20под обработку каждого хендлера
- 7:25:22запускается отдельная грутина прямо под
- 7:25:24капотом. Нам для этого даже делать
- 7:25:25ничего не нужно. Чтобы это
- 7:25:26продемонстрировать, я пишу новый
- 7:25:28хендлер, назову его handler sleep.
- 7:25:31Конечно же, наш respons.
- 7:25:34И здесь я буду перед тем, как писать
- 7:25:36HTTP ответ, я просто скопирую вот этот
- 7:25:38вот код, вставлю его сюда, буду отвечать
- 7:25:41HTTP response, конечно же, буду
- 7:25:44проверять на ошибке, просто тоже
- 7:25:45скопирую этот код. И перед тем, как
- 7:25:47делать HTP respons, то есть перед тем,
- 7:25:48как отдавать HTTP ответ, я буду засыпать
- 7:25:50на 5 секунд.
- 7:25:53Таким вот образом. Ну, теперь давайте
- 7:25:55зарегистрируем этот хендр в нашем HTP
- 7:25:57приложении. HTTP handle Fun. И я его
- 7:26:00зарегистрирую на паттерне на
- 7:26:02endpинте/sliep.
- 7:26:03Вот он нашх handlerр. Handler сleep. Ну
- 7:26:05и давайте запустим нашу программу.
- 7:26:08Всё запустилось. И теперь давайте я
- 7:26:10попробую на pattern/sleep сделать
- 7:26:11одиночный HTP запрос. Посмотрим, что
- 7:26:13будет. Опа. И мы ждём. У нас ожидание,
- 7:26:16ожидание, ожидание. 5 секунд мы будем
- 7:26:18ждать. Оп, HTP респонс получили. Хорошо.
- 7:26:21Если бы у нас обработка вот этих вот
- 7:26:23хендлеров шла последовательно в одной
- 7:26:25рутине, если бы для каждого хендлера не
- 7:26:27запускалось дополнительных рутин, то в
- 7:26:29итоге человек, который бы делал запрос,
- 7:26:30например, вторым, он бы ждал сначала,
- 7:26:32пока 5 секунд обработается запрос
- 7:26:34первого человека и потом только 5 секунд
- 7:26:36ждал бы свой запрос. То есть уже бы 10
- 7:26:37секунд ждал. Но для того, чтобы
- 7:26:39продемонстрировать, что это на самом
- 7:26:40деле не так, что у нас на самом деле
- 7:26:41обработка каждого отдельного хендлера
- 7:26:43запускается в дополнительной рутине, я
- 7:26:45опишу ещё один такой же запрос и
- 7:26:47отправлю их параллельно. Сначала этот,
- 7:26:49потом сразу же вот этот. Ну, давайте тут
- 7:26:51я нажимаю send и тут тоже send. Смотрим,
- 7:26:53у меня ожидание происходит ответа.
- 7:26:55Ожидание ответа. Ожидание ответа. Оп. И
- 7:26:57всё. И одновременно нам приходят ответы.
- 7:26:59Почему? Да потому что, опять же, это всё
- 7:27:01происходит в разных гарутинах. Под
- 7:27:02каждый хендлер будет под капотом
- 7:27:04запускаться дополнительная рутина. Это
- 7:27:06ещё одно из больших преимуществ голенга,
- 7:27:07то, что мы, когда обрабатываем самые
- 7:27:09наши базовые HTTP запросы, нам не нужно
- 7:27:11как-то руками запускать дополнительные
- 7:27:12рутины. Это удобно. Значит ли это, что
- 7:27:14нам в принципе никогда больше не
- 7:27:15понадобится руками запускать рутины?
- 7:27:17Нет. Просто именно пакет NET HTTP под
- 7:27:20капотом. Каждый раз, когда он влиснесёр
- 7:27:22будет принимать какое-то входящее HTTP
- 7:27:23подключение и запускать отдельный хендр
- 7:27:26для того, чтобы обработать это самое
- 7:27:27подключение, то хендлер будет
- 7:27:29запускаться в отдельной рутине для
- 7:27:30ускорения обработки клиентских запросов.
- 7:27:33[музыка]
- 7:27:38Далее мы с вами наконец-таки готовы
- 7:27:40узнать про HTTP ещё кое-что. И да, я вас
- 7:27:43немножко обманул, когда сказал, что в
- 7:27:45протоколе HTTP нам нужно знать всего
- 7:27:48лишь про домен или IP-адрес, про порт и
- 7:27:50про паттерн. На самом деле нет. На самом
- 7:27:52деле для того, чтобы полноценно
- 7:27:54пользоваться HTTP протоколом, нам
- 7:27:55необходимо знать ещё про вот эти вот
- 7:27:57сущности. Мы их сейчас разберём каждое
- 7:27:59последовательно. Давайте посмотрим, что
- 7:28:01тут вообще есть. Тело запроса, тело
- 7:28:04ответа, статус код ответа, какие-то
- 7:28:06хедеры и HTTP метод запроса. Тоже
- 7:28:08непонятно, что такое. Ну, как я и
- 7:28:10сказал, будем разбирать одно за другим
- 7:28:11по очереди. Первое, что мы разберём -
- 7:28:13это тело запроса. Узнаем, что это за
- 7:28:15тело запроса, для чего оно нужно и как
- 7:28:17используется. Для того, чтобы понять,
- 7:28:19что такое тело HTTP запроса и для чего
- 7:28:21оно нам нужно, я предлагаю рассмотреть
- 7:28:23следующий пример. Мы с вами сейчас
- 7:28:25напишем очень простое, максимально
- 7:28:27упрощённое энд приложение управление
- 7:28:30кошельком. У нас изначально будет дан
- 7:28:32какой-то наш денежный баланс, и лежать
- 7:28:34эти деньги будут в переменной мане.
- 7:28:35Также у нас будет дана переменная банк,
- 7:28:37то есть это будет наша копилка. Что
- 7:28:39вообще будет уметь наш кошелёк? Мы будем
- 7:28:42предоставлять два хендлера, с помощью
- 7:28:43которых наши клиенты смогут ходить в
- 7:28:45ээкэнд приложение по сети и совершать
- 7:28:47какие-то действия со своими денежными
- 7:28:49средствами. Ну и так как у нас, то нам,
- 7:28:51в принципе, неважно, кто будет к нам
- 7:28:53ходить с сетевыми запросами. Будь то
- 7:28:55какой-нибудь веб-сайт или будь то
- 7:28:57какое-нибудь десктопное приложение, либо
- 7:28:59будь то мобильное приложение. Нам
- 7:29:01неважно, у нас просто эээнд, мы
- 7:29:02описываем свои хендлеры, а потом уже кто
- 7:29:05может, тот и отправляет нам свои HTTP
- 7:29:06запросы, которые мы будем как-то
- 7:29:07обрабатывать. Какие же хендлеры будет
- 7:29:09предоставлять нашекэнд приложение?
- 7:29:11Первый хендлер будет располагаться по
- 7:29:13паттерну и будет позволять пользователю
- 7:29:15что-то купить. Ну, в нашем упрощённом
- 7:29:17приложении покупка чего-то будет просто
- 7:29:20выводом на экран: "Я совершаю покупку на
- 7:29:21столько-то долларов". И нам нужно будет
- 7:29:23уменьшить нашу переменную манию. Ну, то
- 7:29:25есть мы сделали какую-то покупку,
- 7:29:27соответственно, мы потратили деньги,
- 7:29:28соответственно, у нас денег стало
- 7:29:29меньше, переменная мани уменьшилась.
- 7:29:31Второй хендлер будет располагаться по
- 7:29:33паттерну сsave и будет служить для того,
- 7:29:35чтобы отложить имеющиеся у нас деньги.
- 7:29:37Получается, если у нас осталось, к
- 7:29:39примеру, 500 долларов, мы можем сделать
- 7:29:41слшve. И какую-то часть из этих 500
- 7:29:43долларов мы сможем сохранить в нашу
- 7:29:45копилку. Ну хорошо, давайте начнём
- 7:29:46описывать это приложение и посмотрим,
- 7:29:48как оно вообще выглядит. И причём тут
- 7:29:50тело запроса. О'кей, у нас пустое
- 7:29:52приложение. Поехали описывать. Значит,
- 7:29:53помним, у нас есть две переменные.
- 7:29:55Первая переменная - это переменная
- 7:29:57маoney. Пускай изначально у нас будет
- 7:29:591.000 долларов. И вторая переменная -
- 7:30:01это у нас банк. То есть это наше
- 7:30:02сбережение изначально. Пускай тут будет
- 7:30:040 долларов, то есть пустая копилка, по
- 7:30:06сути. Хорошо. опишем два наших хендлера.
- 7:30:08Первый хендлер - это у нас будет payх
- 7:30:09handler.
- 7:30:14Второй хендлер. Второй хендлер - это у
- 7:30:15нас save handler, то есть хендлер для
- 7:30:17того, чтобы сохранить какие-то деньги в
- 7:30:19копилку, которые у нас есть. Перенести
- 7:30:21что-то из переменной мани в переменную
- 7:30:22нашей копилки. Ну то есть сохранить
- 7:30:24деньги. Хорошо, давайте зарегистрируем
- 7:30:26эти два хендлера. Регистрирую первый хдр
- 7:30:28http.handlefunk
- 7:30:30на паттерне/pay. Это у меня payх
- 7:30:32handler. Потом регистрирую второй хлеer
- 7:30:35http handle funk. Только у меня в этот
- 7:30:36раз уже будет слшve и это у меня save.
- 7:30:40Хорошо. После этого мы с вами запустим
- 7:30:42наш HTTP сервер на 90.91 порту. Второй
- 7:30:46аргумент нам сейчас не нужен. Передаём
- 7:30:48NILIL и будем потом смотреть, что если у
- 7:30:49нас какая-то ошибка произошла во время
- 7:30:52запуска нашего HTTP сервера, тогда я так
- 7:30:53и выведу на экран http север Eror. И вот
- 7:30:56она наша ошибка, точнее её текст.
- 7:30:58Хорошо. Но теперь встаёт такой вот
- 7:31:01интересный вопрос. Хорошо, мы умеем
- 7:31:02принимать HTTP запросы на Patter/pay на
- 7:31:04нашем 90.091 порту. Двоеточи тут забыл
- 7:31:07добавить обязательно. Но когда мы примем
- 7:31:09наш HTTP запрос, вот, допустим, в
- 7:31:11Пейхенре, когда мы захотим за что-то
- 7:31:13заплатить, как мне понять, на сколько
- 7:31:15денег я совершаю оплату? А никак, потому
- 7:31:18что у нас в HTTP запросе никакой
- 7:31:20информации дополнительно нету о том, что
- 7:31:22мы вообще именно покупаем, на сколько
- 7:31:24долларов мы совершаем покупку, вообще
- 7:31:26ничего нету, никакой дополнительной
- 7:31:28информации. Всё, что мы знаем - это то,
- 7:31:29что кто-то нам прислал HTTP запрос. на
- 7:31:31Patter/pay. То есть как бы наш клиент
- 7:31:33захотел как-то за счёт нашего приложения
- 7:31:35произвести какую-то покупку. Но как нам
- 7:31:37понять вообще, насколько именно долларов
- 7:31:39эта покупка у нас совершается? Для этого
- 7:31:41как раз-таки и существует тело запроса.
- 7:31:43Давайте посмотрим. Я создаю новый HTTP
- 7:31:46запрос, описываю его. То, что я хочу
- 7:31:47делать запрос на local host 2.9091/pay.
- 7:31:52Делаю запрос на local host на 901 порт
- 7:31:55на паттерн на endpint/pay. Хорошо. И
- 7:31:58теперь я нажимаю вот здесь вот кнопочка
- 7:32:00bodyди. И тут в выпадающем меню я
- 7:32:03выбираю RA пабам. Теперь я могу в моём
- 7:32:07HTTP запросе какой-то дополнительный
- 7:32:09текст передавать. И я в этом тексте буду
- 7:32:11передавать количество долларов, которое
- 7:32:13я хочу списать во время моей оплаты.
- 7:32:16Допустим, это будет 100 долларов.
- 7:32:17Хорошо. Теперь я со стороны нашего
- 7:32:19пейхенра должен каким-то образом
- 7:32:21прочитать вот это вот тело входящего
- 7:32:23HTTP запроса. Где мне вообще его взять?
- 7:32:26А мы помним, что у нас помимо респонс
- 7:32:27wрайтера в аргументы нашего хендлера
- 7:32:30приходит ещё и HTTP requкст. Как
- 7:32:32раз-таки вот эта вот переменная, вот
- 7:32:33этот вот аргумент HTTP реквеста и
- 7:32:35содержит вообще всю информацию о
- 7:32:37входящем HTTP запросе, в том числе его
- 7:32:40тело. Как нам это тело вычитать? Давайте
- 7:32:42я сначала напишу, потом объясню. Я
- 7:32:44обращаюсь к пакету Ио и в нём к функции
- 7:32:46read. Эта функция принимает какой-то
- 7:32:48readдер. Давайте посмотрим, что такое
- 7:32:49reader. Reader - это какой-то интерфейс,
- 7:32:51у которого есть только лишь метод read,
- 7:32:53и он принимает сй by. На самом деле это
- 7:32:55работает таким образом, что мы в этот
- 7:32:56readдер можем передать в медрид с Bye. И
- 7:32:59нам этот сй байт наполнится какой-то
- 7:33:02информацие, которую мы хотим прочитать.
- 7:33:03На самом деле это всё может довольно
- 7:33:04неочевидно работать под капотом. Если
- 7:33:06кому интересно, можете посидеть на
- 7:33:07досуге, поразбираться, про это почитать.
- 7:33:09Но обычно об этом не сильно запариваются
- 7:33:10и читают тело запроса следующим образом.
- 7:33:12В этот. Я передаю R.B.
- 7:33:16Таким образом, я беру из входящего HTTP
- 7:33:18запроса его тело и при помощи RDOL
- 7:33:21функции пакета ЕО вычитываю всё это
- 7:33:23тело. Нам Red функция возвращает два
- 7:33:25аргумента. Первый - это сй - это то
- 7:33:27самое HTTP request BY, то есть тело
- 7:33:30входящее в HTP запроса. Ну и второй
- 7:33:32аргумент - это ошибка, которая может
- 7:33:34возникнуть при попытке прочитать это
- 7:33:35самое входящее тело запроса. Ну давайте
- 7:33:38будем проверять, что если у нас ошибка
- 7:33:39не new, тогда выведу об этом информацию
- 7:33:41на экран. Fail to read http body.
- 7:33:45Ну и текст ошибки тоже выведу. Ну и
- 7:33:47завершу наш хендлер. Ну, также хотел
- 7:33:49сказать, что во время вывода ошибки на
- 7:33:51экран вручную вызывать у неё метод ир,
- 7:33:53чтобы получить текст ошибки, это лишнее,
- 7:33:55потому что printlнn функция из пакета
- 7:33:57FMT сделает это за нас. Поэтому тут, в
- 7:33:59принципе, можно не вызывать вот этот вот
- 7:34:01метод Erро, он будет вызван
- 7:34:02автоматически. Тут может возникнуть
- 7:34:03вполне резонный вопрос, почему вообще мы
- 7:34:06из постмана отправляем в теле нашего
- 7:34:09HTTP запроса чиселку просто 100 или мы
- 7:34:12можем отправить какую-нибудь строку, но
- 7:34:14внутри нашего пйхенра мы вычитываем
- 7:34:17какой-то слайс байт. Вообще слайс байт -
- 7:34:20это по сути своей набор нулей единиц,
- 7:34:22там 1 11 1
- 7:34:25и так далее. То есть какой-то рандомный
- 7:34:27набор байт. Напоминаю, для тех, кто не
- 7:34:29помнит, 1 байт - это у нас 8 бит. А бит
- 7:34:33- это либо ноль, либо один.
- 7:34:35Соответственно, в одном байте у нас
- 7:34:36содержится восемь нулей единиц
- 7:34:38как-нибудь вот так вот. А в слайсе байт
- 7:34:41у нас содержится много-много разных вот
- 7:34:44таких вот значений. Таким образом, у нас
- 7:34:45в слайсе байт передаётся просто какое-то
- 7:34:47большое количество нуле единиц. Почему
- 7:34:49вдруг внезапно, если мы из постмана
- 7:34:51отправляли нормальную строку или
- 7:34:53нормальное число, нам пришёл сй байт?
- 7:34:55Вызвано это тем, что для того, чтобы
- 7:34:57передать информацию изнутри постмана в
- 7:34:59наше ээ приложение, эта самая информация
- 7:35:02вместе с HTTP запросом должна пройти
- 7:35:05свой путь по сети. Даже несмотря на то,
- 7:35:07что у нас и Postman, и Goeng приложения
- 7:35:09развёрнуты на одном компьютере, всё
- 7:35:11равно для того, чтобы сделать запрос с
- 7:35:13Постмана на это GL приложение, запрос
- 7:35:16должен быть сетевой. Пройти этот запрос
- 7:35:18должен по сети. Несмотря на то, что мы
- 7:35:20делаем сетевой запрос сами в себя,
- 7:35:22получается, неважно, делаем мы запрос на
- 7:35:23какой-то удалённый сервер, либо делаем
- 7:35:25мы запрос сами в себя. Если это запрос
- 7:35:27сетевой, то мы должны передавать
- 7:35:29информацию, которую мы передаём в
- 7:35:31запросе по сети. А самый быстрый,
- 7:35:33надёжный и оптимальный способ передавать
- 7:35:35данные по сети - это передавать данные в
- 7:35:38двоичном виде. Получается, если мы в
- 7:35:39Постмане описываем какие-то данные и
- 7:35:41потом отправляем их в теле HTTP запроса,
- 7:35:44эти данные автоматически преобразуются в
- 7:35:46какие-то нули единицы и потом уже будут
- 7:35:48отправляться по сети. Опять же, почему?
- 7:35:50Потому что это наиболее надёжно, это
- 7:35:52наиболее быстро и наиболее
- 7:35:54производительно. Вспоминаем, да, вообще
- 7:35:56какой длинный путь может скрываться под
- 7:35:58обычным HTTP запросиком. С телефона на
- 7:36:00роутер, по проводам на сотовую вышку, по
- 7:36:02воздуху на телефон и обратно. Это на
- 7:36:05самом деле очень большой длинный путь. и
- 7:36:06передавать какие-то сложные данные по
- 7:36:08сети, учитывая, какой длинный может быть
- 7:36:10путь, это совсем небезопасно, не
- 7:36:12оптимально. Данные могут во время
- 7:36:14передачи по сети повредиться. А вот если
- 7:36:16представить эти самые данные в виде
- 7:36:17набора нулей единиц, тогда это всё
- 7:36:19становится быстрее, это становится проще
- 7:36:21и надёжнее. Именно поэтому, когда мы в
- 7:36:24Постмане отправляем какую-то информацию
- 7:36:25в теле HTTP запроса, то нам в наше
- 7:36:27гошное приложение приходит именно слайс
- 7:36:29Bйт, то есть набор этих нулей единиц. И
- 7:36:31потом мы уже с этими нулями единицами
- 7:36:33можем внутри нашего приложения начинать
- 7:36:35работать. преобразовывать их уже в
- 7:36:37понятные нам структуры данных, в
- 7:36:39понятные нам типы переменных и дальше
- 7:36:41уже с этими переменными структурами
- 7:36:42данных работать. И в будущем я вас
- 7:36:44попрошу этого факта не пугаться и этот
- 7:36:46факт учитывать, что вся информация по
- 7:36:47сети конкретно при использовании
- 7:36:49протокола HTTP, неважно, то запрос
- 7:36:52клиента на сервер либо ответ сервера на
- 7:36:54клиент, вся эта информация, она
- 7:36:55передаётся в двоичном виде, в виде
- 7:36:57нулей, единиц. Но для нас это не
- 7:36:59проблема. Мы после того, как на стороне
- 7:37:00нашего Golden HTTP сервера примем эти
- 7:37:03нули единицы, мы их просто преобразуем в
- 7:37:05нужные нам данные. Сейчас мы с вами
- 7:37:06посмотрим, как это вообще делается.
- 7:37:08Опять же, казалось бы, неудобно, странно
- 7:37:09и непонятно, но взамен мы получаем
- 7:37:11скорость и надёжность. А это в вопросах,
- 7:37:13которые касаются сетевого
- 7:37:14взаимодействия, одни из самых главных
- 7:37:16параметров. Нам нужно, чтобы запрос
- 7:37:18точно дошёл, чтобы он дошёл в целости и
- 7:37:20сохранности и чтобы он дошёл максимально
- 7:37:22быстро. О'кей, мы в переменную HTTP
- 7:37:25request body получили слайс, то есть
- 7:37:27какое-то байтовое представление
- 7:37:29входящего тела запроса. Теперь мы хотим
- 7:37:31каким-то образом перевести его в число.
- 7:37:33Для этого есть множество разных
- 7:37:34способов. Ну давайте я покажу, наверное,
- 7:37:36один из таких самых понятных. Сперва мы
- 7:37:38преобразуем вот этот вот слай by,
- 7:37:40который представляет тело входящего HTP
- 7:37:41запроса просто в строку. Сделаю
- 7:37:43переменную HTTP request body string и
- 7:37:46положу в неё слайс входящий, который мы
- 7:37:49преобразуем в строку. Потом эту строку я
- 7:37:51преобразую в число при помощи пакета
- 7:37:53strв.
- 7:37:55И тут есть функция в этом пакете а то и,
- 7:37:58то есть это ask integer. По сути, мы
- 7:38:00передаём строку и она нам будет
- 7:38:01преобразована в какое-то число. Ну и
- 7:38:03также мы опциональную ошибку будем
- 7:38:04получать на случай, если не получилось
- 7:38:06по каким-то причинам строку преобраз.
- 7:38:08Соответственно, передаём HTP request
- 7:38:10body string. То есть, повторюсь, опять
- 7:38:12же, нам в запросе было передано какое-то
- 7:38:14тело. Мы в четырнадцатой строчке это
- 7:38:16самое тело вычитали, но оно у нас
- 7:38:18представлено массивом BТ. Потому что, в
- 7:38:20принципе, по сети оптимальнее всего
- 7:38:22передавать информацию именно в байтах.
- 7:38:23Это быстрее и эффективней. Потом мы этот
- 7:38:25слайс байт в двадцатой строчке
- 7:38:27преобразуем в строку. Теперь мы даже
- 7:38:29можем вывести это на экран и получить
- 7:38:30что-то осмысленное. После этого мы
- 7:38:32данную строку преобразуем в число для
- 7:38:35того, чтобы мы могли из нашей переменной
- 7:38:36мани вычесть значение, которое было
- 7:38:38передано нам в HTTP теле запроса.
- 7:38:40Хорошо, будем получать это самое число.
- 7:38:42Я назову это payment amount. Ну и будем
- 7:38:45получать ошибку, которая может
- 7:38:46возникнуть при переводе входящего тела
- 7:38:48запроса в число. Хорошо, если у нас
- 7:38:50ошибка не new, то есть если была
- 7:38:52какая-то ошибка, я выведу это на экран.
- 7:38:54Fail to convert http body to integer. И
- 7:38:59буду выводить, собственно, ошибку саму
- 7:39:00на экран. Ну и после этого завершу наш
- 7:39:02хендлер. Хорошо. У нас, допустим, всё
- 7:39:04отлично. Мы из входящего HTTP запроса
- 7:39:06прочитали его тело, перевели его в
- 7:39:08строку, строку перевели в число,
- 7:39:10получили число, на которое мы хотим
- 7:39:11списать наши деньги. Ну и после этого я
- 7:39:13буду деньги, собственно, списывать.
- 7:39:14Писать money минус равно payment amount.
- 7:39:16Хорошо. Тут, на самом деле, очень много
- 7:39:18моментов, которые, по сути, нужно
- 7:39:19править, но давайте будем разбирать их
- 7:39:21последовательно. Помните, что я говорил,
- 7:39:23что каждый раз, когда нам будет
- 7:39:24приходить какой-то HTTP запрос и
- 7:39:25вызываться хендлер, этот самый хендлер
- 7:39:27будет вызываться в отдельной рутине. А
- 7:39:29что это значит? Это значит, что мы,
- 7:39:31когда работаем внутри пейхендлера, мы,
- 7:39:32по сути, работаем в отдельной рутине. А
- 7:39:34мы обращаемся к глобальной переменной,
- 7:39:36которая объявлена в мейгарутине. А это
- 7:39:38значит, что в двадцать восьмой строчке у
- 7:39:39нас потенциально может возникнуть гонка
- 7:39:41данных. А это значит, что нам нужно либо
- 7:39:43переменную маoney обкладывать мьютексом,
- 7:39:45либо просто использовать тут атомики.
- 7:39:46Ну, я тут воспользуюсь вторым вариантом,
- 7:39:48то есть атомиками, потому что он тут
- 7:39:49наиболее подходит. То есть, если у нас
- 7:39:51стоит выбор между тем, чтобы
- 7:39:52использовать,
- 7:39:54который покрывает какую-то переменную
- 7:39:57следующим образом, то есть мы будем
- 7:39:58брать блокировку, потом изменять
- 7:39:59значение переменной, потом отпускать
- 7:40:01блокировку. Если выбор стоит между этим
- 7:40:03и между тем, чтобы просто взять атомик и
- 7:40:06использовать его через методы, то,
- 7:40:09конечно, нужно выбирать атомик, потому
- 7:40:10что это будет быстрее. Тек - это
- 7:40:12довольно сложная структура. Блокировка -
- 7:40:14это довольно недешёвая операция,
- 7:40:16разблокировка тоже, а Атомик с этим
- 7:40:18гораздо быстрее справится. Поэтому, если
- 7:40:19нам просто нужно взять целочисленную
- 7:40:21переменную и её конкурентно безопасно
- 7:40:23менять, тогда нам атомики подойдут
- 7:40:24гораздо лучше. Повторюсь, потому что это
- 7:40:26тупо быстрее. Мьютексы же нужно
- 7:40:28использовать тогда, когда атомиками мы
- 7:40:29уже не можем ограничиться. Ну, из таких
- 7:40:31примеров, когда нам атомиков не хватает,
- 7:40:33это когда мы с вами производим
- 7:40:34параллельные операции над слайсом. То
- 7:40:36есть у нас нету атомика для слайса,
- 7:40:37поэтому уже нужно использовать мьюксы. А
- 7:40:40так просто менять конкурентно безопасное
- 7:40:41значение одной переменной гораздо лучше
- 7:40:43через атомики, потому что мьютексы тупо
- 7:40:45дольше. Просто этот код будет работать
- 7:40:46медленнее, чем вот этот. Атомики
- 7:40:48выигрывают скоростью. Зачем нам лишний
- 7:40:50вхд на мьютексах, если нам тут атомики
- 7:40:52подойдут более чем. Atomic. int.
- 7:40:56Ну, изначально мы не можем, создавая
- 7:40:57атомиic, какое-то значение туда
- 7:40:58положить. Поэтому я в переменную маoney
- 7:41:00значение буду класть в самом начале
- 7:41:02нашей программы, то есть в самом начале
- 7:41:03функции main. Я буду вызвать Money, ed
- 7:41:06сюда передавать 1.000. Таким образом я в
- 7:41:08вот этот Atomic Money буду класть в
- 7:41:09самом начале выполнения нашей программы
- 7:41:111.000 долларов. Хорошо. И заместо этой
- 7:41:13двадцать девятой строчки я буду писать
- 7:41:14money тоed-
- 7:41:16payment amount. Двадцать девятую строчку
- 7:41:19можно удалить. Нам тут редактор кода
- 7:41:21жалуется, что он не может использовать
- 7:41:22int там, где ожидалось int, но мы уже, в
- 7:41:25принципе, умеем с этим работать. Я
- 7:41:27просто здесь вот сделаю in64, и всё
- 7:41:29будет отлично. Просто переведу вот этот
- 7:41:30вот integer в in64. Хорошо. Почему я в
- 7:41:33метод, что переводится как добавить,
- 7:41:35передаю минус? Ну, потому что у нас у
- 7:41:36атомика нету метода минус или что-то
- 7:41:38такое. Есть только метод. Получается,
- 7:41:40если я хочу от начальной переменной мани
- 7:41:42отнять payment amount, который нам был
- 7:41:44передан в теле HTP запроса, я тут
- 7:41:46поставлю знак минус, и тогда я прибавлю
- 7:41:48минус payment amount, то есть отниму
- 7:41:50этот самый payment аount. Ну и казалось
- 7:41:51бы, всё круто, но, наверное, я не хотел
- 7:41:53бы уходить в минус, да, потому что было
- 7:41:55бы странно, если у меня там баланс 10
- 7:41:57долларов, а я делаю покупку на 500
- 7:41:59долларов. Я так сделать не могу. Поэтому
- 7:42:01перед тем, как фактически совершать
- 7:42:02покупку, я буду проверять, что если у
- 7:42:04меня маoney тоlot, а тем самым я получу
- 7:42:06значение из переменной маoneни - in64
- 7:42:09payment amount а amount больше либо
- 7:42:10равно нуля, только тогда я смогу
- 7:42:13фактически совершить вот эту вот самую
- 7:42:14оплату. Почему я в условии перевожу
- 7:42:16payment amount в N64? Да потому что
- 7:42:18функция load нам тоже возвращает N64.
- 7:42:20Если я тут не буду переводить payment
- 7:42:22amount в N64, тогда мне будет сказано,
- 7:42:24что я не могу находить разницу между
- 7:42:26двумя разными типами переменных. Мне
- 7:42:27нужно всё это приводить к одному типу.
- 7:42:29Ну и, соответственно, тут я перевожу это
- 7:42:30к 64. О'кей. Тут бы я ещё, наверное,
- 7:42:33хотел после оплаты сразу же выводить на
- 7:42:35экран. Оплата прошла успешно. И через
- 7:42:37двоеточие новое значение переменной
- 7:42:39маoney. Money.lad. Но, казалось бы, всё
- 7:42:41хорошо. Мы умеем читать входящее тело
- 7:42:42HTTP запроса, мы умеем преобразовывать
- 7:42:44его вджер, и мы будем списывать деньги
- 7:42:47только, если у нас хватает на это
- 7:42:49баланса. Ну, вроде бы всё круто. Теперь
- 7:42:51давайте посмотрим на наш safe handler.
- 7:42:52Когда к нам будет приходить запрос по
- 7:42:54нашему паттернуsave, мы всё, что будем
- 7:42:57знать - это то, что кто-то хочет
- 7:42:59сохранить какое-то количество денег,
- 7:43:01какое-то количество денег перевести из
- 7:43:03мани в копилку. Ну как нам узнать,
- 7:43:05сколько именно денег в копилку клиент
- 7:43:07хочет перенести? А тут нам точно так же,
- 7:43:09как и в случае с оплатой, поможет тело
- 7:43:11входящего HTTP запроса. Мы со стороны
- 7:43:13клиента в этом теле сможем обозначить,
- 7:43:15сколько именно долларов я хочу перенести
- 7:43:17в копилку. А со стороны нашего
- 7:43:18сейфендлера мы сможем вычитать это
- 7:43:20значение и узнать, сколько именно
- 7:43:21долларов в копилку хочет наш клиент
- 7:43:23перевести. Ну, по сути, алгоритм тот же
- 7:43:25самый, что и в Payхandре. Сначала нам
- 7:43:27нужно прочитать HTTP request body. http
- 7:43:29request body. Это у нас eo.
- 7:43:33И сюда я передаю R. Нам показывает, что
- 7:43:36что-то не так, потому что Redol помимо
- 7:43:38слайса байт возвращает также ещё и
- 7:43:39ошибку.
- 7:43:41Буду смотреть, что если у меня ошибка
- 7:43:42неравна, значит, произошла какая-то
- 7:43:44ошибка во время попытки прочитать тело
- 7:43:46входящего HTP запроса. Ну и выведу об
- 7:43:48этом сообщение на экран. Fail to read
- 7:43:50http request body. Ну и вот она наша
- 7:43:53ошибочка. И буду завершать хендлер. Ну
- 7:43:55потому что дальнейшее выполнение
- 7:43:56хендлера, оно бесполезно. Если я не смог
- 7:43:58прочитать HTTP тело, не смог прочитать
- 7:44:00вот эти вот байты, ну дальше вообще я не
- 7:44:02смогу ничего сделать. Я не знаю, сколько
- 7:44:03мне нужно денег сохранить в копилку.
- 7:44:05Дальше так же самое HTP request body
- 7:44:07string, то есть буду преобразовывать
- 7:44:09входящий слайт в строку
- 7:44:12и потом преобразовывать полученную
- 7:44:13строку в число. Это у меня будет
- 7:44:14saveount, то есть количество долларов,
- 7:44:16которые я хочу сохранить и положить в
- 7:44:18копилку. STC convв, а той как раз-таки
- 7:44:21функция из пакета STC ков, которая
- 7:44:22преобразует строку в число. Строка - это
- 7:44:24наша HTP request body string. И так же
- 7:44:26само буду смотреть, что если ошибка
- 7:44:27неравно. Вот давайте подумаем, в каких
- 7:44:29случаях тут вообще может быть ошибка. Да
- 7:44:31если нам тут в строку передадут не
- 7:44:33какое-нибуд число в http запросе со
- 7:44:35стороны клиента, а, например,
- 7:44:36какую-нибудь такую фигню, но мы её никак
- 7:44:38не сможем перевести в число, а значит,
- 7:44:39будет ошибка. Ну, а если у нас тут
- 7:44:41корректное число передано, то мы его
- 7:44:42спокойно получим. Ну, а если тут
- 7:44:44какая-то ошибочка всё-таки была, то
- 7:44:45выведу об этом информацию на экран. Fail
- 7:44:48to convert http body to int. Ну, то есть
- 7:44:52to integer, и через двой точа вот саму
- 7:44:54эту ошибочку. После этого я завершаю
- 7:44:56выполнение хендлера save, потому что
- 7:44:58если вдруг у меня возникла ошибка во
- 7:44:59время перевода входящего тела запроса в
- 7:45:01число, это значит, что я не смог
- 7:45:03получить то количество долларов, которое
- 7:45:05клиент хочет сохранить в копилку. А
- 7:45:07значит, я опять не знаю, сколько он
- 7:45:08вообще хочет там что сохранить. Это мне
- 7:45:10говорит о том, что я не могу выполнять
- 7:45:12дальше свой хендлер. Я ничего полезного
- 7:45:14уже точно не сделаю, потому что я не
- 7:45:15знаю, сколько денег, собственно, нужно
- 7:45:16перекладывать в копилку. О'кей? Но,
- 7:45:18допустим, ошибки нету, и мы идём дальше.
- 7:45:20Дальше нам нужно произвести, по сути,
- 7:45:22две операции. То есть мы уже знаем,
- 7:45:23сколько именно клиент хочет положить в
- 7:45:24копилку. Нам нужно сначала эти деньги
- 7:45:27взять из кошелька. Потом эти деньги нам
- 7:45:30нужно положить в копилку. Ну, в
- 7:45:32принципе, всё понятно. То есть мы берём
- 7:45:33и из нашего кошелька, то есть маoney, мы
- 7:45:36берём эти деньги. То есть минус save
- 7:45:38amount. Ну, по сути, у нас сейчас опять
- 7:45:40будет эта самая ошибка, что мы в метод
- 7:45:43Мьютекса, метод передаём чиселку. Там
- 7:45:46ожидается int 64, а мы передаём самый
- 7:45:48обычный int, поэтому оборачиваем это всё
- 7:45:50в intрнули.
- 7:45:52То есть это мы взяли деньги с кошелька.
- 7:45:54После этого нужно эти деньги положить в
- 7:45:56копилку. Мы берём и в нашу переменную
- 7:45:58банк прибавляем количество денег,
- 7:46:00которые мы взяли из кошелька. Ну, то
- 7:46:01есть то количество денег, которое
- 7:46:03передал нам пользователь в своём HTTP
- 7:46:04запросе. О'кей. Но тут опять может
- 7:46:06возникнуть ситуация, что у нас на
- 7:46:07балансе, к примеру, 100 долларов, а
- 7:46:09пользователь захотел положить себе в
- 7:46:11копилку 500. Типа мы же не можем
- 7:46:12положить себе в копилку 500 долларов,
- 7:46:14если у нас всего как бы в кошельке 100
- 7:46:15долларов. Ну нам откуда ещё 400 долларов
- 7:46:17взять? Нет, мы не можем. Поэтому нужно
- 7:46:19делать проверки, что если у меня money
- 7:46:21точкало lotт, то есть если то количество
- 7:46:23денег, которое у меня лежит в кошельке,
- 7:46:24больше либо равно save amount, то есть
- 7:46:26если у меня фактически в кошельке либо
- 7:46:28больше, либо столько же денег, сколько
- 7:46:30пользователь хочет положить себе в
- 7:46:32копилку, тогда и только тогда я смогу
- 7:46:34это сделать. То есть, если у меня
- 7:46:35действительно хватает денег в кошельке
- 7:46:36для того, чтобы положить их в копилку, я
- 7:46:38только в этом случае смогу положить их,
- 7:46:40собственно, в копилку и взять из
- 7:46:41кошелька. Хорошо, тут у нас
- 7:46:42подчёркивает, ну, опять это наша ошибка,
- 7:46:44что money. Возвращает 64, а save mount
- 7:46:47просто integer, поэтому надо переводить
- 7:46:49это всё к одному типу. Приведу к IN64.
- 7:46:51Хорошо, казалось бы, всё проверили, что
- 7:46:53у нас хватает денег. Если денег хватает,
- 7:46:55тогда мы их перекладываем из кошелька в
- 7:46:56копилку. Но смотрим, у нас переменная
- 7:46:58банка - это обычный integer. Save
- 7:47:00handler - это HTTPх handler, значит, он
- 7:47:02будет запущен в отдельной рутине.
- 7:47:04Значит, у нас тут опять потенциальный
- 7:47:05доступ к переменной банк из разных
- 7:47:07грутин. Это гонка данных, поэтому мы
- 7:47:09должны этого избегать. Тут тоже будем
- 7:47:11использовать атомиic. Атомиic N64. В
- 7:47:13отличие от переменной мани, я не буду
- 7:47:15изначально в копилку ничего добавлять.
- 7:47:16Изначально у нас копилки будет 0
- 7:47:18долларов. Ну и тут у нас уже вместо
- 7:47:19оператора плюс равно нужно использовать
- 7:47:21метод added. И вот передаём сюда save
- 7:47:23аount. Вот даже мне редактор кода
- 7:47:25автоматически уже этот save аount в 64
- 7:47:27обернул. Ну, давайте ещё дополнительно,
- 7:47:29если у меня не хватило денег на
- 7:47:30проведение той или иной операции, я буду
- 7:47:32об этом информацию на экран выводить.
- 7:47:34Здесь вот, если во время оплаты у меня
- 7:47:35не хватило денег, я буду выводить на
- 7:47:37экран, что не хватает денег на
- 7:47:40проведение оплаты.
- 7:47:42Вот. И в случае с сейфхендлером точно
- 7:47:44так же буду вводить на экран, что не
- 7:47:47хватает денег, чтобы положить в копилку.
- 7:47:52Хорошо. Давайте я вот ещё проводник
- 7:47:54вс-кода вот так вот спрячу, потому что
- 7:47:56всё равно у нас тут всего лишь один
- 7:47:58файл, а зато будет побольше кода видно.
- 7:48:00Оп, всё, спрятал. О'кей. Но видим, в
- 7:48:02случае с оплатой мы говорим, что оплата
- 7:48:04прошла успешно, а в случае с тем, когда
- 7:48:06мы кладём деньги в копилку, то мы что-то
- 7:48:07ничего не говорим. Хотя хочется,
- 7:48:09наверное, да, тоже знать, сколько у нас
- 7:48:10там в итоге-то после сохранения денег в
- 7:48:12копилку осталось денег в кошельке и
- 7:48:14сколько у нас появилось денег в копилке.
- 7:48:16Давайте это всё тоже будем выводить.
- 7:48:17Новое значение переменной мани. И тут у
- 7:48:21меня money.
- 7:48:23Ну и, соответственно, новое значение
- 7:48:25переменной
- 7:48:26bank. Это у меня bank.lot. О'кей. Ну и
- 7:48:31казалось бы, вот так вот издалека, если
- 7:48:32посмотреть, оно должно работать. И я вам
- 7:48:34сразу скажу, да, то, что мы с вами
- 7:48:36написали, оно будет работать. Но тут
- 7:48:38есть одна очень такая большая коварная
- 7:48:41проблема, которую мы можем заметить
- 7:48:43далеко не сразу. Ну давайте пока не
- 7:48:45будем думать про эту проблему. Давайте
- 7:48:46пока попробуем протестировать нашу
- 7:48:47программу, посмотреть, как она вообще
- 7:48:49работает. Запускаю программу. Программа
- 7:48:51запущена. Ну и попробую вот сделать
- 7:48:53оплату, то есть кинуть запрос на
- 7:48:54Pattern/pay на 100 долларов. Кидаю.
- 7:48:58Опа. И вижу, оплата прошла успешно. 900.
- 7:49:00То есть было 1.000 долларов. Я сделал
- 7:49:02оплату на 100 долларов, осталось 900.
- 7:49:04Давайте теперь попробую сделать оплату
- 7:49:05на 50 долларов. Отправляю. Оплата прошла
- 7:49:08успешно. 850 долларов, да? То есть я
- 7:49:09сначала потратил 100 долларов, потом
- 7:49:11потратил 50 долларов, в итоге в сумме
- 7:49:12150 потратил 1.000 - 150 850. Всё
- 7:49:15правильно. Теперь давайте попробуем
- 7:49:16сохранить деньги в копилку. Создаю ещё
- 7:49:18один запрос, описываю его. То, что я его
- 7:49:21буду кидать на local host 901 порт
- 7:49:23pattern/save. Хорошо. Теперь нажимаю
- 7:49:25bodyди. Тут r. Ну и вот текст. Хорошо. Я
- 7:49:29хочу передать туда 500 долларов. То есть
- 7:49:31я хочу 500 долларов вложить в копилку.
- 7:49:33Отправляю и вижу вывод на экране. Новое
- 7:49:36значение переменной мани 350. То есть я
- 7:49:38850 долларов, которые у меня были в
- 7:49:39кошельке, уменьшил на 500 долларов,
- 7:49:41которые я из этого кошелька взял. И эти
- 7:49:43500 долларов я переложил в копилку. Всё
- 7:49:45круто, всё чётко, всё работает. Давайте
- 7:49:47теперь попробуем во время оплаты, то
- 7:49:49есть когда кидаю запрос на Pattern/pay,
- 7:49:51вместо числа передать какую-нибудь
- 7:49:52фигню. Например, я передам слово абоба.
- 7:49:55Вот оно. Делаю запрос и вижу, что у меня
- 7:49:58ошибка. То есть не получилось
- 7:49:59преобразовать тело запроса в integer.
- 7:50:01Почему? Да потому что мы не смогли вот
- 7:50:02эту абобу с парсий. То есть мы не
- 7:50:04поняли, что это такое. Это явно было не
- 7:50:05число. Ну и то же самое в случае с
- 7:50:07сохранением денег в копилку. Тоже
- 7:50:08передам что-то непонятное. Ну и вот тут
- 7:50:10у меня такой же ответ по сути, что не
- 7:50:12получилось преобразовать HTTP тело V
- 7:50:14integer. Ну потому что вот это FД СФД.
- 7:50:16Это какой-то инвалид синтакс. То есть
- 7:50:18это явно не число было. Хорошо, осталось
- 7:50:20нам проверить только какие-то покупки,
- 7:50:22когда у нас может не хватить денег. То
- 7:50:23есть сейчас у нас на балансе 350
- 7:50:24долларов. Давайте попробую сделать
- 7:50:25покупку на 500 долларов. Ещё вот на
- 7:50:28Patter/Pay кидаю запрос, где в HTPL у
- 7:50:30меня 500. Отправляю. Не хватает денег на
- 7:50:33приведение оплаты. Всё верно. Но если я
- 7:50:35кину запрос, например, на 200 долларов,
- 7:50:37хоп, тогда, да, оплата прошла успешно.
- 7:50:38Ну и то же самое с сохранением денег в
- 7:50:40копилку. То есть попробую сохранить 500
- 7:50:42долларов ещё. Оп, не хватает денег,
- 7:50:45чтобы положить в копилку. Но у нас 150
- 7:50:46долларов. Давайте попробуем 100 из них
- 7:50:48положить в копилку. Да, всё отлично
- 7:50:50работает. У нас было 500 в копилке,
- 7:50:52стало 600. Было 150 в кошельке, стало
- 7:50:5450. Ну, взяли 100 из кошелька, положили
- 7:50:55в копилку. Всё, логично. Кажется, что
- 7:50:58всё работает хорошо в нашей программе.
- 7:51:00Всё вот просто вообще замечательно. Но
- 7:51:02тут у нас в нашей программе есть одна
- 7:51:04прямо критическая ошибка, которую,
- 7:51:06повторюсь, мы просто так заметить не
- 7:51:08сможем. Эта ошибка будет проявляться
- 7:51:09тогда, когда у нас на приложении будет
- 7:51:11большая нагрузка. То есть много
- 7:51:12пользователей одновременно будут
- 7:51:14пытаться что-то с нашим приложением
- 7:51:15сделать, там кидать какие-то запросы,
- 7:51:17проводить какие-то операции, и только
- 7:51:18лишь тогда внезапно вообще непонятно
- 7:51:21почему у нас вылезет ошибка, о которой
- 7:51:23мы сейчас поговорим. Давайте представим
- 7:51:24ситуацию, что у нас на балансе 50
- 7:51:26долларов. То есть в переменной майне у
- 7:51:28нас лежит 50 долларов. Ну, в банке ноль
- 7:51:31у нас. Хорошо. Далее, в какой-то момент
- 7:51:33времени какой-то клиент делает HTTP
- 7:51:36запрос на наш паттер/Pay, попадает в
- 7:51:39Payхandler, и он хочет сделать покупку
- 7:51:41на 30 долларов. В этот же самый момент,
- 7:51:44в эту же самую секунду, другой клиент
- 7:51:46делает запрос на наш сейфхендлер, то
- 7:51:49есть хочет положить деньги из кошелька к
- 7:51:51себе в копилку и тоже передаёт 30
- 7:51:53долларов.
- 7:51:55Пока представим, что у нас вот эти два
- 7:51:57клиента имеют общий кошелёк, то есть у
- 7:51:58них общая вот эта переменная мани. Ну,
- 7:52:00там, например, это какой-нибудь семейный
- 7:52:02кошелёк, да, и у нас есть возможность с
- 7:52:04этим кошельком работать как мужу, да,
- 7:52:05так и жене. Всё. Муж с женой
- 7:52:07одновременно делают два запроса. Муж
- 7:52:09хочет что-то купить, жена хочет положить
- 7:52:11деньги в копилку. Так как мы знаем, что
- 7:52:12это у нас HTTP-х хендлеры, то делаем
- 7:52:15вывод, что они у нас запускаются в двух
- 7:52:16отдельных рутинах. И если муж и жена
- 7:52:19сделали запрос вот в один момент, а это
- 7:52:21вполне может быть, особенно, повторюсь,
- 7:52:22при большой нагрузке на наше приложение,
- 7:52:24у нас может быть следующая картина. У
- 7:52:26нас запускаются вот эти два хендлера.
- 7:52:27Они обрабатываются абсолютно
- 7:52:28одновременно. Одновременно вычитывается
- 7:52:30тело запроса, одновременно переводится в
- 7:52:32строку, одновременно переводится строка
- 7:52:34в число. И одновременно эти две горутины
- 7:52:37доходят до проверки баланса. Первая
- 7:52:39грутина дошла до проверки баланса и
- 7:52:41втораятина дошла до проверки баланса.
- 7:52:44Что тут будет происходить? Первая рутина
- 7:52:45смотрит: "У меня денег на то, чтобы
- 7:52:47провести оплату хватает. У нас денег 50,
- 7:52:49а оплата на 30. Ну да, как бы если у нас
- 7:52:51в кошельке 50 долларов, мы на 30
- 7:52:53долларов сможем провести оплату. Поэтому
- 7:52:55здесь у нас получается это выражение
- 7:52:57даёт true и проваливается в выполнение
- 7:53:00тела условного ветвления. В эту же самую
- 7:53:02секунду, когда у нас проходила тут
- 7:53:04проверка в двадцать девятой строчке, вот
- 7:53:05в этой вот, в эту же секунду, это вторая
- 7:53:07рутина тоже будет смотреть. Денег на то,
- 7:53:09чтобы положить в копилку, хватает в
- 7:53:11кошельке. Тут 50, тут 30 в запросе. Ну
- 7:53:1550 больше либо равно 30ти, да? То есть
- 7:53:17денег на то, чтобы положить в копилку,
- 7:53:18хватает. Тут тоже нам возвращается true
- 7:53:22и тоже проваливается выполнение в тело
- 7:53:24условного ветвления. Далее у нас
- 7:53:26списываются деньги за покупку. То есть
- 7:53:2950 долларов было, списалось 30 долларов,
- 7:53:31в итоге осталось 20 долларов. И тут у
- 7:53:34нас списываются деньги для того, чтобы
- 7:53:36положить их в копилку. 20 долларов - 30
- 7:53:40долларов равно -10 долларов. И в итоге
- 7:53:45мы случайно ушли в отрицательный баланс.
- 7:53:48Хотя такого быть не может. То есть, если
- 7:53:50у нас на балансе не хватает денег, мы
- 7:53:51должны были вывести на экран, не хватает
- 7:53:53денег, чтобы положить в копилку, а мы
- 7:53:54положили. Почему так случилось? Потому
- 7:53:57что у нас что операция проведения
- 7:53:59оплаты, что операция по переводу денег
- 7:54:02из кошелька в копилку- это операции
- 7:54:05многосоставные. Из чего состоит первая
- 7:54:07операция? Операция на проведение оплаты.
- 7:54:08Она состоит из двух действий: сделать
- 7:54:11проверку, сделать списание. А операция
- 7:54:14по переводу денег в копилку вообще из
- 7:54:16трёх действий состоит. сделать проверку,
- 7:54:18сделать списание из кошелька, добавить
- 7:54:21деньги в копилку. Получается, при
- 7:54:22параллельном выполнении вот этих вот
- 7:54:24наших двух хендлеров может возникнуть
- 7:54:26ситуация, что одновременно начинает
- 7:54:28выполняться вот этот вот код. У нас
- 7:54:30одновременно проходит проверка раз и
- 7:54:32одновременно проходит проверка два.
- 7:54:34После чего производится двойное
- 7:54:35списание, так называемое. Сначала
- 7:54:37списание за покупку, потом списание за
- 7:54:39то, чтобы перевести эти деньги в
- 7:54:40копилку. Но проблема в том, что у нас
- 7:54:42может не быть денег на то, чтобы
- 7:54:44совершить это самое двойное списание. У
- 7:54:46нас было 50 долларов, и мы, по сути,
- 7:54:47могли бы либо провести оплату, либо
- 7:54:50положить деньги в копилку, но не всё
- 7:54:51сразу. Но так как эти два хендлера
- 7:54:53выполняются параллельно, то вполне может
- 7:54:55быть ситуация, что вот эти две проверки
- 7:54:57проходят тоже параллельно, тоже
- 7:54:58одновременно. Эти две проверки друг про
- 7:55:01друга не знают. Поэтому мы здесь
- 7:55:02смотрим, что типа у нас денег на покупку
- 7:55:04хватает, да, идём покупать. И в эту же
- 7:55:06секунду, как мы вот здесь вот проводили
- 7:55:07проверку, в эту же секунду у нас здесь
- 7:55:09смогла производиться проверка, у нас
- 7:55:11хватает денег, чтобы положить их в
- 7:55:12копилку, да, и идёт перекладывание денег
- 7:55:15в копилку. Получается, у нас в какой-то
- 7:55:17из этих частей кода могла случиться
- 7:55:19ситуация, что между тем, как производить
- 7:55:21проверку, и между тем, как списывать
- 7:55:24деньги с кошелька, значение переменной
- 7:55:26мани могло успеть поменяться. Вот здесь
- 7:55:28вот между выполнением двух этих строчек,
- 7:55:30между тем, как была произведена проверка
- 7:55:32и между фактическим списанием, вот в
- 7:55:34этот момент вот здесь вот деньги уже
- 7:55:36могли успеть списаться, потому что
- 7:55:37кто-то их хотел себе в копилку положить.
- 7:55:39Для большей наглядности я вам хочу
- 7:55:40продемонстрировать это. Но так как я не
- 7:55:42могу один отправлять большое количество
- 7:55:44запросов в секунду, поэтому я поступлю
- 7:55:46следующим образом. Здесь в пейхенре
- 7:55:48после проведения проверки я вызову
- 7:55:51таймслип на 3 секунды для того, чтобы
- 7:55:53между проверкой и фактическим списанием
- 7:55:56денег в пейхендлере прошло явно какое-то
- 7:55:58значительное количество времени. Это
- 7:56:00количество времени даже без таймслипа
- 7:56:02тут будет проходить, потому что
- 7:56:03выполнение этой команды и этой команды -
- 7:56:05это две разные команды, и между их
- 7:56:07выполнением могла выполниться ещё
- 7:56:09какая-то третья команда. Но так как я
- 7:56:11человек, я не программа, я не миллион
- 7:56:13пользователей, я всего лишь один,
- 7:56:15поэтому я себе облегчу задачу и добавлю
- 7:56:17здесь тайм слип для того, чтобы
- 7:56:19увеличить время, которое проходит между
- 7:56:21проверкой и фактическим списанием в
- 7:56:22пейхендлере, чтобы дать себе время в
- 7:56:24этот момент сделать коварный запрос на
- 7:56:27save. Ну давайте посмотрим, как будет
- 7:56:29работать наша программа. Я изначально
- 7:56:30себе поставлю баланс 50 долларов. Вот
- 7:56:33money 50, то есть у меня 50 долларов на
- 7:56:34балансе. И буду делать два запроса.
- 7:56:36Сначала на сPay я отправлю 30 долларов.
- 7:56:39То есть на 30 долларов хочу покупку
- 7:56:40совершить и на слеve тоже 30 долларов.
- 7:56:42То есть 30 долларов хочу положить себе в
- 7:56:44копилку. Поехали. Я перезапущу
- 7:56:46программу. Gounmain.
- 7:56:48Запустилась. И теперь отправляю запрос
- 7:56:51на Pay. В этот же момент на save.
- 7:56:55Оп. И видим, что в консоли у нас
- 7:56:57случилось. Я отправил запрос на Pay, тем
- 7:57:00самым стриггерил вот этот вот Payхенer,
- 7:57:01пошло его выполнение, прошла проверка,
- 7:57:04что денег на оплату хватает. То есть у
- 7:57:05нас 50 долларов, оплата 30, всё хватает.
- 7:57:08И здесь он заснул, но как бы это
- 7:57:10имитация того, что тут время какое-то у
- 7:57:12нас прошло. В этот же момент у нас пошёл
- 7:57:15запрос на сейфлер. Сейхлер выполнялся,
- 7:57:17сделал проверку, что да, действительно,
- 7:57:19у нас хватает денег на то, чтобы
- 7:57:20положить их в копилку. Взял, да, и
- 7:57:22положил. И мы видим, что у нас из 50
- 7:57:24долларов 30 ушло в копилку, осталось 20.
- 7:57:27И в этот же момент у нас началось
- 7:57:29списание денег за покупку. И оказалось,
- 7:57:32что у нас 20 долларов всего лишь в
- 7:57:34кошельке. Покупка на 30 долларов. И у
- 7:57:36нас оплата прошла успешно. -10°, хотя
- 7:57:39такого быть не должно. У нас не может
- 7:57:40баланс уходить в минус. Но из-за того,
- 7:57:42что у нас что проведение оплаты, что
- 7:57:44сохранение денег в копилку - это
- 7:57:45многосоставные операции, то тут опять
- 7:57:48возникает проблема гонок данных. Только
- 7:57:51тут у нас уже проблема гонки данных не
- 7:57:52на уровне одной какой-то переменной, а
- 7:57:54на уровне проведения целой операции. То
- 7:57:56есть, повторюсь, я вот сюда вот вот
- 7:57:58прямо вот между вот этими двумя
- 7:57:59строчками добавил time сп, но просто
- 7:58:01потому, что я один человек, я не могу
- 7:58:02генерировать тысячи запросов в секунду.
- 7:58:04А если и могу, то мы сейчас совсем с
- 7:58:06вами ничего не поймём, как я вообще это
- 7:58:07делаю. Поэтому, так как я один человек,
- 7:58:09я здесь сделаю таймсп для того, чтобы
- 7:58:11мне просто облегчить задачу, сделать два
- 7:58:13одновременных запроса, которые
- 7:58:15одновременно попадают вот в этот вот
- 7:58:16кусок кода, и продемонстрировать, что
- 7:58:18вот этот вот наш код имеет огромный
- 7:58:19недостаток в том, что у нас потенциально
- 7:58:21может случиться отрицательный баланс,
- 7:58:23хотя такого быть не должно. У нас, если
- 7:58:25у нас сначала началась оплата, должна
- 7:58:26была до конца пройти оплата, и только
- 7:58:29после этого должны были предприниматься
- 7:58:31какие-то там попытки проверить, сколько
- 7:58:33у нас денег на балансе, для того чтобы
- 7:58:34положить их в копилку. Получается, раз
- 7:58:36критическая секция, два критическая
- 7:58:38секция. Нам нужно каким-то образом
- 7:58:40заставить рутины вот в этих вот местах
- 7:58:43выполняться по очереди, чтобы сначала
- 7:58:45прошли все проверки и фактическое
- 7:58:47списание для оплаты. И только потом уже,
- 7:58:50если кто-то там параллельно захочет
- 7:58:51положить какие-то деньги себе в копилку,
- 7:58:53только после этого уже начались проверки
- 7:58:55для того, чтобы положить деньги в
- 7:58:56копилку и фактическое перекладывание
- 7:58:58денег в копилку. И наоборот, если кто-то
- 7:59:00в этот момент проводит проверку на то,
- 7:59:01что есть ли у него деньги, чтобы
- 7:59:02положить их в копилку, и кто-то другой в
- 7:59:04этот же момент захотел провести оплату,
- 7:59:06то этот кто-то другой ждал, пока до
- 7:59:08конца доведётся операция перекладывания
- 7:59:10денег в копилку и только после этого
- 7:59:12начинал свою операцию проведения оплаты.
- 7:59:14Опять же скажу это слово, нам нужно
- 7:59:16рутины выстроить вот в этих вот секциях
- 7:59:17в очередь. А мы уже с вами умеем это
- 7:59:19делать благодаря мьютексам. За плюс я
- 7:59:21обозначу взятие блокировки на Мюкс. За
- 7:59:23минус я обозначу отпускание этой самой
- 7:59:25блокировки. Нам нужно вот здесь вот
- 7:59:27взять блокировку на mutex, а здесь
- 7:59:29отпустить. И тут также взять блокировку
- 7:59:31наex и отпустить. Таким образом, если
- 7:59:33какая-то рутина преобразовала входящее
- 7:59:35тело запроса и дошла до момента списания
- 7:59:37денег, то она попробует взять блокировку
- 7:59:39на Mutteкс. Если в этот момент
- 7:59:41блокировки на Mтекс нету, блокировка
- 7:59:43спокойно возьмётся и рутина начнёт свою
- 7:59:44выполнению, проверит, сколько денег на
- 7:59:46балансе, произведёт, собственно, саму
- 7:59:48оплату и потом отпустит блокировку на
- 7:59:50Mтекс. Но во время того, как блокировка
- 7:59:52на Mutкс взята и Рутина тут, например,
- 7:59:54делает какие-то проверки, и другая
- 7:59:56рутина в этот же момент преобразовала
- 7:59:57всё тело запроса и дошла до проведения
- 7:59:59операции по сохранению денег в копилку,
- 8:00:01это самая вторая горутина попробует
- 8:00:03взять блокировку на Mтекс и не сможет
- 8:00:05этого сделать, потому что эта блокировка
- 8:00:06взята другой горутиной. Вот здесь вот. И
- 8:00:09только когда первая грутина дойдёт до
- 8:00:11конца и разблокирует,
- 8:00:13только тогда вторая сможет взять
- 8:00:15блокировку и начать уже своё выполнение.
- 8:00:17Таким образом, мы с вами вот в этой вот
- 8:00:19самой критической секции смогли
- 8:00:20выстроить рутины в очередь. И тем самым
- 8:00:22мы избежим описанной проблемы, когда
- 8:00:24одновременно у нас выполняются две
- 8:00:26многосоставные операции. Да,
- 8:00:28оказывается, вот настолько непросто
- 8:00:29работать с конкурентными приложениями,
- 8:00:31но это лишь вопрос опыта. Со временем вы
- 8:00:34уже сразу издалека научитесь видеть все
- 8:00:37эти критические секции, которые могут
- 8:00:39привести к какому-то неправильному
- 8:00:40выполнению программы. То есть как я
- 8:00:42увидел вообще вот эту вот критическую
- 8:00:44секцию здесь и здесь? Ну, я, во-первых,
- 8:00:45смотрю, что у меня два хендлера, то есть
- 8:00:47это HTTP хендлеры, а значит, они будут
- 8:00:49выполняться в отдельной рутине. И я
- 8:00:51смотрю, что у меня тут может вызвать
- 8:00:53проблемы: чтение тело запроса и
- 8:00:54переведение его в строку. Да нет, это
- 8:00:56две вообще независимые операции. То есть
- 8:00:58это две переменные. Они хоть и
- 8:01:00называются одинаково, они находятся
- 8:01:01вообще в разных гартинах. их область
- 8:01:04видимости ограничена их функцией, и я
- 8:01:06использую их только в рамках этой
- 8:01:07функции, в рамках этой рутины. То есть
- 8:01:09доступ к переменной HTTP request Bди,
- 8:01:11доступ к переменной HTTP request bodyст
- 8:01:13ограничен одной рутиной. Значит, тут
- 8:01:15никаких гонок данных быть просто
- 8:01:16физически не может. О'кей. Дальше
- 8:01:18преобразование полученного тела запроса
- 8:01:20в число, но это тоже происходит всё в
- 8:01:22рамках одной горутины, одной функции.
- 8:01:24Полученная переменная тоже используется
- 8:01:26только в рамках одной рутины, одной
- 8:01:28функции. Тут тоже никакой гонки данных
- 8:01:30быть не может. Но потом я вдруг вижу,
- 8:01:32что у меня идёт взаимодействие с
- 8:01:33переменной мани. А вот переменная мани у
- 8:01:36меня уже в другой рутине используется. И
- 8:01:38область видимости этой переменной
- 8:01:39глобальная. А значит, переменная мани в
- 8:01:41пейхенре и переменная маoney в
- 8:01:43saveйхендлере - это одна и та же
- 8:01:45переменная. А значит, получается, у меня
- 8:01:48тут доступ к одной переменной в двух
- 8:01:49разных рутинах. Сразу начинаю
- 8:01:52настораживаться, что тут может пойти не
- 8:01:54так. То есть я понимаю, что тут у меня
- 8:01:55уже работа с какими-то общими ресурсами
- 8:01:57идёт, и начинаю повнимательнее
- 8:01:59присматриваться. Если какая-то работа с
- 8:02:00общими ресурсами, значит, я сразу
- 8:02:02прикидываю, что может произойти, если
- 8:02:04одновременно параллельно начинается
- 8:02:05выполнение этих двух кусков кода. Ну, я
- 8:02:07вижу, что может произойти, что у меня
- 8:02:09баланс 50 долларов, у меня происходит
- 8:02:10проверка, могу ли я сделать покупку на
- 8:02:1230 долларов, могу. Могу ли я положить в
- 8:02:14копилку 30 долларов, могу. И
- 8:02:16одновременно происходит покупка на 30
- 8:02:17долларов и пополнение копилки на 30
- 8:02:19долларов. В итоге 50 - 30 -30 = -10. Вот
- 8:02:22таким вот образом я нашёл эту гонку
- 8:02:24данных. Повторюсь, изначально это
- 8:02:26кажется совсем какой-то магией, типа,
- 8:02:27да, как вообще до этого можно было
- 8:02:28додуматься? Но с опытом к вам придёт
- 8:02:30понимание. В первый раз оно, конечно,
- 8:02:32кажется чем-то вообще заоблачным, но всё
- 8:02:34в сухом остатке мы с вами выяснили, что
- 8:02:35нам нужно вот эту критическую секцию и
- 8:02:37вот эту критическую секцию обложить
- 8:02:38мютексом. Давайте это и сделаем. Я
- 8:02:40создаю mtx sy muteex. И я перед первой
- 8:02:47критической секцией беру блокировку на
- 8:02:48этом Мютексе, а после неё отпускаю
- 8:02:51блокировку. И тут то же самое. Я перед
- 8:02:54второй критической секцией беру
- 8:02:55блокировку нак и после неё я эту самую
- 8:02:58блокировку отпускаю. Тем самым я взял
- 8:03:00раз секцию MUTEx, два секцию Mtex. Таким
- 8:03:03образом, я заставил эти секции
- 8:03:04выполняться друг за дружкой в двух
- 8:03:06разных рутинах. И у меня не будет
- 8:03:07такого, что одновременно происходят две
- 8:03:09проверки, одновременно происходит два
- 8:03:11списания. Нет, такого у меня не будет. У
- 8:03:12меня последовательно выполнится сначала
- 8:03:14это, потом эта секция. Ну либо наоборот,
- 8:03:17в зависимости от того, там как запросы
- 8:03:18придут. Давайте теперь попробуем. Я
- 8:03:20оставлю сLEP. Так же само протестировать
- 8:03:22нашу программу и посмотреть, насколько у
- 8:03:24нас произойдёт это самое двойное
- 8:03:25списание. Сохраняю файл, перезапускаю
- 8:03:27программу и так же самое сначала делаю
- 8:03:29запрос на Payхandler, потом резко делаю
- 8:03:31на saveй. Делаю запрос на payм
- 8:03:34в консоль. Смотрим в консоль. У нас
- 8:03:36оплата прошла успешно. Мы первую
- 8:03:38отправили оплату на 30 долларов, она у
- 8:03:40нас прошла. А вот в копилку мы уже не
- 8:03:42стали ничего перекладывать, потому что
- 8:03:44пока производилась оплата, тут у нас
- 8:03:46была блокировка на Мьютексе. Потом
- 8:03:48сейфлер преобразовал всё тело запроса и
- 8:03:50попробовал тоже взять блокировку нак и
- 8:03:52не смог, потому что она взята уже в
- 8:03:54другом месте. Соответственно, сейх вот
- 8:03:56здесь вот просто будет ждать, ждать,
- 8:03:58ждать, ждать, пока эта блокировка нак
- 8:03:59где-то в другом месте будет отпущена.
- 8:04:02Потом оплата до конца производится,
- 8:04:05отпускается блокировка на Muttex. После
- 8:04:07этого, наконец-таки, safe сможет взять
- 8:04:08блокировку на Muttex и произвести
- 8:04:10проверку. И уже в этой проверке он
- 8:04:12увидит, что денег на балансе не хватает
- 8:04:14для того, чтобы положить их в копилку.
- 8:04:16отработает блокса и выведится на экран и
- 8:04:18блокировка на MEx будет отпущена. Ну
- 8:04:20давайте вот ещё раз на картиночке
- 8:04:21разберём, как вообще MEX в наших
- 8:04:23хендлерах влияет на выполнение кода этих
- 8:04:25самых хендлеров. У меня есть первый
- 8:04:26бегун Pхенр, есть второй бегун
- 8:04:28Safeхandler. У этих бегунов общая
- 8:04:30эстафетная палочка, один общий мьютекс в
- 8:04:32глобальной области видимости. Когда
- 8:04:33какой-то из этих бегунов первый добегает
- 8:04:36до начала дистанции, то есть первый
- 8:04:37добегает до попытки взять блокировку на
- 8:04:39Мьютекс или же в нашей аналогии первый
- 8:04:41добегает до эстафетной палочки. Если в
- 8:04:43этот момент эстафетная палочка будет ещё
- 8:04:45никем не взята, тогда тот, кто добежал
- 8:04:47до попытки взять эту эстафетную палочку,
- 8:04:49он её сможет взять. Допустим, Йхендлер
- 8:04:51первый добежал до попытки взять
- 8:04:52эстафетную палочку, вот здесь вот до
- 8:04:54попытки взять блокировку на Мютекс. И
- 8:04:55эстафетная палочка ещё никем не взята.
- 8:04:57Тогда этот самый Йхенр сможет взять себе
- 8:04:59эстафетную палочку и продолжить
- 8:05:01выполнять свой код. В то же время, если
- 8:05:03какой-нибудь другой бегун, например,
- 8:05:04сейхлер, добежит до места, где ему нужно
- 8:05:07брать эстафетную палочку и попробует её
- 8:05:09взять, а эстафетной палочки нету, тогда
- 8:05:11этот самый сейфхендлер остановится на
- 8:05:13месте, где он попытался взять эстафетную
- 8:05:14палочку, и будет ждать, когда же
- 8:05:16появится возможность эту самую
- 8:05:18эстафетную палочку взять. Как только
- 8:05:20тот, кто взял ранее эстафетную палочку,
- 8:05:22добежит до момента отпускания этой самой
- 8:05:24эстафетной палочки, то есть до
- 8:05:25разблокировки Мьютекса, он вернёт эту
- 8:05:27эстафетную палочку на место.
- 8:05:28Соответственно, опять эстафетную палочку
- 8:05:30никто не будет держать в этот момент.
- 8:05:32Тот, кто ждал возможности эту самую
- 8:05:33эстафетную палочку принять, наконец-таки
- 8:05:35сможет её взять уже себе. И только после
- 8:05:38этого продолжится выполнение кода. Если
- 8:05:40в этот момент, пока эстафетная палочка
- 8:05:42была взята уже вторым бегунком, первый
- 8:05:44бегунок опять попадёт в попытку взять
- 8:05:47эту самую эстафетную палочку, а
- 8:05:48эстафетной палочки не будет, то уже
- 8:05:50первый бегунок будет вот на этой попытке
- 8:05:52взять эстафетную палочку, ждать, ждать,
- 8:05:54ждать, ждать, когда же эстафетная
- 8:05:55палочка вернётся на место и будет
- 8:05:57возможность её взять уже когда второй
- 8:05:58бегун до конца выполнит весь необходимый
- 8:06:00код и дойдёт до отпускания эстафетной
- 8:06:02палочки, то есть до разблокировки
- 8:06:04Мьютекса, соответственно, опять эта
- 8:06:05самая эстафетная палочка вернётся на
- 8:06:07своё место. Соответственно, как только
- 8:06:09эстафетная палочка вернётся на своё
- 8:06:10место, тот, кто ждал возможности эту
- 8:06:12самую эстафетную палочку взять, сможет
- 8:06:14это сделать
- 8:06:16таким вот образом и продолжить
- 8:06:17выполнение кода. Потом опять хранитель
- 8:06:19эстафетной палочки, тот, кто её ранее
- 8:06:21взял, в нашем случае это сейчас йхлер,
- 8:06:23дойдёт рано или поздно до разблокировки
- 8:06:25Мьютекса, то есть обратно положит
- 8:06:27эстафетную палочку. И таким образом мы
- 8:06:29благодаря этому самому Мьютексу горутины
- 8:06:31в критической секции выстраиваем в
- 8:06:33очередь, что они вот весь код свой
- 8:06:35остальной выполняют параллельно. Но как
- 8:06:37только доходит до этой самой эстафетной
- 8:06:38палочки, то какая-то рутина эстафетную
- 8:06:41палочку берёт, а вторая рутина будет
- 8:06:43ждать возможности взять эстафетную
- 8:06:45палочку, потому что эстафетная палочка у
- 8:06:46нас однак один. И один и тот же можно
- 8:06:49одновременно взять только один раз. Если
- 8:06:51кто-то другой попробует взять эту
- 8:06:52эстафетную палочку, то он просто
- 8:06:54заблокируется на этой строчке и будет
- 8:06:56ждать, пока эстафетная палочка вернётся
- 8:06:58на место и когда же появится возможность
- 8:07:00эту самую эстафетную палочку взять. Ну,
- 8:07:01таким образом мы с вами благодаря
- 8:07:02Мьютексу смогли избежать гонку данных.
- 8:07:04но гонку данных уже не на уровне
- 8:07:06какой-то одной переменной, а на уровне
- 8:07:08целой операции. Да-да, такое тоже
- 8:07:10случается. За этим нужно следить. Когда
- 8:07:12пишете какое-то приложение, где
- 8:07:14запускается несколько грутин, неважно
- 8:07:16вашими руками или автоматически под
- 8:07:17капотом какой-то другой библиотеки,
- 8:07:19нужно всегда очень внимательно работать
- 8:07:21с какими-то разделяемыми ресурсами. Если
- 8:07:23какие-то ресурсы, какие-то переменные
- 8:07:25или ещё что бы то ни было ещё
- 8:07:27задействуются одновременно в двух и
- 8:07:28более разных рутинах, это сразу должно
- 8:07:31приковывать ваше внимание и нужно
- 8:07:32анализировать, что же в коде происходит,
- 8:07:34чтобы не попасть в такие неприятные
- 8:07:36ситуации, как мы с вами рассмотрели,
- 8:07:38когда у нас не было Мьютекса. И что
- 8:07:39самое коварное, эта программа даже будет
- 8:07:41работать до поры до времени, пока не
- 8:07:43случится то, о чём мы сказали, что
- 8:07:45одновременно две горутины попадут на
- 8:07:47проверку, и тогда пиши пропало. Таким
- 8:07:50образом, мы с вами наконец смогли
- 8:07:51реализовать вот это вот наше миникенд
- 8:07:53приложение управление кошельком. Мы с
- 8:07:55вами, как изначально и задумывали,
- 8:07:56предоставляем два паттерна, два
- 8:07:58хендлера, благодаря которым мы сможем
- 8:08:00проводить оплату и благодаря которым мы
- 8:08:02сможем сохранять деньги в копилку. Наши
- 8:08:04эти хендлеры запускаются в отдельных
- 8:08:06рутинах и корректно работают с
- 8:08:08разделяемыми ресурсами. Тут даже на
- 8:08:10схеме видно, что две стрелочки идут к
- 8:08:12мании. То есть разделяемый ресурс - это
- 8:08:13вот наша переменная мания. Мы разобрали
- 8:08:15очень коварный случай, который может
- 8:08:17произойти, когда у нас производится в
- 8:08:18рутине какая-то многосоставная операция
- 8:08:21с использованием этого самого
- 8:08:22разделяемого ресурса. Ну и в целом
- 8:08:24протестировали наше приложение благодаря
- 8:08:26Постману, поотправляли в него
- 8:08:27httpзапросики и посмотрели, что
- 8:08:29выводится в консоль. Посмотрели, как
- 8:08:30производится работа с переменной маoney
- 8:08:32и как производится работа с переменной
- 8:08:33банк. И благодаря этому примеру мы с
- 8:08:35вами рассмотрели, что такое тело запроса
- 8:08:37и как оно используется. Конкретно в
- 8:08:39нашем примере мы благодаря телу запроса
- 8:08:42смогли узнавать, сколько именно денег
- 8:08:44клиент хочет положить себе в копилку
- 8:08:46либо на какую именно сумму клиент хочет
- 8:08:48произвести покупку. Если бы мы не
- 8:08:50передавали ничего в теле запроса, в этом
- 8:08:52нашем самом теле запроса, мы бы изнутри
- 8:08:55наших хендлеров никогда бы и не узнали,
- 8:08:56сколько именно денег клиент хочет
- 8:08:58потратить, либо сколько именно денег
- 8:08:59клиент хочет положить в копилку.
- 8:09:00Получается, это самое тело запроса - это
- 8:09:02какая-то дополнительная уточняющая
- 8:09:05информация для HTTP запроса. Благодаря
- 8:09:07этой самой дополнительной уточняющей
- 8:09:09информации, мы изнутри хендлера можем
- 8:09:11понимать, что именно от нас хотят. Не
- 8:09:13просто, что кто-то хочет оплату
- 8:09:15произвести, а насколько денег вообще, мы
- 8:09:17не знаем. А мы именно благодаря телу
- 8:09:19запроса передали, уточнили то, что
- 8:09:21оплату нужно производить именно на
- 8:09:23столько-то долларов. Ну либо ещё вот
- 8:09:25можно пример разобрать, что у нас есть
- 8:09:26какой-то ээнд для социальной сети,
- 8:09:28какой-то хендлер есть, который
- 8:09:29обрабатывает новые сообщения. И у нас
- 8:09:31есть два пользователя. Раз и два.
- 8:09:34Пользователь раз. Вот я один, тут два.
- 8:09:37Пользователь один хочет написать
- 8:09:38сообщение пользователю два. Он пишет
- 8:09:40сообщение на своём клиенте, потом
- 8:09:41нажимает кнопку "Отправить", и нам идёт
- 8:09:44в наш ээнд, в наш хендлер, запрос на то,
- 8:09:46что кто-то хочет отправить кому-то
- 8:09:48какое-то сообщение. И если бы у нас не
- 8:09:50было вот тела запроса, то мы изнутри
- 8:09:52нашего хендлера вообще не поняли бы, кто
- 8:09:55кому хочет отправить, что хочет
- 8:09:57отправить. Мы бы просто знали, что
- 8:09:58кто-то кому-то что-то хочет написать. Но
- 8:10:00благодаря телу запроса мы можем в этом
- 8:10:02самом теле запроса эту дополнительную
- 8:10:04уточняющую информацию передавать. Мы тут
- 8:10:06можем передать от кого from, то есть это
- 8:10:09у нас usеer 1, to, то есть кому юзеру 2.
- 8:10:13Ну и текст сообщения
- 8:10:16тоже можем передавать там что-нибудь
- 8:10:17ла-ла-ла. Вот. И мы уже изнутри вот
- 8:10:19этого самого нашего HTTP хендлера, когда
- 8:10:21принимаем запрос, мы уже знаем не
- 8:10:23просто, что кто-то кому-то хочет что-то
- 8:10:25написать, а мы уже знаем, кто кому и что
- 8:10:29именно хочет написать. И мы благодаря
- 8:10:30этому сможем уже передать эту самую
- 8:10:32информацию в второго юзера, отдать ему
- 8:10:35новое сообщение, вот этот вот текст,
- 8:10:37этот самый второй юзер сможет этот текст
- 8:10:38получить, прочитать, он будет знать, от
- 8:10:40кого это написано. Ну и так далее, и так
- 8:10:42далее, и так далее. То есть ещё раз,
- 8:10:43тело запроса нам нужно для того, чтобы в
- 8:10:45запросе, в нашем, в нашем HTTP запросе
- 8:10:48передавать какую-то дополнительную
- 8:10:49уточняющую информацию. Иногда нам эта
- 8:10:51дополнительная информация не нужна, а
- 8:10:53иногда без неё просто никак не обойтись.
- 8:10:56Оставшиеся темы по протоколу HTTP мы
- 8:10:58будем рассматривать на вот этом вот
- 8:10:59описанном примере. И для того, чтобы нам
- 8:11:01новая информация проще воспринималась, я
- 8:11:03вынужден немножко упростить эти наши
- 8:11:05хендлеры для того, чтобы далее мы с вами
- 8:11:08могли акцентировать внимание именно на
- 8:11:10новых вот этих вот темах, а не на том,
- 8:11:12насколько у нас сложный код и как он тут
- 8:11:14хитро устроен. Поэтому я тут переменную
- 8:11:15мани и переменную банк из атомиков верну
- 8:11:18обратно в самые обычные интеджеры.
- 8:11:20Почему я тут могу так сделать? Да,
- 8:11:22потому что если у меня здесь происходит
- 8:11:24какая-то работа с переменной мани,
- 8:11:26например, то она всё равно изолированна
- 8:11:29мютексом. Поэтому, если я тут работаю с
- 8:11:31переменной маней, я могу точно быть
- 8:11:32уверен, что этой работы у меня нету вот
- 8:11:35здесь вот в другой рутине, потому что
- 8:11:36другая рутина не сможет взять блокировку
- 8:11:38нак, если эта блокировка уже здесь
- 8:11:40взята. Поэтому тут я могу со спокойной
- 8:11:42душой избавиться от использования
- 8:11:43атомиков и юзать самые обычные
- 8:11:45интеджеры.
- 8:11:47Тут мне уже не нужно вызывать никакие
- 8:11:48методы. Я просто могу напрямую
- 8:11:50складывать и вычитать различные
- 8:11:51переменные и точно также выводить их
- 8:11:53значения на экран без вызова каких-то
- 8:11:54дополнительных методов. Также я не буду
- 8:11:56ничего выводить на экран, если у меня
- 8:11:58там не хватило денег на проведение
- 8:11:59оплаты, потому что этот код, он нам
- 8:12:01сейчас не нужен. Мы уже поняли, что наши
- 8:12:03проверки работают, работают корректно,
- 8:12:06поэтому вот это всё нам только лишь
- 8:12:07будет мешать, рассматривать дальнейшие
- 8:12:09темы. Ну и также в сейфхендлере я убираю
- 8:12:11всякую работу с атомиками. Нам тут уже
- 8:12:13не нужно ничего переводить руками в
- 8:12:15in64, потому что мани у нас теперь не
- 8:12:17атомник int 64, а просто самый обычный и
- 8:12:20integджер. Тут точно так же мне уже не
- 8:12:22нужно никакие методы вызывать. Могу
- 8:12:23напрямую вычитать значение из нашего
- 8:12:25кошелька. Ну и могу также напрямую
- 8:12:27добавлять значение в копилку через плюс
- 8:12:29равно. Выводы на экран, соответственно,
- 8:12:31у нас остаются самые обычные. И вот это
- 8:12:34вот лса я тоже убираю. Также уберу вот
- 8:12:36эти комментарии. Ну и, соответственно, в
- 8:12:38самом начале мне уже не нужно руками
- 8:12:39добавлять значения в переменную мане. Я
- 8:12:41могу это сделать сразу же во время её
- 8:12:43создания. Таймслип этот убираю, потому
- 8:12:45что он нужен был лишь для того, чтобы
- 8:12:46продемонстрировать имеющуюся в коде до
- 8:12:49добавлениям мьютекса в проблему.
- 8:12:51Ну всё, отныне я буду считать наш код
- 8:12:53достаточно упрощённым для того, чтобы мы
- 8:12:55могли с вами адекватно продолжить
- 8:12:56разбирать дальнейшие темы. Тело ответа,
- 8:12:58статус-код ответа, хедеры, HTTP метод
- 8:13:01запроса. Всё это мы с вами сейчас
- 8:13:03рассмотрим. Ну и, как я уже говорил, для
- 8:13:05дальнейшего рассмотрения тем, касаемо
- 8:13:07HTTP протокола, мы с вами возьмём в
- 8:13:09основу написанный в этом примере код.
- 8:13:12[музыка]
- 8:13:17В чём сейчас основная проблема нашего
- 8:13:19вот этого приложения? А проблема в
- 8:13:21следующем. Вся информация о том, что у
- 8:13:23нас происходит после входящих HTTP
- 8:13:25запросов, у нас остаётся просто в
- 8:13:27консольном выводе. Нам пришёл какой-то
- 8:13:29HTTP запрос на какой-то хендлер. Мы в
- 8:13:31ответ на этот HTTP запрос просто вывели
- 8:13:32что-то у себя на консоли. Потом нам
- 8:13:34пришёл ещё один HTTP запрос. Мы опять
- 8:13:36что-то вывели на консоли. Что же тут не
- 8:13:38так? А не так тут то, что мы эту
- 8:13:40информацию просто выводим у себя на
- 8:13:42консоли, у себя в приложении. Наше
- 8:13:44приложение вообще может быть запущено на
- 8:13:46каком-то удалённом сервере, и нам,
- 8:13:48например, с веб-странички, ну или с
- 8:13:49мобильного приложения шлются какие-то
- 8:13:51HTTP запросы, вызываются какие-то
- 8:13:52HTTPхлеры. Но вся информация о том, что
- 8:13:55происходит с нашим балансом, вся
- 8:13:56информация о том, что происходит с
- 8:13:57нашими деньгами, с нашей копилкой, она
- 8:13:59остаётся просто в консольном выводе
- 8:14:01приложения где-то там на удалённом
- 8:14:02сервере. А клиенты, которые делают эти
- 8:14:04HTTP запросы, они вообще не понимают, а
- 8:14:06что в итоге-то случилось после моего
- 8:14:08HTTP запроса. Были ли списаны деньги, не
- 8:14:11были ли списаны деньги, была ли какая-то
- 8:14:13ошибка во время списания, а если была,
- 8:14:15то какая? Какой мой новый баланс? Об
- 8:14:17этом всём клиенты не знают, потому что у
- 8:14:19клиентов нету доступа как бы к
- 8:14:21консольному выводу где-то на нашем
- 8:14:22удалённом сервере. У клиентов есть
- 8:14:24только доступ к HTTP запросам. Вот они
- 8:14:26делают HTTP запрос, передают в нём,
- 8:14:27например, 100 долларов, платят. Ну,
- 8:14:29о'кей, заплатили, у них там было 800
- 8:14:31долларов, например, стало 700. Мы об
- 8:14:33этом вывели в консоль нашем удалённом
- 8:14:34сервере. А дальше это что? Ну вот на
- 8:14:36веб-сайте что показать, какой новый
- 8:14:37баланс, какой старый баланс, что
- 8:14:39произошло после моего HTP запроса. Это
- 8:14:41всё остаётся загадкой. Почему остаётся
- 8:14:42загадкой? Да, потому что мы в нашем
- 8:14:45HTTP- ответе не отдаём никакой
- 8:14:47информации о том, что произошло после
- 8:14:49HTTP запроса. И как раз для передачи
- 8:14:51такой информации у нас и существует
- 8:14:53статус коды и тело ответа. Что такое
- 8:14:56тело HTTP ответа? Это то же самое, что и
- 8:14:58тело HTTP запроса, только в HTTP-ответе.
- 8:15:02Получается, как раз-таки в HTTP ответе
- 8:15:04мы можем передавать вот эту вот самую
- 8:15:05информацию, которую мы раньше просто
- 8:15:07выводили в консоль, благодаря чему
- 8:15:09клиенты, которые делали HTTP запросы в
- 8:15:11HTTP ответах смогут прочитать, сколько
- 8:15:14же там новый баланс, прошла ли оплата
- 8:15:16успешно или неуспешно. Ну хорошо,
- 8:15:17давайте это и сделаем. На самом деле, мы
- 8:15:19с вами уже умеем писать HTTP ответы. Мы
- 8:15:21с вами это уже делали через respons. Ну
- 8:15:23хорошо, пойдём сверху вниз. Начнём с
- 8:15:25нашего пйхендлера. И первое, когда бы мы
- 8:15:27хотели видеть какой-то HTTP-ответ- это
- 8:15:29если у нас случилась ошибка во время
- 8:15:30чтения тела HTTP запроса. Ну и после
- 8:15:32вывода на экран я буду в responsте
- 8:15:34вызывать метод WR и сюда передавать вот
- 8:15:36эту вот строку с ошибкой. Чтобы два раза
- 8:15:38её не писать, я объявлю её в отдельной
- 8:15:40переменной msg, то есть message, и сюда
- 8:15:42я просто перекопирую вот этот вот код.
- 8:15:46Тут плюс. Тут нам уже нужно вызывать
- 8:15:48метод error, потому что нам к строке
- 8:15:49нужно прибавить строку. Ну и,
- 8:15:50соответственно, print ln msg. И в
- 8:15:52репонсрайтере я буду это же самое mс
- 8:15:54записывать. Тут у нас возможны какие-то
- 8:15:56ошибки во время записи htp ответа. Я, в
- 8:15:58принципе, тоже эту ошибку буду
- 8:15:59отслеживать, потому что, ну, а вдруг она
- 8:16:00всё-таки случится? Но тут я уже не смогу
- 8:16:03эту самую ошибку записать в HTTP ответ,
- 8:16:05потому что у меня, в принципе, ошибка
- 8:16:06сама эта возникла во время попытки
- 8:16:08что-то в HTTP ответ записать. Поэтому я
- 8:16:10просто её выведу на экран.
- 8:16:13Fail to write http response.
- 8:16:17И вот эта самая ошибочка. О'кей. То же
- 8:16:20самое во время перевода HTP тела в
- 8:16:23integer.
- 8:16:25Вот это я всё скопирую в переменную MSG.
- 8:16:27Это всё запишу.
- 8:16:30выводить на экран буду MSG и точно также
- 8:16:32буду писать ответ.
- 8:16:36Всё. Теперь нам нужно сделать то же
- 8:16:37самое во время успешной оплаты.
- 8:16:40Вот она наша MSG.
- 8:16:49Мани нам нужно в строку перевести strf и
- 8:16:54вот она наша переменная мани. Всё это
- 8:16:55MSG я буду вводить на экран и также
- 8:16:57отдавать в ваше TP ответе. Если у меня
- 8:16:59будет какая-то ошибка во время попытки
- 8:17:00записать что-то вашp ответ, я буду
- 8:17:01вводить информацию об этом на экран. Ну,
- 8:17:03в принципе, то же самое мы могли бы
- 8:17:04написать в safeхендре, но тут я уже
- 8:17:06советую вам самим попробовать это
- 8:17:07сделать. Я буду всё показывать на
- 8:17:09примере пйхенра, потому что всё, что мы
- 8:17:11сейчас написали в пейхенре, всю вот эту
- 8:17:13вот обработку каких-то ошибок и записи
- 8:17:15HTTP ответа через respons, оно точно так
- 8:17:18же будет выглядеть и в saveхлере.
- 8:17:20Поэтому я сейчас не хочу тратить время
- 8:17:21на то, чтобы перекопировать просто код
- 8:17:22из одного хендлера в другой. Как раз
- 8:17:24отличная возможность вам
- 8:17:25попрактиковаться в записи вот этих вот
- 8:17:26HTTP ответов. О'кей, давайте запустим
- 8:17:28нашу программу и попробуем заплатить. То
- 8:17:31есть pay. И изначально я сюда попробую
- 8:17:32передать какую-нибудьберду. Отправляю.
- 8:17:35Опа. И вижу, что у меня вот это вот
- 8:17:36сообщение об ошибке. Оно у меня было не
- 8:17:38только написано в терминал, оно ещё и
- 8:17:40мне было передано в качестве HTTP
- 8:17:41ответа. Вот он текст, который я получил
- 8:17:43в HTTP-ответе. Это то самое тело HTTP
- 8:17:46ответа. Хорошо. Теперь давайте что-то
- 8:17:47корректное передадим. Например, 150
- 8:17:49долларов. Отправляю. Опа. И теперь у
- 8:17:51меня такая же самая история. У меня не
- 8:17:52только вывод в консоль происходит,
- 8:17:54который никак клиенты не могут увидеть.
- 8:17:56У меня ещё информация о том, что плата
- 8:17:57прошла успешно, и обновлённое количество
- 8:17:59денег в нашем кошельке. Всё это
- 8:18:00возвращается теперь и в HTTP ответе, в
- 8:18:03том числе. Благодаря чему после HTTP
- 8:18:05запроса наши клиенты смогут из HTTP
- 8:18:07ответа вычитывать эту самую информацию.
- 8:18:09Для этого нам и нужно тело ответа. Мы
- 8:18:11просто в нём можем передавать какую-то
- 8:18:13информацию, которую мы хотим отдать
- 8:18:14нашим клиентам после того, как они в нас
- 8:18:16сделали какой-то запрос. Это может быть
- 8:18:18либо какая-то опциональная информация о
- 8:18:20случившейся ошибке, либо это может быть
- 8:18:22какая-то осмысленная информация о том,
- 8:18:23какой у нас новый баланс, например, ну и
- 8:18:25так далее. Либо же, если мы опять с вами
- 8:18:27разрабатываем какой-то наш мессджер, у
- 8:18:28нас есть хендлер, который нужен для
- 8:18:30того, чтобы получить все переписки
- 8:18:32пользователя и вывести их на экран, то
- 8:18:34вот клиент делает запрос в наш хендлер,
- 8:18:36мол, я хочу получить все свои переписки,
- 8:18:38и мы ему эти переписки как раз-таки при
- 8:18:41помощи тела ответа сможем отдать. Ну, по
- 8:18:44сути говорю, тело ответа то же самое,
- 8:18:45что и тело запроса. Просто какая-то
- 8:18:47информация, которую мы можем
- 8:18:48дополнительно при помощи протокола HTP
- 8:18:49передавать, что с клиента на сервер, что
- 8:18:52с сервера обратно на клиент. В ответе.
- 8:18:54О'кей. Что же такое статус код ответа? А
- 8:18:56статус код ответа - это, если
- 8:18:58приглядеться, вот здесь вот у нас всегда
- 8:18:59есть какая-то циферка 200. Что это
- 8:19:01такое? Это как раз-таки это статус код
- 8:19:03ответа. То есть мы во время обработки
- 8:19:06HTTP запросов внутри наших хендлеров
- 8:19:09можем помимо HTTP тела ответа клиентам
- 8:19:12также отдавать ещё и статус-код.
- 8:19:14Статус-код - это условная циферка,
- 8:19:16которая обозначает то, насколько успешно
- 8:19:19или неуспешно мы с вами обработали HTTP
- 8:19:21запрос входящий. Вы можете спросить,
- 8:19:23зачем нам нужен этот статус-код, если у
- 8:19:25нас и так вся информация может быть
- 8:19:26передана в теле ответа. Да, затем, что
- 8:19:29если я отправляю свой HTTP запрос
- 8:19:30изнутри какой-то программы, то мне
- 8:19:32гораздо удобней ориентироваться именно
- 8:19:34на этот статус-код, насколько успешно
- 8:19:36или неуспешно была произведена операция.
- 8:19:38И только после того, как я пойму, была
- 8:19:40ли операция успешна или нет, после этого
- 8:19:43я могу уже вычитывать тело ответа и
- 8:19:45смотреть какую-то добавочную информацию.
- 8:19:47Ну, допустим, я делаю изнутри своей
- 8:19:48программы запрос в эээкэнд какой-нибудь
- 8:19:50социальной сети с целью получить все
- 8:19:52свои сообщения. Я первым делом, когда
- 8:19:54мне придёт HTTP ответ, я посмотрю на
- 8:19:57статус код. И только когда я узнаю, был
- 8:19:59ли обработан мой запрос успешно или нет,
- 8:20:02я смогу понять, что именно лежит в теле
- 8:20:04ответа. Допустим, мне пришёл статус код
- 8:20:06200. Это общепринятое обозначение, что
- 8:20:09HTTP запрос был успешно обработан. А
- 8:20:11значит, в теле HTTP ответа как раз-таки
- 8:20:13лежат все сообщения, которые я хотел
- 8:20:15получить. А если мне пришёл статус-код
- 8:20:18не 200, к примеру, есть такой статус-код
- 8:20:20500. 500 - это значит внутренняя ошибка
- 8:20:22сервера. То есть когда я делал свой HTP
- 8:20:25запрос, он был обработан на этом самом
- 8:20:27бэкэнде какой-то там социальной сети
- 8:20:29неуспешно, и мне вернулся статус код
- 8:20:32500, потому что у них там какая-то
- 8:20:33внутренняя ошибка произошла. И тогда я
- 8:20:35буду знать, что у меня уже в теле ответа
- 8:20:37лежат не все переписки, которые я хотел
- 8:20:39получить, а сообщение об ошибке, что
- 8:20:42именно пошло не так. Получается, всегда,
- 8:20:44когда мы с вами делаем HTTP запросы,
- 8:20:46неважно, были они сделаны изнутри
- 8:20:47постмана или были сделаны изнутри нашей
- 8:20:49программы, мы всегда получим статус код
- 8:20:52какой-то в ответе и, возможно, получим
- 8:20:54тело ответа. Возможно, никакого тела
- 8:20:56ответа не будет. Может быть, там вообще
- 8:20:57пустая строка будет. Поэтому мы всегда
- 8:20:59первостепенно должны ориентироваться на
- 8:21:02статус код ответа. И только по
- 8:21:03статус-коду ответа мы поймём, был ли наш
- 8:21:06запрос успешный или нет. И потом уже,
- 8:21:09если нам какая-то дополнительная
- 8:21:10информация из HTTP ответа нужна, мы уже
- 8:21:12тогда полезем в тело этого ответа.
- 8:21:14Например, если после успешного запроса
- 8:21:16мы получили тут все наши переписки, ну
- 8:21:18тогда мы полезем в тело ответа смотреть
- 8:21:19эти самые переписки. Либо если у нас
- 8:21:21наоборот пришёл статус код 500, что
- 8:21:23значит, что была какая-то внутренняя
- 8:21:24ошибка сервера, тогда мы полезем в это
- 8:21:26спонс тело, если захотим получить более
- 8:21:28корректное сообщение об ошибке, что
- 8:21:30именно произошло там на этом бэкэнде
- 8:21:32социальной сети. Именно поэтому тело
- 8:21:34ответа и статус код ответа, они всегда
- 8:21:36идут таким единым целым. Мы, когда
- 8:21:39делаем запрос на какой-то эээнд, мы
- 8:21:40всегда получим статус-код. Вот прямо
- 8:21:42всегда этот статус-код будет получен. А
- 8:21:44тело ответа уже опционально. Ну и,
- 8:21:46соответственно, мы сначала ориентируемся
- 8:21:48в первую очередь, насколько был успешен
- 8:21:50или неуспешен наш запрос по статус-коду
- 8:21:52ответа, а потом уже при необходимости
- 8:21:54лезем в тело ответа, чтобы посмотреть,
- 8:21:56что же там нам в итоге было передано. Ну
- 8:21:58хорошо, давайте я вот ещё раз перезапущу
- 8:21:59нашу программу, и мы посмотрим, как она
- 8:22:01работает, когда у нас какая-то ошибка
- 8:22:03случается. Например, если клиент в теле
- 8:22:06запроса передал какое-то невалидное
- 8:22:07значение, ну, например, буквы, хотя мы
- 8:22:09ожидали цифры, отправляем. Опа. И мы,
- 8:22:12несмотря на то, что в теле HTTP ответа
- 8:22:14мы получили сообщение об ошибке, статус
- 8:22:16код у нас всё равно 200. Типа, о'кей,
- 8:22:18получается, если бы мы из какой-то
- 8:22:20программы другой делали бы запрос в этот
- 8:22:22наш мини-бэкэнд, вот с таким вот
- 8:22:24некорректным телом запроса, то мы бы
- 8:22:25посмотрели на статус-код, такие: "А, ну,
- 8:22:27всё хорошо" и пошли бы дальше. И даже не
- 8:22:29узнали бы, что там на самом деле внутри
- 8:22:31в теле ответа у нас лежит сообщение об
- 8:22:33ошибке. Поэтому мы со стороны наших
- 8:22:35хендлеров в случае возникновения
- 8:22:37каких-то ошибок обязаны отправлять
- 8:22:39нестандартный статус-код ответа. Мы в
- 8:22:41статус коде ответа должны
- 8:22:42сигнализировать о том, что произошла
- 8:22:44какая-то ошибка. Ну вот, допустим, у нас
- 8:22:46тут ошибка из-за того, что у нас не
- 8:22:47получилось прочитать тело запроса. Если
- 8:22:49нам в принципе не получилось никакое
- 8:22:51тело запроса прочитать, значит это,
- 8:22:53скорее всего ошибка какая-то на стороне
- 8:22:54нашего сервера. Ну, типа, что бы там ни
- 8:22:56было, мы всё равно прочитать-то это
- 8:22:58должны уметь. А если мы не умеем,
- 8:23:00значит, что это у нас статус код 500, то
- 8:23:02есть внутренняя ошибка сервера. Давайте
- 8:23:04посмотрим, как нам этот статус-код
- 8:23:05вернуть. Я здесь до того, как ещё буду
- 8:23:08писать что-то в консоль и писать что-то
- 8:23:09в тело ответа, я воспользуюсь также
- 8:23:12responс writтером и тут напишу WR
- 8:23:13header. Уже метод WR headader у нас
- 8:23:15принимает статус-код, который я хочу
- 8:23:17написать в ответ. Статус-коды у нас
- 8:23:19лежат в пакете HTTP. Также httpstatus. И
- 8:23:22тут вот куча всяких разных статусов. Я
- 8:23:24советую вам, в принципе, в интернете
- 8:23:26загуглить HTTP status codes и
- 8:23:28посмотреть, какие вообще коды есть, что
- 8:23:31они означают, в каких случаях они
- 8:23:32возвращаются. Но мы сейчас рассмотрим
- 8:23:34вот парочку самых таких основных. То
- 8:23:36есть мы, опять же, повторюсь, если не
- 8:23:38смогли прочитать тело входящего запроса,
- 8:23:40ну, получается, это на нашей стороне
- 8:23:42ошибка, потому что чтобы тут не было
- 8:23:43передано, мы должны уметь это прочитать.
- 8:23:45Если мы не смогли это прочитать, значит,
- 8:23:47это статус internal server error. Это
- 8:23:50как раз-таки тот самый HTTP статус 500,
- 8:23:52который говорит о том, что была
- 8:23:53внутренняя ошибка сервера. И мы вот в
- 8:23:55девятнадцатой строчке будем в HTTP
- 8:23:57ответе сигнализировать о том, что
- 8:23:58произошла какая-то ошибка именно в
- 8:24:00статус-коде ответа. О'кей. И теперь
- 8:24:02давайте посмотрим, что будет, если у нас
- 8:24:04ошибка преобразования HTTP тела V
- 8:24:06integer. Тоже нам нужно тут какой-то
- 8:24:07нестандартный статус-код передавать,
- 8:24:09потому что если мы тут будем передавать
- 8:24:11статус код 200, то опять же клиент будет
- 8:24:13думать, что типа ну если 200 статус код,
- 8:24:15значит всё хорошо. Нет, я беру и через
- 8:24:17responsваю метод WR header. И тут я
- 8:24:20запишу HTTP статус BET request.
- 8:24:23Betrequest - это четырёхсотый статус.
- 8:24:26Что значит беquest? Это значит,
- 8:24:28пользователь совершил какой-то
- 8:24:29некорректный HTTP запрос. То есть мы, в
- 8:24:31принципе-то, со своей стороны смогли его
- 8:24:33полностью прочитать, этот HTTP запрос.
- 8:24:35Мы смогли перевести его там в строку, но
- 8:24:37у нас возникли проблемы с тем, чтобы эту
- 8:24:39строку перевести в число. А значит,
- 8:24:41клиент, несмотря на то, что в HTTP теле
- 8:24:43запроса должен был передать нам число,
- 8:24:46он передал что-то непонятное. Ну, это
- 8:24:48значит ошибка со стороны клиента. Вот
- 8:24:50как раз-таки бетquквест, то есть плохой
- 8:24:51запрос нам был какой-то прислан. Вот
- 8:24:53четырёхсотый статус. Ну и дальше по
- 8:24:54стандарту пишу в консоль сообщение об
- 8:24:56ошибке и в HTTP ответ пишу сообщение об
- 8:24:59ошибке. Ну, в pйхендлере у нас ещё есть
- 8:25:00ситуации, когда у нас не получилось
- 8:25:02записать HTTP ответ. Но тут прикол в
- 8:25:05том, что если мы с вами пишем HTTP ответ
- 8:25:07через w метод responsтера, мы после него
- 8:25:10уже не сможем записать какой-то
- 8:25:12HTTPстатус-код в ответе, потому что
- 8:25:14метод WR требует того, если посудить по
- 8:25:16описанию, чтобы до его вызова мы уже
- 8:25:19где-то записали HTTP статус ответа.
- 8:25:21Иначе, если нигде не будет записан
- 8:25:23статус ответа, тогда по умолчанию будет
- 8:25:25выбран двухсотый статус, статус ОК. Ну
- 8:25:27и, в принципе, по логике, если у нас тут
- 8:25:29возникла ошибка во время записи в HTTP
- 8:25:31ответ HTTP тело, ну, навряд ли у нас
- 8:25:33тогда получится записать HTPстатус.
- 8:25:34Поэтому тут я как-то дополнительно над
- 8:25:36статус-кодами не буду заморачиваться. В
- 8:25:38принципе, мы уже везде, благодаря нашим
- 8:25:40статус-кодам сигнализируем о том, что
- 8:25:41какие-то ошибки у нас могли возникнуть.
- 8:25:43Сигнализируем о том, что клиент отправил
- 8:25:45некорректный запрос. Ну, если всё было
- 8:25:46хорошо, то у нас по умолчанию тут
- 8:25:47запишется именно статус ОК, то есть
- 8:25:49двухсотый ответ, то есть всё хорошо. Ну,
- 8:25:51сохраняю файл. Давайте перезапущу
- 8:25:53программу нашу. И теперь посмотрим, как
- 8:25:54она работает. Изначально я отправлю
- 8:25:56корректное тело запроса, и я вижу, что у
- 8:25:59меня 200 статус ответа. И в теле ответа
- 8:26:01у меня дополнительная информация о том,
- 8:26:02что оплата прошла успешно. И там новый
- 8:26:04баланс, мой 900 долларов. Хорошо, теперь
- 8:26:06я попробую передать какую-то фигню в
- 8:26:08теле запроса. Отправляю. Опа. И вижу,
- 8:26:11что статус-код у меня теперь 400.
- 8:26:13Почему? Да потому что я его вот здесь
- 8:26:15вот в тридцать четвёртой строчке
- 8:26:16записал. И теперь, если я какой-то
- 8:26:18программой буду описывать вот эти HTTP
- 8:26:20запросы, я сделаю этот HTTP запрос, и я
- 8:26:22получу 400 статус-код. Что значит, что
- 8:26:25произошла какая-то ошибка во время
- 8:26:27запроса? А именно, если 400 статус код,
- 8:26:29это значит, что какая-то ошибка с моей
- 8:26:32стороны, со стороны клиента, который
- 8:26:33этот запрос делал. То есть BТqu - это
- 8:26:35какой-то неверно оформленный запрос. И
- 8:26:37потом уже в HTT теле ответа я смогу
- 8:26:39более конкретно узнать, что именно пошло
- 8:26:41не так. Я узнаю, что не получилось
- 8:26:43преобразовать HTTP тело запроса вд. Ну и
- 8:26:46тут более конкретная информация, что вот
- 8:26:47это fds ла-ла-ла неправильный синтаксис
- 8:26:50имеет. Ну то есть это не число. О'кей.
- 8:26:52Отныне мы знаем, что такое тело ответа и
- 8:26:54что такое статус-код ответа. Поговорили
- 8:26:56о том, для чего они нужны, как они
- 8:26:58используются и как со стороны HTTP
- 8:27:00сервера нам выставлять разное тело
- 8:27:02ответа и разные статус-коды ответа.
- 8:27:06[музыка]
- 8:27:10Следующим шагом мы рассмотрим хедеры.
- 8:27:12Что такое хедеры в HTTP протоколе и для
- 8:27:15чего они нам вообще могут быть нужны? Ну
- 8:27:17вот я в самом-самом начале нашего
- 8:27:19йхендлера обращусь к входящему HTTP
- 8:27:21запросу и в нём я возьму поле. Это поле
- 8:27:25- это на самом деле мапа,
- 8:27:28у которой есть ключи и значения. Ну
- 8:27:30давайте я тогда в цикле for выведу все
- 8:27:31ключи и значения, которые в этой мапе
- 8:27:33есть. For K value range R то header. То
- 8:27:38есть по этой мапе этих самых хедеров,
- 8:27:40как раз-таки это те самые наши хедеры, о
- 8:27:42которых мы сейчас говорим. Я пройдусь и
- 8:27:44выведу на экран все ключи и значения.
- 8:27:46Kвоточие K, потом через 2 тире value,
- 8:27:49двоеточие и тут value. Ну, в принципе, в
- 8:27:51остальном программу я менять не буду и
- 8:27:54перезапущу её. Перезапустил. Отправляю
- 8:27:56опять наш какой-нибудь запрос любой
- 8:27:58вообще. Опа. И я вижу, что в консоль,
- 8:28:01как раз-таки благодаря вот этому циклу я
- 8:28:03вывел все ключи и значения, которые у
- 8:28:05меня в этой мапе Header лежали. Что тут
- 8:28:06есть? Тут, на самом деле, по большому
- 8:28:08счёту, зашита всякая служебная
- 8:28:10информация. Ну, например, у нас есть
- 8:28:12контент type, то есть это тип контента,
- 8:28:15который был нам отправлен в http
- 8:28:17запросе, а точнее в теле HTTP запроса.
- 8:28:19Например, сейчас видите у нас текст. Это
- 8:28:21так называемый Plтекст. Это самый
- 8:28:23обычный текст, который был отправлен.
- 8:28:24Также тут есть ещё другие форматы,
- 8:28:26например, jonent
- 8:28:30type стал application Json. Могу тут
- 8:28:33отправить, например, XML. Опа, теперь
- 8:28:35XML. То есть в хедерен Type у нас просто
- 8:28:37передаётся тип контента, который нам был
- 8:28:39отправлен в теле HTTP запроса, благодаря
- 8:28:42чему мы изнутри пйхендлера сможем
- 8:28:43узнать, что именно в теле запроса лежит,
- 8:28:46ну и делать какие-то выводы на основании
- 8:28:47этого, как работать с переданной нам
- 8:28:49информацией. Ну о'кей. Смотрим дальше,
- 8:28:51что тут есть. Есть, например, usеeragent
- 8:28:53header значение postmrunant time. То
- 8:28:55есть мы благодаря хедерую usеeragent
- 8:28:57знаем, что HTTP клиентом, который нам
- 8:28:59отправил этот HTTP запрос, был Postman.
- 8:29:02Ну и так далее. То есть в HTTP хедерах
- 8:29:03обычно зашита всякая служебная
- 8:29:05информация. Либо ещё довольно частый
- 8:29:07сценарий, когда в этих HTTP хедерах
- 8:29:09зашит какой-то токен. Благодаря этому
- 8:29:11токену мы с вами внутри пйхендлера можем
- 8:29:13узнавать, кто нам отправил запрос. Нужно
- 8:29:15это в тех случаях, когда вот у нас есть
- 8:29:16какой-то кошелёк, он принадлежит
- 8:29:18пользователю X, а пользователь Y просто
- 8:29:20берёт и на наше с вами ээнд- приложение
- 8:29:22делает запрос: "Мол, соверши покупку".
- 8:29:25Но у пользователя Y не должно быть
- 8:29:26доступа к кошельку пользователя X. И как
- 8:29:29раз-таки в HTTP хедере вместе с HTTP
- 8:29:31запросом передаётся токен авторизации,
- 8:29:33так называемый. И наш хендлер уже внутри
- 8:29:35смотрит, принадлежит ли это токен
- 8:29:36авторизации пользователю X. И если да,
- 8:29:39тогда запрос проводится. Если нет, тогда
- 8:29:41мы отвечаем, что у вас нет доступа для
- 8:29:43проведения данной операции. Ну, это уже,
- 8:29:44на самом деле, довольно продвинутая тема
- 8:29:46с авторизацией и аутентификацией HTTP
- 8:29:48запросов. Нужно это, опять же, для того,
- 8:29:49чтобы люди, у которых нет доступа к тем
- 8:29:51или иным ресурсам, не могли этот доступ
- 8:29:53получить, чтобы я не мог получить доступ
- 8:29:54к вашим перепискам, сделав просто
- 8:29:56правильный запрос на бэкэнд ВКонтакте.
- 8:29:58Ну и чтобы вы не могли получить доступ к
- 8:29:59моим перепискам. Мы с вами рассмотрим
- 8:30:01эту тему попозже, но сейчас просто на
- 8:30:02словах можем обсудить, что подобного
- 8:30:04рода токены также могут отправляться в
- 8:30:06HTTP хедерах. То есть хедеры - это
- 8:30:07просто какая-то дополнительная служебная
- 8:30:09информация, которая нам помогает понять,
- 8:30:11какого типа контент нам был прислан,
- 8:30:13какой клиент был инициатором этого HTTP
- 8:30:15запроса, возможно, какие-то токены
- 8:30:16авторизации у нас там ещё могут быть, ну
- 8:30:18и так далее, и так далее, и так далее.
- 8:30:19Плюс мы можем вообще свои хедеры
- 8:30:21кастомные добавлять, если нам это нужно.
- 8:30:23Вот здесь вот в постнее есть слева от
- 8:30:25кнопки Body кнопка headers. Ну и тут вот
- 8:30:27они все наши хедеры. Если они у вас не
- 8:30:29видны, то, скорее всего, вот сюда вот
- 8:30:30нужно нажать. Оп, и вот они появляются.
- 8:30:32Ну и тут вот мы можем свой какой-то
- 8:30:33кастомный headдер добавить. Назову его
- 8:30:35My header. И значение у него будет Hello
- 8:30:37World.
- 8:30:40Теперь я отправляю HTP запрос и слева
- 8:30:43смотрю, что мне пришёл помимо прочих ещё
- 8:30:45и header My header. Вот он. И значение
- 8:30:47Hello World. Ну пока что мы как-то
- 8:30:48супесмысленно эти хедеры заиспользовать
- 8:30:50с вами не сможем, потому что перед нами
- 8:30:52ещё не встала задача, которая бы
- 8:30:53потребовала как-то использовать эти
- 8:30:54хедеры. Поэтому в данный момент просто
- 8:30:56на словах обсуждаем, что такое хедер,
- 8:30:57как мы можем свой хедер добавить. Ну и
- 8:30:59какой-то примерный вектор использования
- 8:31:00этих самых хедеров.
- 8:31:02[музыка]
- 8:31:07Нам осталось обсудить, что такое HTTP
- 8:31:09метод в запросе. Ну и давайте не будем
- 8:31:10затягивать. Сначала посмотрим, как
- 8:31:12вообще этот метод выглядит. А вот здесь
- 8:31:14вот, когда мы с вами вводили адрес, куда
- 8:31:15мы хотим отправить свой HTTP запрос,
- 8:31:17слева была ещё такая менюшка, где всякие
- 8:31:20разноцветные буковки. Так вот, эти самые
- 8:31:22разноцветные словечки - это и есть
- 8:31:23разные HTTP методы. Смотрим, тут есть
- 8:31:25метод get, метод post, метод put, patch
- 8:31:28delete, head и options. На самом деле их
- 8:31:30немножко больше, но тут такие самые
- 8:31:32основные собраны. Что это вообще за
- 8:31:34такое? Для чего оно нам надо? Мы каждый
- 8:31:36раз вместе с нашим запросом помимо тела
- 8:31:39запроса, помимо хедера в запросе
- 8:31:41отправляем ещё и метод запроса. Давайте
- 8:31:44я вот здесь вот в самом начале нашего
- 8:31:45пейхенра вместо хедеров, которые нам
- 8:31:47были переданы в запросе, буду выводить
- 8:31:49HTTP метод, который к нам пришёл. http
- 8:31:52метод через двоеточие. Ну а где я могу
- 8:31:54этот метод получить? Конечно же, в нашем
- 8:31:56входящем HTTP реквесте. Повторюсь, вот в
- 8:31:58этом аргументе нам приходит вообще вся
- 8:32:00инфа о входящем HTTP запросе. Это тело
- 8:32:04запроса, это все хедеры запроса, также
- 8:32:06это метод запроса. Ну, по умолчанию
- 8:32:08всегда стоит метод запроса get. Мы его и
- 8:32:11ожидаем увидеть R тоthod. И вот тут вот,
- 8:32:14получается, в нашем реквесте будет этот
- 8:32:15самый метод запроса зашит. Давайте
- 8:32:17перезапущу нашу программу и опять сделаю
- 8:32:19наш запрос. Отправлю. Отправляю и вижу в
- 8:32:21консольном выводе HTTP метод get. Ну,
- 8:32:23тут у нас ещё и ошибка выводится
- 8:32:25постоянно, да? И 400 ещё Brequest статус
- 8:32:27код возвращается. Это потому что у нас в
- 8:32:29HTTPле какая-то фигня. У нас тут должен
- 8:32:31быть обычный текст и обычная циферка.
- 8:32:33Вот тогда всё будет хорошо. Оп, всё,
- 8:32:34статус код 200, оплата прошла успешно.
- 8:32:36Ну и видим, выводится, что HTTP метод у
- 8:32:38нас был get. Давайте теперь в постне я
- 8:32:40выберу HTP метод post. Отправляю запрос.
- 8:32:43Ну и нам как бы изнутри пайхенра теперь
- 8:32:45видно, что к нам был отправлен запрос с
- 8:32:47HTTP методом Post. Что угодно могу тут
- 8:32:49выбрать. Put patch delete. Давайте вот
- 8:32:51delete выберу. Отправляю и вижу, что
- 8:32:53теперь нам в HTTP методе внутрь нашего
- 8:32:55пйхендлера пришёл метод delete. Ну это
- 8:32:57всё круто, замечательно, но тут вопрос
- 8:32:59встаёт, зачем нам вообще нужны эти самые
- 8:33:01HTTP методы? Мы меняем этот метод, но
- 8:33:04внутри нашей программы ничего не
- 8:33:05меняется. Мы ровно так же обрабатываем
- 8:33:07все входящие запросы, как и
- 8:33:08обрабатывали. Просто мы теперь знаем,
- 8:33:10что нам был сделан запрос с методом get
- 8:33:12или с методом Postст или с методом
- 8:33:13Delete или с любым другим методом. Ну а
- 8:33:15что это меняе? Для чего это надо? Если
- 8:33:18честно, я сам до конца не понимаю, для
- 8:33:19чего нам нужны эти самые методы в HTTP
- 8:33:22запросе. Я понимаю идею, я понимаю, что
- 8:33:24хотели сделать авторы протоколы HTTP,
- 8:33:27когда добавляли туда эти самые HTTP
- 8:33:29методы. Но по факту, поработав в
- 8:33:31коммерческой разработке какое-то время,
- 8:33:33я увидел, что очень многие просто
- 8:33:35пренебрегают этими самыми HTTP методами.
- 8:33:38Поэтому для того, чтобы ответить на
- 8:33:39вопрос, зачем нам нужны HTTP методы, я
- 8:33:41обращусь к нейросети чат ГПТ и напрямую
- 8:33:44у неё спрошу: "Привет, подскажи,
- 8:33:46пожалуйста, для чего конкретно нам нужны
- 8:33:50HTTP методы в запросе, если мы можем
- 8:33:54спокойно обойтись и без них? Давайте
- 8:33:58посмотрим, что она нам ответит". Ну, нам
- 8:34:00тут говорится, в принципе, то, что мы
- 8:34:01только что обсудили, что у нас есть
- 8:34:03какие-то разные HTP методы. Зачем они
- 8:34:05нужны? Разделение логики. разные методы
- 8:34:07несут разные цели. get - это получить
- 8:34:10данные, пост создать, put изменить,
- 8:34:13delete удалить. Но это довольно странное
- 8:34:16утверждение, потому что я просто могу
- 8:34:18создать несколько разных хендлеров на
- 8:34:20разные паттерны, на разные эндпоинты и
- 8:34:23отправлять запросы на эти разные
- 8:34:24эндпоинты, тем самым и получая то самое
- 8:34:26разделение логики. Вот тут вот я
- 8:34:27благодаря разным паттернам, разным
- 8:34:29эндпоинтам разделил логику проведения
- 8:34:31какой-то оплаты и логику сохранения
- 8:34:33денег в копилку. Разделил. Разделил.
- 8:34:36HTTP метод нам тогда зачем? Ну, не
- 8:34:38совсем понятно. Хорошо, идём дальше.
- 8:34:40Безопасности кэширования. Какие-то
- 8:34:41браузеры умеют по-разному обрабатывать
- 8:34:43запросы. Какие-то запросы могут
- 8:34:45кэшироваться, ну, а какие-то не могут.
- 8:34:47Ну, хорошо, возможно, тут есть какое-то
- 8:34:49место быть, но мы не понимаем, какие
- 8:34:51браузеры, какие прокси, как кэшируют,
- 8:34:53тоже, ну, что-то такое довольно
- 8:34:54эфимерное. Третий пункт- идеаматичность
- 8:34:56AP. AP - это, по сути, набор вот этих
- 8:34:58вотпоинтов, набор вот этих хендлеров,
- 8:35:00которые мы с вами описываем. Это есть
- 8:35:01AP. Хорошо. То есть HTTP методы нам
- 8:35:03обеспечивают какую-то идиоматичность,
- 8:35:05что у нас наша IP, получается, наши
- 8:35:07эндпоинты все становятся читаемыми,
- 8:35:09предсказуемыми и логичными. Но они и так
- 8:35:12читаемые, и так предсказуемые, и так
- 8:35:14логичные. Я просто говорю, что если ты
- 8:35:16отправляешь запрос на сPay и передаёшь
- 8:35:18какое-то HTTP тело, то это будет
- 8:35:20проведение оплаты. Ну логично. Логично.
- 8:35:22Если ты отправляешь что-то на
- 8:35:23паттер/save, тогда ты будешь переносить
- 8:35:26деньги в копилку. Ну, в принципе, и так
- 8:35:27всё предсказуемо и логично. Но опять же
- 8:35:29вопрос, зачем нам тогда HTTP методы
- 8:35:30нужны? Ну, в общем, мы так и дальше
- 8:35:32можем с вами рассматривать все эти
- 8:35:33доводы. Я вам честно скажу, я тоже
- 8:35:36когда-то думал, что вот эти HTTP методы
- 8:35:38- это очень что-то важное, но я просто
- 8:35:39своими глазами увидел, что в
- 8:35:41коммерческой разработке где-то в
- 8:35:43половине случаев кладётся огромный болт
- 8:35:45на вот эти вот HTTP методы и просто всё
- 8:35:47обрабатывается в каком-нибудь пост. То
- 8:35:49есть автоматически в постмане у всех
- 8:35:51запросов стоит пост и всё. А изнутри
- 8:35:53HTTPхдлеров, которые написаны на гошке,
- 8:35:56вообще никто не отслеживает, какой там
- 8:35:57метод им был отправлен. Это не значит,
- 8:36:00что про HTTP методы нам можно не знать.
- 8:36:02Нет, знать про них надо. На
- 8:36:03собеседованиях часто спрашивают. И так
- 8:36:05или иначе всё ещё большое количество
- 8:36:06кода опирается на эти HTTP методы,
- 8:36:09поэтому знать про них всё-таки
- 8:36:10необходимо. Но давайте попробуем ещё раз
- 8:36:11обсудить все эти HTTP методы, как будто
- 8:36:14бы на них не забивается большой болт в
- 8:36:15половине случаев. О'кей. У нас вместе с
- 8:36:17запросом, помимо телозапроса, помимо
- 8:36:20хедеров, отправляется ещё и метод
- 8:36:22запроса. Для чего вообще задумывались
- 8:36:24эти методы? Задумывались они для
- 8:36:25следующего, что если клиент делает
- 8:36:28какой-то запрос и выбирает метод get,
- 8:36:31это значит, что он хочет с нашего
- 8:36:32бэкэнда какие-то ресурсы получить, не
- 8:36:35создать новые, не обновить уже
- 8:36:37существующие, не удалить, а именно
- 8:36:39получить. Опять же пример, социальная
- 8:36:42сеть, какие-то переписки. И если я кидаю
- 8:36:45HTTP запрос с методом GET, это значит, я
- 8:36:47хочу эти переписки просто получить. Я не
- 8:36:49хочу создать новое сообщение. Я не хочу
- 8:36:51изменить уже существующее. Я не хочу
- 8:36:53удалить какие-то сообщения. Нет, я хочу
- 8:36:55просто получить его. И тем самым, что я
- 8:36:57в HTTP запросе передаю метод именно GET.
- 8:37:00Я явно со стороны клиента говорю, что я
- 8:37:03хочу от сервера просто получить какую-то
- 8:37:04инфу. Хорошо, если я выбираю метод пост,
- 8:37:07это говорит о том, что я хочу создать на
- 8:37:09сервере новый ресурс. В случае с
- 8:37:11мессенджером это создать какое-то новое
- 8:37:13сообщение, не получить список уже
- 8:37:15существующих, не изменить существующее,
- 8:37:18не удалить существующее, именно создать
- 8:37:21новое я хочу. И я сигнализирую об этом
- 8:37:24тем, что в HTTP запросе я выбираю метод
- 8:37:26пост, либо, например, метод pat патч.
- 8:37:28Если я выбираю метод pat патч, это
- 8:37:30значит, что я хочу изменить уже
- 8:37:31существующий ресурс на сервере. То есть
- 8:37:34в случае с мессенджером я хочу
- 8:37:35отредактировать какое-то уже
- 8:37:36существующее сообщение. Если я выбираю
- 8:37:39Delete, то, очевидно, я хочу удалить
- 8:37:40какое-то уже существующее сообщение. Но
- 8:37:42мало выбрать этот HTTP метод в запросе
- 8:37:45со стороны клиента, я должен ещё и со
- 8:37:47стороны сервера внутри наших хендлеров
- 8:37:49отслеживать, какой именно метод мне был
- 8:37:51прислан. И если вдруг был прислан
- 8:37:54неверный метод, тогда возвращать ошибку.
- 8:37:56Ну, к примеру, йхнler. Что это такое? Мы
- 8:37:58в пейхендлере, по большому счёту, просто
- 8:38:01вычитаем наши деньги. По сути, мы меняем
- 8:38:03уже существующий ресурс. Это значит, что
- 8:38:05я должен предоставлять свой хендлер
- 8:38:07только на HTTP метод patch. Изменение
- 8:38:10уже существующего ресурса. Получается, я
- 8:38:12вот выберу именно payпрос. Я должен
- 8:38:14предоставлять этот Payхandler только на
- 8:38:16httpthд patch. Вот он HTTP метод patch,
- 8:38:19то есть изменение уже существующего
- 8:38:21ресурса. Ну хорошо, то есть нам
- 8:38:23отправляется тут метод pat патч, и я
- 8:38:24должен в начале этого пхенра смотреть,
- 8:38:26мол, если у меня request метод не равно
- 8:38:30http тоp patch, вот тут метод patch -
- 8:38:32это переменная, которая по сути хранит
- 8:38:34себе название этого метода. То есть если
- 8:38:36мне был передан не метод pat патч, тогда
- 8:38:38я в http ответ, в статус код ответа буду
- 8:38:41записывать специальный статус-код,
- 8:38:43который нужен для того, чтобы говорить,
- 8:38:44что клиент сделал запрос с неверным HTTP
- 8:38:47методом. Это статус method not allowad.
- 8:38:50405 статус-код говорит о том, что клиент
- 8:38:53сделал запрос с неверным каким-то
- 8:38:54методом. Ну и, в принципе, завершу свой
- 8:38:56хендер. Давайте запущу программу. Ну и
- 8:38:58теперь попробую отправить вот этот
- 8:39:00запрос/pay на httpthд get, например.
- 8:39:03Опа. И я вижу, у меня 405 ошибка. Метод
- 8:39:05not allow. О'кей. И теперь я должен
- 8:39:08осознанно со стороны HTTP клиента
- 8:39:10выбрать метод патч. Тем самым я как бы
- 8:39:13сам себе подтверждаю то, что я уверен,
- 8:39:16что когда я делаю свой запрос, я именно
- 8:39:18рассчитываю изменить уже существующий на
- 8:39:20сервере ресурс. И то, что я
- 8:39:22дополнительно себе отдаю отчёт в том,
- 8:39:24что я могу этим запросом что-то там на
- 8:39:25сервере поменять. Ну и давайте теперь я
- 8:39:27делаю запрос. Оп. И вижу, оплата прошла
- 8:39:29успешно. И двухсотый статус код в
- 8:39:31ответе. Ну, соответственно, какой бы
- 8:39:32метод я тут кроме патч не выбрал, я не
- 8:39:35смогу провести оплату. Мне будет
- 8:39:36говориться, что 405 у меня ошибка, 405
- 8:39:39статус-код в ответе. Метод not allow. То
- 8:39:41есть я выбрал неверный HTTP метод во
- 8:39:43время HTTP запроса. Ну и таким образом я
- 8:39:45должен следить, что именно мой хендлер
- 8:39:46делает с ресурсами на сервере, и ставит
- 8:39:48соответствующие проверки в начале
- 8:39:50каждого хендлера, соответствует ли HTTP
- 8:39:52метод во входящем HTTP запросе тому, что
- 8:39:55я ожидаю внутри хендлера. И если этот
- 8:39:58метод не соответствует тому, что я
- 8:39:59ожидаю, например, у меня требуется метод
- 8:40:01pat патч, то есть изменение уже
- 8:40:02существующего ресурса, а мне сюда
- 8:40:04передают метод get. Значит, наверное,
- 8:40:06клиент, когда отправлял вот этот запрос,
- 8:40:09он подумал, что этот запрос просто
- 8:40:11вернёт ему какие-то ресурсы с сервера,
- 8:40:14например, вернёт ему текущий баланс. Но
- 8:40:16нет, клиент этим запросом он поменяет
- 8:40:18существующий баланс и произведёт оплату.
- 8:40:20У него спишутся деньги вот с пятьдесят
- 8:40:22четвёртой строчке. А если он отправляет
- 8:40:23метод get, наверное, он не подозревает
- 8:40:25об этом. Тогда я верну ему метод not
- 8:40:27allow, чтобы он ещё раз подумал над тем,
- 8:40:29какой именно метод он выбирает, и точно
- 8:40:30ли он собирается сделать именно то, что
- 8:40:32он по факту может сделать, если бы я не
- 8:40:34проверил его метод. Ну, звучит всё
- 8:40:36круто, типа дополнительная какая-то
- 8:40:37защита от дурака и всё прочее, но по
- 8:40:40факту могу вам сказать, что очень много
- 8:40:42где вообще никакие такие проверки не
- 8:40:44устраиваются, а все запросы отправляются
- 8:40:47с методом пост и всё, типа, ну, HTTP
- 8:40:49методы всё закончились, короче, не
- 8:40:51используют их, но не везде. Где-то
- 8:40:53действительно подходят ответственно к
- 8:40:55этому моменту и действительно проверяют
- 8:40:57входящие запросы, входящие методы этих
- 8:41:00запросов и отправляют метод not allow,
- 8:41:02если метод не подходит. Вообще
- 8:41:04желательно всегда придерживаться вот
- 8:41:06этого подхода, когда мы действительно
- 8:41:07проверяем входящие методы у входящих
- 8:41:09HTTP запросов, но это не что-то такое,
- 8:41:11без чего мы не смогли бы жить. Нет, в
- 8:41:13принципе, спокойно и без этого мы можем
- 8:41:15реализовывать наш бэкэнд. Просто лучше и
- 8:41:17более правильно с точки зрения каких-то
- 8:41:19общепринятых стандартов не забывать о
- 8:41:22существовании HTV методов. Всё-таки,
- 8:41:24как-никак, это действительно
- 8:41:25дополнительная защита от дурака, которая
- 8:41:27рано или поздно может начать приносить
- 8:41:29свои плоды. Но я могу нас с вами
- 8:41:30поздравить. Мы разобрали на базовом
- 8:41:32уровне HTTP протокол, много всего про
- 8:41:35него узнали. Мы узнали, что нам для
- 8:41:37того, чтобы сделать HTTP запрос, нужно
- 8:41:39знать домен или IP-адрес сервера, на
- 8:41:41который мы делаем запрос. Либо, если мы
- 8:41:43хотим сделать запрос на самих себя с
- 8:41:45целью, например, тестирования, тогда мы
- 8:41:47можем просто выбрать домен local host. И
- 8:41:49мы будем делать запрос не на какой-то
- 8:41:51удалённый сервер, а на самих себя. Мы
- 8:41:53узнали, что для того, чтобы совершить
- 8:41:54HTTP запрос, нам нужно знать порт, на
- 8:41:57котором развёрнуто приложение, в которое
- 8:41:58мы будем запрос делать. Нам нужно знать
- 8:42:00patтерn или же endpint, чтобы в
- 8:42:02удалённом приложении вызвать именно ту
- 8:42:04функциональность, которую мы хотим. И мы
- 8:42:06узнали, что для полноценного общения
- 8:42:07между клиентом и сервером в протоколе
- 8:42:09HTTP предусмотрены следующие вещи. Это
- 8:42:11тело запроса для того, чтобы какую-то
- 8:42:13дополнительную информацию в запросе
- 8:42:14передавать. Например, текст
- 8:42:16отправляемого сообщения или сумма
- 8:42:18оплаты. У нас в HTTP ответе есть тело
- 8:42:21ответа и статус-код ответа. Тело ответа
- 8:42:23и статус-код ответа нам позволяют
- 8:42:25понять, как именно прошёл наш HTTP
- 8:42:27запрос, был он успешный или нет. Если
- 8:42:30неуспешный, то что именно произошло и по
- 8:42:32каким причинам наш запрос был
- 8:42:33неуспешный. Ну и в теле ответа нам
- 8:42:35всякая дополнительная информация
- 8:42:36приходит. Ровно так же, как и в теле
- 8:42:37запроса, мы эту дополнительную
- 8:42:39информацию отправляем. Поговорили про
- 8:42:40хедеры, поговорили про HTTP методы
- 8:42:42запроса, и теперь мы с вами в целом
- 8:42:44готовы начинать использовать HTTP
- 8:42:46протокол для того, чтобы писать наше
- 8:42:48приложение.
- 8:42:50[музыка]
- 8:42:54Далее нам с вами просто необходимо
- 8:42:56поговорить про такой инструмент, как
- 8:42:58Gon. Узнаем, что это такое, как
- 8:42:59используется, зачем нужен. Ну, в общем,
- 8:43:01поехали. Вообще, Jon, как и всё, что мы
- 8:43:03прошли с вами до этого - это, в первую
- 8:43:05очередь инструмент, который решает
- 8:43:07какую-то задачу. То есть перед нами, как
- 8:43:09перед разработчиками, встаёт какая-то
- 8:43:11проблема, и у нас находится инструмент,
- 8:43:13который нужен для того, чтобы эту
- 8:43:14проблему решать. В каких-то случаях нам
- 8:43:16с вами проще сначала узнать, как
- 8:43:17работает тот или иной инструмент, а уже
- 8:43:19только потом смотреть, какую проблему он
- 8:43:20решает, как у нас было в случае с
- 8:43:22интерфейсами. А иногда нам проще сначала
- 8:43:24узнать, какую именно проблему решает
- 8:43:26инструмент, а только потом уже обсуждать
- 8:43:28то, как этот инструмент работает. Ну,
- 8:43:29Jon - это у нас как раз-таки второй
- 8:43:31случай. Нам сначала проще увидеть
- 8:43:33проблему, а потом уже смотреть, каким
- 8:43:35образом GSON нам помогает эту проблему
- 8:43:37решать. Ну, как обычно, будем
- 8:43:38рассматривать всё на примерчике.
- 8:43:40Представим, что мы с вами описываем
- 8:43:42приложение, которое является модулем
- 8:43:44проведения оплат. По сути, тут явно
- 8:43:46указано, что это упрощённый модуль
- 8:43:48проведения оплаты. То есть, по большому
- 8:43:49счёту, это просто такой небольшой
- 8:43:51тестовый примерчик. Что наш модуль
- 8:43:53проведения оплат будет предоставлять
- 8:43:55клиентам? Одинственный endpint, один
- 8:43:57pattern/pay, который будет принимать два
- 8:44:00значения. То есть в теле HTTP запроса
- 8:44:02должно быть указано следующее.
- 8:44:04Во-первых, это описание покупки, а
- 8:44:06во-вторых - это сумма покупки. То есть,
- 8:44:08когда кто-то, какой-то клиент захочет
- 8:44:10через наш модуль оплаты провести оплату,
- 8:44:12он должен передать в своём запросе, в
- 8:44:14теле запроса описание покупки и сумму
- 8:44:16покупки, благодаря чему я со своей
- 8:44:18стороны, со стороны эээнд приложения,
- 8:44:20которое представляет собой модуль
- 8:44:21проведения оплат, буду знать, что именно
- 8:44:24хочет купить пользователь и на какую
- 8:44:25сумму. Значит, сумма покупки будет
- 8:44:27списываться с нашего какого-то денежного
- 8:44:29баланса, который будет представлен
- 8:44:30переменной мане. Также все наши покупки
- 8:44:32будут сохранены в истории покупок. Ну,
- 8:44:35допустим, изначально пользователь купил
- 8:44:37игру на ПК на 70 долларов, и вот это у
- 8:44:39нас положилось в историю покупок. Потом
- 8:44:42пользователь купил телефон на 350
- 8:44:44долларов. Это тоже у нас отразилось в
- 8:44:46истории покупок. Ну и потом пользователь
- 8:44:48после успешного шопинга ещё зашёл в
- 8:44:49какой-нибудь фаastфуд покушать и купил
- 8:44:51себе бургер на 5 долларов. Также это у
- 8:44:53нас отразилось в истории покупок. В
- 8:44:54целом вот и весь модуль проведения
- 8:44:56оплат, который нам с вами нужно
- 8:44:57реализовать. 1 endpint/pay, который в
- 8:44:59теле HTTP запроса должен принимать
- 8:45:00описание покупки и сумму покупки. Сумма
- 8:45:02покупки списывается с переменной мании.
- 8:45:04А в целом вся информация о проведённой
- 8:45:06покупке, она будет сохраняться в историю
- 8:45:07покупок. Ну, история покупок, сразу могу
- 8:45:09сказать издалека, это будет, скорее
- 8:45:11всего, просто самый обычный слайс, в
- 8:45:12который мы будем оппендить новые вот эти
- 8:45:14вот наши информации о покупках. Ну что,
- 8:45:16предлагаю начать писать код. Ну что,
- 8:45:18приступаем к написанию кода. Слева вы
- 8:45:19видите у меня полностью пустой проект.
- 8:45:21Ну и начнём писать код. Создаю main.
- 8:45:24Тут, конечно же, главный пакет и главная
- 8:45:26функция.
- 8:45:28Так как у нас backend приложение
- 8:45:29планируется довольно простое, всего лишь
- 8:45:31на один endpint. Этот самый endpint
- 8:45:33будет работать всего лишь с двумя
- 8:45:34переменными, поэтому я тут не вижу
- 8:45:36смысла объявлять как-то себя как модуль
- 8:45:38для того, чтобы работать с какими-то
- 8:45:40сторонними библиотеками, для того, чтобы
- 8:45:41использовать пакеты в своём приложении.
- 8:45:43Не, я всё напишу в main. Ну серьёзно,
- 8:45:46кода тут совсем немного будет. О'кей,
- 8:45:47начинаем описывать наш payх handlerр.
- 8:45:49Хендлер, который будет обрабатывать
- 8:45:50patтерn/pay. Начинаю писать функцию,
- 8:45:52назову это, в принципе, payler, ничего
- 8:45:54нового. И тут при помощи WR воспользуюсь
- 8:45:57снипетом и объявлю сразу сигнатуру
- 8:45:59функции. Ну и, конечно же, давайте я
- 8:46:00вскрою проводник в эс-коде, потому что
- 8:46:02тут у нас всё равно один единственный
- 8:46:03main go файл, а вот так зато побольше
- 8:46:05кода будет видно. Ну, сохраняю файл.
- 8:46:07Автоматически импортируется мой пакет на
- 8:46:09http.
- 8:46:10И вот он мой payх handler. Сразу же
- 8:46:12зарегистрирую его в нашем приложении как
- 8:46:14http handler. http handle funk. Patтер у
- 8:46:17меня будет/pay. Ну и хле. Вот он наш
- 8:46:19payх handler. После этого сразу же опишу
- 8:46:21логику по запуску HTTP сервера. Listen
- 8:46:24and Surf на 90.91 порту. Второй аргумент
- 8:46:27мы по классике пока не используем. И
- 8:46:28если у нас ошибка не равна Нил, тогда
- 8:46:30буду вводить информацию об этом на
- 8:46:32экран. Ошибка во время работы HTTP
- 8:46:36сервера. Ну и вот наша ошибка. Вообще,
- 8:46:39на самом деле, я думаю, мы сейчас с вами
- 8:46:40уже готовы узнать немножечко новое про
- 8:46:42синтаксис Гошки. Когда у нас в Гошке
- 8:46:44встречаются следующие записи, когда мы
- 8:46:46вызываем какую-то функцию, функция нам
- 8:46:48возвращает какую-то переменную, и мы
- 8:46:50сразу же в следующей строчке хотим
- 8:46:52узнать значение этой переменной. В таких
- 8:46:53случаях можно написать немножечко
- 8:46:55по-другому, немножечко короче. Я буду
- 8:46:56писать следующим образом. if двоеточие
- 8:47:00равно http.liston surf 90.0001 порт
- 8:47:04точка запятой не равно.
- 8:47:07И тело наше условие. Как это будет всё в
- 8:47:09сумме работать? У меня вот в этих вот
- 8:47:11строчках сначала вызовется httpiston and
- 8:47:14serve. Эта функция так или иначе должна
- 8:47:16вернуть какую-то переменную под
- 8:47:17интерфейсом error. Я эту переменную буду
- 8:47:19класть в переменную. И потом после вот
- 8:47:22здесь вот точки запятой я буду смотреть,
- 8:47:24равно линил или нет. И если R не равно,
- 8:47:27то есть если была какая-то ошибка, тогда
- 8:47:28я тут буду обрабатывать, собственно, эту
- 8:47:30самую ошибку. Вот так вот. Всё. То есть,
- 8:47:33по сути, вот это это то же самое, что и
- 8:47:35это. Просто более короткая запись,
- 8:47:37которая чаще используется, потому что на
- 8:47:39самом деле в настоящем гон-коде довольно
- 8:47:41часто приходится вызывать какие-то
- 8:47:42сторонние функции. Они могут нам
- 8:47:43возвращать какие-то ошибки. И хочется
- 8:47:45сразу же в этой же строчке проверить,
- 8:47:47была ли какая-то у нас ошибка или нет.
- 8:47:48Поэтому я буду впредь использовать вот
- 8:47:50такую вот запись. Повторюсь, вот это и
- 8:47:52вот это одно и то же. Единственное, что
- 8:47:55отличается - это то, что при первом
- 8:47:56способе мы с вами создали переменную ер
- 8:48:01того, как отработает условное ветвление.
- 8:48:03А вот во втором случае вот эта вот
- 8:48:04переменная R, она у нас перестанет
- 8:48:06существовать, как только закончится
- 8:48:08условное ветвление, потому что область
- 8:48:09видимости вот этой вот переменной - это
- 8:48:11и есть вот это вот условное ветвление. А
- 8:48:12в первом случае область видимости
- 8:48:14переменной r - это целая функция main.
- 8:48:16Иногда бывают случаи, когда мы хотим
- 8:48:17получить какую-то переменную из функции,
- 8:48:19потом сразу же проверить значение этой
- 8:48:21переменной, но при этом мы захотим эту
- 8:48:23переменную и после проверки значений
- 8:48:25использовать. Тогда нам не подойдёт вот
- 8:48:27этот укороченный вариант. Ну, потому что
- 8:48:28переменная тогда исчезнет после
- 8:48:30условного ветвления, хотя мы планировали
- 8:48:31использовать её после условного
- 8:48:32ветвления. Но в случае, когда мы
- 8:48:34вызываем функции, они возвращают ошибки,
- 8:48:36и мы хотим проверить r не равно, то вот
- 8:48:38в превалирующем большинстве сценариев
- 8:48:40нам вот эта, она после одной
- 8:48:42единственной проверки больше уже не
- 8:48:43нужна будет. Поэтому вот такая вот
- 8:48:45конструкция очень часто используется в
- 8:48:47случаях, когда нам функция возвращает
- 8:48:48какую-то ошибочку, и мы сразу же хотим
- 8:48:49эту ошибочку проверить. О'кей,
- 8:48:51регистрируем нашхнler, запускаем HTTP
- 8:48:53сервер на 90.000 девно первом порту. Ну,
- 8:48:55теперь нам остался описать наш Payхенer.
- 8:48:57Значит, он у нас будет работать с
- 8:48:58переменной мани какой-то. Давайте
- 8:48:59создадим эту переменную мани. Опять же,
- 8:49:02изначально положу, да, просто 1.000
- 8:49:03долларов и с какой-то историей покупок.
- 8:49:05То есть мы должны будем с вами сохранять
- 8:49:07историю покупок. Давайте подумаем, как
- 8:49:08бы мы могли это делать. Ну, самое вот
- 8:49:10очевидное, даже смотря на эту схему, это
- 8:49:12то, что у нас должен быть какой-то
- 8:49:13слайс. который сохраняет такие вот
- 8:49:15какие-то переменные. Ну, наиболее
- 8:49:17удобно, мне кажется, будет объявить
- 8:49:18структуру, которая будет описывать вот
- 8:49:20эту вот покупку. Давайте я покажу, что я
- 8:49:22имею в виду. Type payment strct. То есть
- 8:49:25это наша какая-то покупка. У неё есть
- 8:49:27описание покупки, то есть description
- 8:49:29строкового типа, ну и сумма покупки, то
- 8:49:31есть я назову это USD типа integer. То
- 8:49:33есть вот он наш payйment. Ну если
- 8:49:34проводить какие-то аналогии с тем
- 8:49:36модулем оплат, который мы писали, когда
- 8:49:38рассматривали интерфейсы, то можно
- 8:49:40назвать это payment info. Ну, в
- 8:49:41принципе, без разницы. Вот, как мы
- 8:49:43назовём, да, как кому нравится, я просто
- 8:49:45Pйк назову. Так вот, в данном случае это
- 8:49:47понятно, что имеется в виду, и это
- 8:49:48просто более короткая запись. Хорошо, то
- 8:49:50есть вот это вот наша отдельная запись.
- 8:49:52Вот это desрипtion получается, да? Вот
- 8:49:54это desрипtion, игра на ПК, а 70
- 8:49:56долларов - это есть вот наше USD, то
- 8:49:58есть сумма покупки. О'кей, получается
- 8:49:59история покупок - это будет слай таких
- 8:50:01вот структур. О'кей, назову это payment
- 8:50:03history. History. И это у меня будет
- 8:50:07слайс из вот этих вот оплат. Ну,
- 8:50:09изначально я проинициализирую его
- 8:50:11размерностью ноль, потому что я заранее
- 8:50:12совершенно не могу представить, сколько
- 8:50:13у меня будет оплат. Гадать не хочу,
- 8:50:15просто изначально у меня будет слайс
- 8:50:16нулевой величины. Делаю наш слайс через
- 8:50:19й. О'кей, вот это наша будет история
- 8:50:20плат. Ну и так как мы помним, что все
- 8:50:22наши хендлеры запускаются в отдельных
- 8:50:23рутинах, это значит, что если у нас
- 8:50:25какая-то переменная объявлена в
- 8:50:26мегарутине и мы захотим её ещё
- 8:50:28дополнительно менять в каких-то HTTPх
- 8:50:30хендлерах, значит нам нужно как-то
- 8:50:31регулировать конкурентный доступ. Ну,
- 8:50:33потому что доступ к одной и той же
- 8:50:34переменной происходит из двух и более
- 8:50:35горутин. Ну, в принципе, я тут как-то
- 8:50:37супер что-то придумывать не буду. Я
- 8:50:38через сделаю
- 8:50:40mtx = symк.
- 8:50:43Всё, через самый обычный mutex. Ну
- 8:50:45хорошо, давайте вот тут вот в нашей
- 8:50:46структуре payment дополнительно в
- 8:50:48комментарии подпишу, что есть что.
- 8:50:49Description - это описание покупки,
- 8:50:52а USD - это сумма покупки.
- 8:50:56И что самое интересное, я должен
- 8:50:57каким-то образом в клиенте, который
- 8:50:59будет нам отправлять этот HTTP запрос, в
- 8:51:02теле запроса сразу же задать как
- 8:51:04описание покупки, так и сумму покупки.
- 8:51:06То есть это не одно какое-то значение,
- 8:51:08как мы с вами раньше задавали при
- 8:51:09оплате, например, 100, да? Это у нас там
- 8:51:11была сумма покупки. Нет, в этот раз нам
- 8:51:14нужно сразу же два значения передавать в
- 8:51:16одном запросе, в одном теле запроса. Ну
- 8:51:19хорошо, давайте подумаем, как бы мы
- 8:51:20могли это сделать. Вот справа у меня
- 8:51:22постма, я захожу в бади, то есть тело
- 8:51:24запроса, и тут я выбираю RA, ну по
- 8:51:26старинке, да, то есть наш какой-то
- 8:51:27текст. Что бы я тут мог сделать? Ну
- 8:51:30как-то мне надо передать вот эти два
- 8:51:31значения, там сумму покупки, описание
- 8:51:33покупки. я бы мог подумать о том, чтобы
- 8:51:36как-то разделять их каким-то символом.
- 8:51:38Ну, например, запятой. То есть я бы
- 8:51:39передавал каким-то образом тут ээ
- 8:51:41desрипtion и через запятую я бы
- 8:51:43передавал сумму покупки, например, 100.
- 8:51:44Хорошо, в чём тут может быть проблема?
- 8:51:46Ну, проблема может быть в том, что в
- 8:51:48самом описании у нас может быть запятая.
- 8:51:50И получается, как мне тогда понять,
- 8:51:52какая именно запятая отделяет описание
- 8:51:55от суммы покупки? Ну, хорошо, я бы мог
- 8:51:57тогда наоборот придумать. Сначала сумма
- 8:51:59покупки, потом запятая и описание
- 8:52:01покупки. Ну, в принципе, звучит как
- 8:52:04вариант, то есть как будто бы это даже
- 8:52:05может заработать. Давайте попробуем это
- 8:52:07реализовать, э, следующим образом. Мы в
- 8:52:09нашем хендлере, как обычно, будем
- 8:52:11вычитывать HTTP request body. Это у нас
- 8:52:13e.
- 8:52:15Request. О'кей. Если у нас ошибка не
- 8:52:18равно, тогда я просто в responsс код
- 8:52:21буду записывать http internal server
- 8:52:23error. То есть какая-то внутренняя
- 8:52:24ошибка сервера, пятисотая ошибка. И всё.
- 8:52:26И буду завершать, короче, наш хенр. То
- 8:52:28есть какой-то дополнительной информации
- 8:52:29ни в консоль, ни в тело ответа я не буду
- 8:52:31добавлять. Ну, у нас такой тестовый
- 8:52:32примерчик. Особо это сейчас не нужно
- 8:52:34нам. Хорошо, прочитали вот это HTP
- 8:52:35request Body. Потом нужно как-то
- 8:52:36получить из этого строку. Поехали. HTTP
- 8:52:39request body string. Преобразуем этот
- 8:52:42массив Bт в строку. Преобразовали.
- 8:52:46Теперь мы получили вот эту вот строчку.
- 8:52:48Ну, там, понятное дело, у нас не будет
- 8:52:49вот этих кавычек. Это я просто условно
- 8:52:51обозначил, где у нас начинается,
- 8:52:52заканчивается сумма оплаты и описание
- 8:52:53оплаты. Хорошо, получили вот эту строку.
- 8:52:56Теперь нужно каким-то образом разделить
- 8:52:57её запятой, самой первой. Для таких
- 8:52:59целей у нас в пакете Strings есть
- 8:53:01функция Split N. Что делает эта функция
- 8:53:04Split N? Она берёт какую-то строку,
- 8:53:06которую мы в неё передаём, и разделяет
- 8:53:08её на слайс строк. То есть это
- 8:53:10получается подстроки по переданному
- 8:53:13разделителю и разделяет на то количество
- 8:53:15подстрок, которое мы передадим. Что это
- 8:53:17имеется в виду? Допустим, мы получили
- 8:53:18вот эту вот строку из HTTP телозапроса.
- 8:53:20Мы хотим её разделить по запятой. Мы
- 8:53:22берём и передаём эту строку функцию
- 8:53:24split N. вот эту строку и сеп, то есть
- 8:53:26это сепаратор, то есть разделитель, мы
- 8:53:28передаём запятую. То есть мы хотим
- 8:53:30отделять наши подстроки по запятой, а n
- 8:53:32мы передадим два. Почему? Да потому что
- 8:53:34у нас само описание тоже может включать
- 8:53:36какие-то запятые. Например, если
- 8:53:37какое-то обширное описание покупки, там
- 8:53:39с какими-нибудь причастными
- 8:53:41дипричастными оборотами, я не знаю, и у
- 8:53:43нас там могут быть запятые. Поэтому нас
- 8:53:44интересует разделение только на две
- 8:53:46подстроки. Первая подстрока, вторая
- 8:53:48подстрока, разделение по запятой. Ну,
- 8:53:50поехали. То есть вот это HTTP request
- 8:53:52body string передаём. Разделитель у нас
- 8:53:54запятая и n у нас 2. О'кей. И мы
- 8:53:57получаем какие-то части, я называю это
- 8:53:58пар, части нашего тела HTTP запроса
- 8:54:01входящего. По сути, это у нас, как мы
- 8:54:03видим, слайд строк, где у нас строка с
- 8:54:05индексом ноль должна быть суммой оплаты,
- 8:54:07а строка с индексом один должна быть
- 8:54:09описанием оплаты. Но вообще нам split n
- 8:54:11не гарантирует, что если мы сюда
- 8:54:13передали два, то нам обязательно
- 8:54:15вернётся две подстроки, потому что может
- 8:54:17быть такая ситуация, когда, в принципе,
- 8:54:18нету разделителя у нас никакого. Тогда
- 8:54:20нам в этом слайсе строк вернётся всего
- 8:54:22лишь одна строка. То есть слайс будет
- 8:54:23размером один. Поэтому нам обязательно
- 8:54:25нужно проверять, что если у нас l parts
- 8:54:28не равно два, тогда получается у нас
- 8:54:30какое-то неверное тело запроса входящее,
- 8:54:32потому что нам не удалось разделить его
- 8:54:34на две части по запятой. Ну я тогда
- 8:54:36просто буду в WRйхдере записывать HTTP B
- 8:54:38request. Всё. То есть какой-то неверный
- 8:54:40запрос пользователя и завершать
- 8:54:42выполнение нашего хендлера. Хорошо.
- 8:54:44Допустим, мы получили всё вот эти наши
- 8:54:46две части. Теперь мы можем начать
- 8:54:47преобразовывать их в нашу вот эту
- 8:54:49структуру Payment. О'кей. Нам,
- 8:54:50получается, вот эту первую часть нужно
- 8:54:52будет перевести в число. И это число у
- 8:54:54нас и будет, получается, суммой покупки.
- 8:54:55Ну, поехали. USD запятая равно ков а
- 8:55:02parts с индексом ноль, потому что именно
- 8:55:04вот в нулевом индексе у нас содержится
- 8:55:05вот эта вот первая подстрока.
- 8:55:07Соответственно, если ошибка у нас не
- 8:55:08равна, тогда я также напишу BДquest.
- 8:55:14Ну, получается, у нас тут не число было
- 8:55:15передано вот первой подстрокой, потому
- 8:55:17что, собственно, не получилось перевести
- 8:55:19в число. Значит, было не число. И
- 8:55:21завершаях handler, всё, наконец-таки мы
- 8:55:23с вами можем создать экземпляр вот этой
- 8:55:24вот структуры Payment. Вот Payment, это
- 8:55:27у нас структура Payment, и у неё у нас
- 8:55:30два поля. Первое - это description- это
- 8:55:32парт с индексом один, то есть вот эта
- 8:55:34вот часть подстроки. Ну и USD - это вот
- 8:55:36та переменная, которую мы, наконец-таки,
- 8:55:37смогли вычленить. Хорошо. Теперь у нас
- 8:55:39вообще вся информация есть для того,
- 8:55:41чтобы про взаимодействовать с нашими
- 8:55:42переменными Moneyoney и Payment History.
- 8:55:44Мы берём и с переменной Money вычитаем
- 8:55:48USD, то есть покупочку нашу. Но как
- 8:55:50обычно, мы не можем это просто так
- 8:55:51сделать. Нам сначала нужно проверить
- 8:55:52баланс наш. Что есть у нас маoney минус
- 8:55:55USD больше либо равно нуля. То есть, ну,
- 8:55:57в принципе, если у нас хватает денег на
- 8:55:58совершение вот этой вот самой покупки,
- 8:56:00тогда я буду покупку совершать. Ну и,
- 8:56:02конечно же, после совершения покупки я
- 8:56:03хочу записать её в Payment History.
- 8:56:05Payment History, append. И тут наша вот
- 8:56:08эта вот payйment, который мы с вами
- 8:56:09совершили. Хорошо, но помним, да, про
- 8:56:11гонку данных вот здесь вот. Поэтому
- 8:56:12мьтексами всё обкладываем. MTX log, MTX
- 8:56:15unlock. Ну хорошо. Только теперь
- 8:56:17единственное, что я бы хотел после
- 8:56:19каждой оплаты выводить в консоль мой
- 8:56:20текущий новый баланс и знать вообще, что
- 8:56:22там в истории оплат происходит.
- 8:56:24Следующим образом. Перед разблокировком
- 8:56:25текса я буду вводить на экран Money. И
- 8:56:28тут Money. Ну и, конечно же, наша
- 8:56:30Payment History. Payment History.
- 8:56:34Вот она, наша Payment History. Да. И
- 8:56:37всё. Мы с вами описали наш хендлер.
- 8:56:39Теперь можем его запустить, посмотреть,
- 8:56:40как это вообще всё в сумме работает.
- 8:56:41Давайте я запускаю нашу программу. Она
- 8:56:43запустилась. Пробуем отправить сначала
- 8:56:45наш запрос с пустым телом. Посмотрим,
- 8:56:47что будет. Отправляю. Опа, 400 B
- 8:56:50request. Это у нас отработала вот эта
- 8:56:52вот часть, потому что у нас не
- 8:56:53получилось разделить входящее тело
- 8:56:55запроса на две части по запятой, поэтому
- 8:56:57у нас как бы ошибка произошла. Давайте
- 8:56:59тут даже попробуем что-то передать. Всё
- 8:57:01равно Bдреквест будет. Теперь передаём
- 8:57:02всё, как надо. Вообще сначала сумму
- 8:57:03покупки через запяту описания, например,
- 8:57:05бургер. Ну давайте теперь попробуем
- 8:57:07отправить наш запрос. Опа, 200. О'кей. И
- 8:57:10видим у нас в консоли вывод Money. 900,
- 8:57:13то есть было 1.000, произошла покупка на
- 8:57:15100 долларов и у нас стало 900 долларов.
- 8:57:17Ну и payment history, то есть история
- 8:57:18покупок. Вот бургер на 100 долларов.
- 8:57:20Хорошо. После этого давайте я на 350
- 8:57:22долларов куплю себе телефон. Телефон
- 8:57:25отправляю. Опять 200 ОК. Вижу, у меня
- 8:57:27ещё на 350 долларов стало меньше денег.
- 8:57:30И Payment History у меня теперь
- 8:57:31пополнилась покупка телефона. Ну и
- 8:57:33давайте вот 550 долларов. То есть ещё, в
- 8:57:34принципе, на 200 долларов что-то можно
- 8:57:36купить. 200 долларов я себе куплю
- 8:57:38игровую приставку. Игровая приставка.
- 8:57:41Отправляем 200 ОК. И видим, опять у нас
- 8:57:44списались деньги. И история покупок у
- 8:57:45нас пополнилась игровой приставкой на
- 8:57:47200 долларов. Ну, казалось бы, всё
- 8:57:48круто, всё чётко. Мы смогли придумать
- 8:57:50вот такой вот формат разделения нашей
- 8:57:52информации через запятую. Мы смогли из
- 8:57:54этого всего телазапроса при помощи
- 8:57:56пакета Strings вычленить две разные
- 8:57:58части. При помощи пакета STRC конф мы
- 8:58:00смогли вычленить из первой части именно
- 8:58:02число. Ну, вроде всё работает. Хорошо.
- 8:58:04Но теперь представим, что наш модуль
- 8:58:06оплат начинает развиваться, и у нас уже
- 8:58:08в оплате помимо описания покупки, суммы
- 8:58:11покупки, мы ещё захотим теперь хранить
- 8:58:13полное имя человека, который совершил
- 8:58:15покупку. И ещё мы захотим хранить адрес
- 8:58:17прописки этого человека.
- 8:58:19адрес.
- 8:58:22Всё это строковые типы. То есть full
- 8:58:23name - это у нас получается о человека
- 8:58:27совершающего
- 8:58:29покупку. А адрес - это место прописки
- 8:58:33человека, совершающего покупку.
- 8:58:37Получается, у нас теперь новый вызов с
- 8:58:38вами. Нам каким-то образом всю эту инфу
- 8:58:40точно также нужно передавать в теле
- 8:58:42запроса, как бы кто купил, где он живёт
- 8:58:45вообще, что бы мы могли тут сделать. Ну,
- 8:58:47мы бы могли с вами опять начать
- 8:58:48придумывать что-то с запятыми. Но в чём
- 8:58:50теперь тут проблема? Ну, например, в
- 8:58:52том, что адрес он тоже в себе может
- 8:58:55включать запятые, да, там город
- 8:58:57такой-то, запятая, улица такая-то
- 8:58:58запятая. И как понять, вот какая запятая
- 8:59:01она разделяет город от улицы в адресе, а
- 8:59:04какая запятая у нас разделяет разные
- 8:59:06части нашего телозапроса? А я вам могу
- 8:59:08сказать, что запросы могут быть ещё
- 8:59:10сложнее, может быть, ещё больше всякой
- 8:59:12информации передаваться в теле запроса,
- 8:59:14но мы бы с вами могли начать придумывать
- 8:59:16какой-то более сложный формат хранения
- 8:59:18данных вот в нашем вот этом теле
- 8:59:19запроса. Например, мы бы могли придумать
- 8:59:21что-то вроде мапы, да, какого-то K
- 8:59:23valueue обозначения данных. Ключом у нас
- 8:59:25будут, например, USD, двоеточие и
- 8:59:28значение. Тут это было бы description,
- 8:59:31двоеточие и значение. Потом у нас был бы
- 8:59:33full name, тоже двоеточие. Тут какое-то
- 8:59:35значение. Потом у нас был бы адрес
- 8:59:37двоеточие, опять какое-то значение. Ну,
- 8:59:39в принципе, довольно уже выглядит
- 8:59:41неплохо. А теперь представим, что у нас
- 8:59:43по какому-то ключу лежит не просто
- 8:59:44одиночное значение, а целый массив
- 8:59:46какой-нибудь. То есть, например, у нас в
- 8:59:48одном запросе HTTP может обрабатываться
- 8:59:50не одна покупка, а сразу несколько.
- 8:59:51Когда человек себе, например, набрал в
- 8:59:53магазине целую корзину товаров, и у нас
- 8:59:55уже наш йхенр должен будет обрабатывать
- 8:59:58не одну покупку, а сразу несколько. То
- 8:59:59есть несколько товаров в одной покупке.
- 9:00:01Получается, нам каким-то образом уже
- 9:00:03нужно уметь массивы обрабатывать в нашем
- 9:00:05вот этом формате, да? То есть у нас бы,
- 9:00:06получается, уже была не одна покупка, а
- 9:00:08был бы какой-то массив этих покупок. Вот
- 9:00:10это вот был бы один элемент этого
- 9:00:12массива. Потом у нас через запятую был
- 9:00:15бы вот так вот второй элемент массива,
- 9:00:16третий элемент массива. Это у нас уже
- 9:00:18получается была бы какая-то целая
- 9:00:20корзина из наших покупок, да? То есть
- 9:00:21можно сказать целый чек. И потом этот
- 9:00:23целый чек могли бы списать со счёта
- 9:00:25клиента и там сохранить в историю
- 9:00:26покупок. Это всё, конечно, прекрасно, но
- 9:00:28вот вспомните, как мы сейчас с вами
- 9:00:30только что запарились с тем, чтобы
- 9:00:32распарсить вот самую простую, вообще
- 9:00:34входящую информацию. Вот эта вот
- 9:00:36стоимость заказа, запятая, описание. Вот
- 9:00:39это вот мы с вами парсили вот как сложно
- 9:00:41на самом-то деле. А теперь представьте,
- 9:00:43как бы мы с вами вот эту информацию
- 9:00:44парсили. Ну, проблема, то есть у нас
- 9:00:46большая проблема возникает, а именно
- 9:00:48проблема в том, чтобы передавать
- 9:00:49какие-то сложные данные по сети, чтобы
- 9:00:51какие-то сложные данные в этом нашем
- 9:00:53теле запроса передавать. Ну, мы вот это
- 9:00:55вот это очень сложно будет парсить. Это
- 9:00:56во-первых. Во-вторых, нам, в принципе,
- 9:00:58нужно вот этот вот формат придумать,
- 9:00:59потому что там очень много различных у
- 9:01:01нас может быть случаев. Как нам
- 9:01:02распознавать true false? Как нам
- 9:01:04распознавать, когда это не true, не флс,
- 9:01:06а это строка, когда это число, когда это
- 9:01:08массив, когда это куча всего разного.
- 9:01:10Проблема, решение этой проблемы - это
- 9:01:12джисон. То есть когда-то в своё время
- 9:01:15умные дяди и тёти тоже столкнулись вот с
- 9:01:17такой вот проблемой и придумали решение.
- 9:01:19Они придумали общепринятый
- 9:01:21стандартизированный формат передачи
- 9:01:23данных по сети. Называется он же.
- 9:01:25выглядит очень похоже на то, что мы с
- 9:01:27вами сейчас обсудили. Просто он
- 9:01:29стандартизированный. Большинство языков
- 9:01:30программирования уже умеет давно
- 9:01:32работать с джисоном. Ну давайте,
- 9:01:33наконец-таки, посмотрим на этот самый
- 9:01:35Gon. Для того, чтобы мне в моём теле
- 9:01:36запроса отправлять не просто какой-то
- 9:01:38рандомно накиданный текст, а именно
- 9:01:40стандартизированный JON, мне нужно здесь
- 9:01:42вот выбрать не текст, а Jon. Опа, всё.
- 9:01:45Мне, видите, тут даже начинает
- 9:01:46подчёркивать ошибка, что у меня неверный
- 9:01:47формат данных. Давайте я напишу в Gсоне
- 9:01:50вот эту вот нашу Payment структуру. Как
- 9:01:53бы я передавал данные по сети в
- 9:01:54телезапроса, если бы у меня были
- 9:01:56требования передавать описание, сумму
- 9:01:58покупки, Фио человека и место прописки.
- 9:02:00Ну давайте. JSON должен оборачиваться в
- 9:02:02фигурные скобочке. Потом Jon имеет
- 9:02:05формат ключ значения. Ключом у нас
- 9:02:07является название какого-то поля,
- 9:02:09например, description. И через двоеточие
- 9:02:12я пишу значение этого поля. То есть
- 9:02:14фактическое описание покупки. Так как
- 9:02:16описание покупки у меня это строка, то я
- 9:02:18передаю тоже в скобочках бургер. Хорошо.
- 9:02:22Потом, если у меня дальше идут ещё
- 9:02:23какие-то поля, я их должен через запятую
- 9:02:25начать описывать. Запятая и ставлю опять
- 9:02:28какой-то ключик, название какого-то
- 9:02:29поля. Дальше у меня что? Дальше у меня
- 9:02:31идёт USD. Вот USD. Через двоеточие я
- 9:02:35передаю значение. Но теперь у меня
- 9:02:37значение - это не строка, а число,
- 9:02:39поэтому я его описываю не в скобочках, а
- 9:02:41просто как число. Хорошо. Дальше у меня
- 9:02:42идёт full name. Давайте full name. И
- 9:02:45через двоеточ опять всего человека.
- 9:02:46Иванов, Иван, Иванович. Потом у меня
- 9:02:51дальше есть ещё адрес, запятая, адрес
- 9:02:54двоеточья. Ну и тут, например, город
- 9:02:56Париж, какая-нибудь улица Багетная
- 9:02:5947. Всё. И так как у меня после адреса
- 9:03:02уже нету никаких других полей, то я тут
- 9:03:04в конце запятую не ставлю. Вот если тут
- 9:03:06запятую поставлю, мне скажут, что
- 9:03:07ошибка. Так как это последнее поле, я
- 9:03:09вот тут запятую не буду ставить. И
- 9:03:10смотрите, как красиво, понятно, наглядно
- 9:03:13и читаемо теперь выглядит передача
- 9:03:16данных наша по сети. Я всё своё тело
- 9:03:18запроса описываю теперь не в непонятном
- 9:03:21каком-то тексте, да, который вообще
- 9:03:22рандомный формат имеет, нам там что-то
- 9:03:24самим надо придумывать. Нет, я использую
- 9:03:26GSON, где за меня уже давно всё
- 9:03:28придумано и стандартизировано. Хорошо,
- 9:03:29то есть получается, мне в теле запроса
- 9:03:31придёт вот такой вот же, и мне каким-то
- 9:03:33образом нужно взять вот этот JON и
- 9:03:35перевести его в переменную языка
- 9:03:38программирования, перевести его в вот
- 9:03:40эту вот структуру Payment. А всё на
- 9:03:41самом деле довольно просто. Мне для
- 9:03:43того, чтобы делать эти переводы, мне
- 9:03:45нужно после каждого поля структуры в
- 9:03:47таких вот кавычках, это не прямые
- 9:03:48кавычки одинарные, это косые кавычки
- 9:03:50одинарные, в этих кавычках я пишу jн
- 9:03:52двоеточие и в двойных скобочках название
- 9:03:55поля description. Что это значит? Я
- 9:03:58сказал языку программирования Goang, что
- 9:04:00если ты будешь из входящего HTTP
- 9:04:02запроса, из тела запроса вычленять GSON
- 9:04:05и переводить его в эту структуру
- 9:04:07Payment, то вот этому полю структуры
- 9:04:10Payment Description будет
- 9:04:11соответствовать поле из Gсона
- 9:04:14description. О'кей. Дальше то же самое с
- 9:04:16долларами. То есть у меня USD вот это
- 9:04:18вот капsлоком, которое написано в языке
- 9:04:21программирования - это вот это вот USD в
- 9:04:23Gsonне вот этими маленькими буквами.
- 9:04:24Хорошо, так и напишу USD. Full name. Это
- 9:04:29у нас вот он наш full name. Тут я его и
- 9:04:32запишу. Хопа. Теперь адресн двоеточие.
- 9:04:36Адрес. Всё. Таким образом я сказал
- 9:04:39Голенгу, что для каждого поля из Джесона
- 9:04:43у меня есть поле в структуре в языке
- 9:04:45программирования. О'кей, описали. И
- 9:04:47теперь мы с вами можем при помощи уже
- 9:04:49готовых библиотек брать и входящий JON
- 9:04:52преобразовывать вот в эту вот структуру.
- 9:04:54И нам уже не нужно что-то там
- 9:04:55преобразовывать в строку. что-то там
- 9:04:57искать запятые, что-то там проверять,
- 9:05:00что-то там делать дополнительное, как-то
- 9:05:02там пытаться преобразовать руками строку
- 9:05:04в число. Нет, нам этого всего делать не
- 9:05:06нужно. Нам нужно просто взять, создать
- 9:05:08переменную Payment,
- 9:05:10изначально пустую, но мы в неё будем
- 9:05:12класть вот этот вот входящий GON. Вот
- 9:05:14его. После этого обратиться к пакету.
- 9:05:17Вот он encoding jon.
- 9:05:20В нём функция unmarsha. Первым
- 9:05:22аргументом она принимает какой-то слайт.
- 9:05:24Это как раз-таки то наше самое тело
- 9:05:25входящего запроса. HTTP request body.
- 9:05:28Вторым аргументом принимает какую-то
- 9:05:29переменную, в которую нужно будет
- 9:05:31записать значение из входящего джисона.
- 9:05:34Ну, собственно, это наша переменная
- 9:05:35payment, но передавать мы её должны по
- 9:05:37указателю, чтобы функция unmarsha смогла
- 9:05:39внутри себя поменять значение вот этой
- 9:05:41вот переменной. Амаршал у нас возвращает
- 9:05:43ошибку. Будем эту ошибку проверять. Если
- 9:05:45у нас ошибка не равна, тогда,
- 9:05:48собственно, я буду просто выводить на
- 9:05:49экран эту ошибку.
- 9:05:51Ну и после этого в статусско-коде я буду
- 9:05:53возвращать HTTP Internal Server Eror.
- 9:05:55Ну, буду предполагать, что если
- 9:05:57почему-то не получилось преобразовать
- 9:05:59входящий GON, как-то правильно его
- 9:06:01распарсить, значит, это ошибка на нашей
- 9:06:03внутренней стороне. Ну и завершу
- 9:06:05хендлер. О'кей. После этого, если тут
- 9:06:07всё было хорошо, я уже вот в сорок
- 9:06:09четвёртой строчке буду знать, что я
- 9:06:11распарсил ходящий сложнейший вот этот
- 9:06:12вот запрос. Просто вот одной строчкой
- 9:06:14считай. И нам вот это всё уже больше не
- 9:06:15надо. Это всё можно стирать.
- 9:06:19Всё. Здесь во время списания баланса я
- 9:06:21найду сумму оплаты, которая у меня уже
- 9:06:24лежит в переменной Payment. А в
- 9:06:25переменной Payment оно появилось из HTTP
- 9:06:27request Body. HTTP request Body - это
- 9:06:29вот этот вот наш входящий Gson. То есть
- 9:06:31я вот в этом входящем дсоне меняю
- 9:06:32циферку, и она меняется в переменный
- 9:06:35payment. Всё, payment. А дальше всё то
- 9:06:39же самое. Всё. И теперь насколько бы
- 9:06:41сложными не были данные, я их всегда
- 9:06:43смогу приставить в этом джисоне и вот
- 9:06:45так вот одной строчкой просто распарсить
- 9:06:47в переменную. Ну, давайте я вот у
- 9:06:48структуры Payment сделаю метод, который
- 9:06:50бы красиво выводил её на экран. То есть
- 9:06:52Fun - это у меня метод для структуры
- 9:06:55Payment. Printl. Вот так вот. Он будет
- 9:06:58делать следующее. Description
- 9:07:01двоеточие
- 9:07:03P description. Копирую.
- 9:07:07Ну и тут у меня дальше, то есть USD, это
- 9:07:09P USD. Дальше full name, это P, full
- 9:07:14name. Дальше адрес.
- 9:07:18Это P то. Всё. Ну и тут после того, как
- 9:07:21я из входящего распарсил эту нашу
- 9:07:23структуру, я буду выводить её на экран.
- 9:07:25Вот payment printl. И уже этот метод у
- 9:07:27меня выведет всё это на экран. Тут
- 9:07:29ресивер у меня копий не по указателю. Ну
- 9:07:31мне тут указатель не нужен, потому что я
- 9:07:32никак эту структуру, никак её поля в
- 9:07:35этом методе не меняю. Поэтому мне и не
- 9:07:37нужно тут ничего по указателю как бы
- 9:07:38принимать особо. Ну что, давайте
- 9:07:39запустим нашу программу.
- 9:07:42Запустили. И я хочу отправить вот этот
- 9:07:45вот запрос, посмотреть, что вообще
- 9:07:46будет. Отправляю. Опа. И я вижу, что у
- 9:07:50меня description бургер, вот здесь вот
- 9:07:52выводится доллары 100, full name Иванов
- 9:07:55Иван Иванович адрес. Ну и потом у нас
- 9:07:58писалось 100 долларов. И вот Payment
- 9:07:59History у нас пополнилась вот этой вот
- 9:08:01покупкой со всеми её данными. Описание,
- 9:08:03количество долларов, которое потратили,
- 9:08:05имя человека и его адрес. Но GSON может
- 9:08:08ещё проще сделать. Мы сейчас руками
- 9:08:10вычитываем тело запроса, но мы можем
- 9:08:12сделать гораздо проще. Смотрите за
- 9:08:14руками, как говорится. Json то new
- 9:08:17decкоoder. Сюда я передаю request body
- 9:08:21точка deкод. И сюда я передаю
- 9:08:23переменную. Ну вот я отсюда её заберу. И
- 9:08:26вот сюда я буду передавать переменную
- 9:08:28payment. Также у нас тут может быть
- 9:08:29ошибка. Если R не равно new, тогда точно
- 9:08:34так же, как здесь, вот эту вот обработку
- 9:08:37ошибки оставлю. Что это нам даёт? Нам
- 9:08:39теперь не нужно самим вычитывать вот это
- 9:08:40request body. Нам вот этот вот
- 9:08:42gson.necoder сам вычитает это request
- 9:08:44bodyди и потом в ту переменную, которую
- 9:08:46мы передадим в декод, запишет полученные
- 9:08:48все из входящего дсона значения.
- 9:08:50Соответственно, вот это всё можно
- 9:08:52удалять. И смотрите, насколько ещё проще
- 9:08:55стала обработка входящего тела запроса.
- 9:08:58Мы сразу же тут можем описать сколь
- 9:08:59угодно сложные данные. Мы тут можем
- 9:09:02описывать всякие массивы, всякие K value
- 9:09:04значения, булевские значения,
- 9:09:06целочисленные, строковые, дробные
- 9:09:08значения и просто одной строчкой
- 9:09:10вычитывать тело запроса и
- 9:09:12преобразовывать его в переменную на
- 9:09:13уровне языка программирования. Давайте
- 9:09:15посмотрим, как это работает.
- 9:09:17Перезапускаю,
- 9:09:19отправляю тот же самый запрос. Всё
- 9:09:21работает точно так же. Вот для этого и
- 9:09:23существует GSON. Он нам помогает по сети
- 9:09:26передавать сложные данные. Это очень
- 9:09:28удобно. Json используется просто
- 9:09:30повсеместно. Json поддерживает, помимо
- 9:09:32прочего, помимо того, что мы
- 9:09:33рассмотрели, ещё булевские значения,
- 9:09:36например, true либо false. Также Jon у
- 9:09:39нас поддерживает дробные значения. Вот
- 9:09:41дробь, а там 1.25 jon поддерживает
- 9:09:44массивы. Я вот, например, array и у меня
- 9:09:47будет массив чисел 10, 20, 30, 40, 50.
- 9:09:52Вот массив чисел он поддерживает. Json
- 9:09:54поддерживает массив каких-нибудь других
- 9:09:56сущностей. Например, я назову это
- 9:09:58комплекс array, и это у меня будет
- 9:10:01массив из нескольких сущностей. Тут,
- 9:10:03например, у меня будет full name,
- 9:10:06name один и тут адрес какой-нибудь.
- 9:10:09Адрес один. Сейчас я скажу, это никак не
- 9:10:12относится к модулю оплаты. Это я просто
- 9:10:13показываю так быстренько, что Jon ещё
- 9:10:15умеет. Это у нас, допустим, какой-то
- 9:10:16человек, да, у него есть full name и
- 9:10:18есть адрес. А вот ещё один. хопа, только
- 9:10:21у него уже будет name 2, адрес 2. То
- 9:10:25есть мы можем в джисоне передавать
- 9:10:28массивы и массивы не просто чисел
- 9:10:30каких-нибудь или строчек, а массивы
- 9:10:32каких-нибудь других сущностей. У этих
- 9:10:34других сущностей тоже могут быть
- 9:10:36какие-то свои массивы
- 9:10:39[аплодисменты]
- 9:10:40и так далее, и так далее, и так далее.
- 9:10:41То есть настолько Gсон гибкий формат,
- 9:10:44что он нам по сути любые данные
- 9:10:46позволяет передавать по сети. Притом с
- 9:10:48этим очень удобно работать. Этот формат
- 9:10:50очень наглядный. Мы буквально видим, что
- 9:10:52за поле и какое у него значение. Видим,
- 9:10:55где у нас строка, где у нас число, где у
- 9:10:57нас булевское значение передаётся, где у
- 9:10:59нас дробное значение, где массив у нас
- 9:11:00передаётся. Это всё не только легко
- 9:11:03читается человеком и описывается. Это
- 9:11:05также очень удобно передаётся по сети,
- 9:11:07потому что мы внутри нашего языка
- 9:11:08программирования вот просто в одну
- 9:11:10строчку можем вычитывать вот это
- 9:11:11входящее тело запроса и перекладывать
- 9:11:13это в структуру, в какой-нибудь
- 9:11:15экземпляр структуры на уровне языка
- 9:11:17программирования. Но даже в таком
- 9:11:18подходе, казалось бы, просто идеальном,
- 9:11:20есть некоторые минусы. Вот первый из
- 9:11:22них. Если мы случайно в нашем теле
- 9:11:24запроса забудем передать какое-то поле,
- 9:11:27например, я забуду передать USD, то есть
- 9:11:28количество долларов, на которое была
- 9:11:30совершена покупка, и отправлю этот
- 9:11:32запрос. Как мы видим, всё обработалось
- 9:11:34корректно. Просто у нашего доллара, то
- 9:11:36есть у нашего поля USD, принялось
- 9:11:38значение по умолчанию. Вызвано это тем,
- 9:11:40что почему-то, если честно, для меня это
- 9:11:42самого загадка, стандартные библиотеки
- 9:11:44Гошки не расценивают потерю какого-то
- 9:11:47поля. Хотя я его тут вроде явно указал,
- 9:11:49что я его жду. Во входящем дсоне, если
- 9:11:51какое-то поле потеряно, это не
- 9:11:52расценивается как ошибка. Ровно так же я
- 9:11:54тут заместо доллара могу забыть,
- 9:11:56например, full name, то есть, да, у меня
- 9:11:57может быть не быть вообще переданного
- 9:11:59имени, хотя я его как бы жду, вообще-то.
- 9:12:01Отправляю запрос. Ну и, видим, у меня
- 9:12:02fullфнеймame, просто вот пустая строка,
- 9:12:04ничего нету. Могу вообще всё забыть. То
- 9:12:05есть вот у меня пустой джесон, одни
- 9:12:07скобочки остались. Всё равно это как бы
- 9:12:08не будет ошибкой. Поэтому обычно помимо
- 9:12:10того, чтобы как-то вот так декодировать
- 9:12:12входящий Gсон, потом дополнительно ещё
- 9:12:14пишут всякие методы валидации, да?
- 9:12:16Например, вот у меня у этой покупочки
- 9:12:18будет какой-нибудь метод типа validate,
- 9:12:20который бы возвращал мне buлин, и он уже
- 9:12:22внутри себя проверял, что если у меня
- 9:12:24там, например, P USD = 0, это значит,
- 9:12:27что типа покупка на 0 долларов была
- 9:12:28совершена, значит, скорее всего мне не
- 9:12:30передали во входящем запросе эти
- 9:12:32доллары, тогда я верну фолs прошёл
- 9:12:34валидацию. Или там, если у меня адрес -
- 9:12:36это пустая строка, тогда я тоже типа не
- 9:12:38прошёл валидацию. Ну и так далее, и так
- 9:12:40далее, и так далее. То есть
- 9:12:40дополнительную валидацию входящих
- 9:12:42запросов обычно прикручивают, чтобы
- 9:12:43смотреть, не забыл ли человек, который
- 9:12:45передаёт нам запрос, описать какое-то
- 9:12:47поле или, может быть, значение у поля
- 9:12:48какое-то неверноя. Например, нам в
- 9:12:50фулнейме могут передать вот так вот. Это
- 9:12:52же типа строка, и мы её как бы схаваем,
- 9:12:54но не хотелось бы. Вот для этого и нужна
- 9:12:56всякая валидация. Мы, кстати, темы
- 9:12:57валидации уже касались в предыдущем
- 9:12:59видео, но на самом деле это не критично.
- 9:13:01Это довольно просто решается. Я говорю,
- 9:13:02вот такими вот проверочками мы с вами
- 9:13:04всегда сможем узнать, как бы, что мы в
- 9:13:05итоге приняли, и описав дополнительный
- 9:13:07такой небольшой метод, сможем
- 9:13:08провалидировать все входящие значения.
- 9:13:10Но взамен мы получаем автоматическое
- 9:13:13преобразование всех вот этих вот
- 9:13:14входящих джисонов в нашу структуру. Это
- 9:13:17крайне удобно. Нам не приходится писать
- 9:13:19какие-то непонятные парсеры, как мы с
- 9:13:21вами делали в самом начале рассмотрения
- 9:13:23данного примера. Нет, мы просто одной
- 9:13:24строчкой будем вычитывать тело входящего
- 9:13:26запроса, сразу же его декодировать. Ну и
- 9:13:28потом в каком-нибудь методе можем
- 9:13:30дополнительно проверять, всё ли у нас
- 9:13:31правильно было передано. Таким образом,
- 9:13:33у нас сейчас с вами прошло первое
- 9:13:34знакомство с джисоном. Мы поговорили о
- 9:13:37том, какую проблему он решает. Потом
- 9:13:39узнали, как именно JSON работает, и как
- 9:13:41мы с помощью джисона можем передавать по
- 9:13:43сети сложные данные. Ну, я не зря
- 9:13:45сказал, что мы с вами при помощи джисона
- 9:13:47можем передавать данные по сети. А это
- 9:13:49значит, что мы в джине можем описывать
- 9:13:51не только тело запроса, но и тело
- 9:13:53ответа. Для чего нам это может
- 9:13:55понадобиться? Ну, например, я
- 9:13:56наконец-таки хочу выводить новое
- 9:13:58значение переменной маoney и новое
- 9:14:00значение переменной Payment History не
- 9:14:02просто в консоль в нашем-приложении, а
- 9:14:04возвращать эту информацию клиенту в HTTP
- 9:14:07ответе. Я могу эти данные тоже
- 9:14:09представить в виде джесона. О'кей,
- 9:14:11давайте я покажу, как это делается. Для
- 9:14:12этого я пишу структурку, назову её HTTP
- 9:14:16response. То есть эта структурка, она
- 9:14:18будет описывать мой HTTP- ответ. В неё
- 9:14:20будет два поля. Это переменная money и
- 9:14:22переменная payment history. Money - это
- 9:14:24у меня тип integer и payment history
- 9:14:28это у меня слайс наших вот этих вот
- 9:14:30структур Payment. Плюс для красоты я
- 9:14:32захочу ещё каждый раз в Payment
- 9:14:33сохранять время оплаты. Назову это time.
- 9:14:36Это переменно типа тайм. то и вот я тут
- 9:14:39подпишу, что это время проведения
- 9:14:42оплаты. Это не что-то такое, что мне
- 9:14:44будет отсылать клиент, потому что между
- 9:14:45тем, как клиент отошлёт свой запрос и
- 9:14:47фактическим проведением оплаты может
- 9:14:49пройти несколько секунд или же даже
- 9:14:50минут, поэтому я буду это время
- 9:14:52проведения оплаты высчитывать прямо на
- 9:14:54бэкэнде. Сразу же после того, как я
- 9:14:56распарсил входящий Gson в структуру
- 9:14:58Payment, я дополнительно этой структуре
- 9:15:00Payment буду выставлять время оплаты.
- 9:15:02payment.time = time now. Это значит, что
- 9:15:05в пятьдесят первой строчке какое время
- 9:15:06было, такое записалось в оплату. Хорошо.
- 9:15:09Возвращаемся к нашему HTTP респонсу. Мне
- 9:15:11нужно наполнить эту структурку данными и
- 9:15:13потом каким-то образом преобразовать её
- 9:15:15в GSON и вернуть в HTTP ответе. Хорошо,
- 9:15:18давайте наполним эту структурку данными.
- 9:15:19Вот у меня HTTP response. Создаю HTTP
- 9:15:23response. Money - это у нас переменная
- 9:15:25Money. И Payment History - это у нас наш
- 9:15:27Payment History. Теперь мне нужно
- 9:15:29каким-то образом это всё преобразовать в
- 9:15:30GSON. Я вызываю пакет JSON и в нём
- 9:15:33функцию Marshall. Marshall принимает у
- 9:15:35нас какую-то структурку, переменную, что
- 9:15:36угодно и возвращает нам с Byт, который
- 9:15:39мы потом сможем вернуть в HTTP ответе.
- 9:15:41Структурка наша - это HTTP response. Ну
- 9:15:43и маршал помимо слайса байта возвращает
- 9:15:45ещё и ошибку. Поэтому ошибку надо
- 9:15:46проверять. Вот наш слайс byйт и ошибка.
- 9:15:49Если у нас ошибка не равна, в таком
- 9:15:52случае я выведу её на экран просто. Ну и
- 9:15:54завершу наш хенр. Ну хорошо, ещё в
- 9:15:56статус коды ответа я запишу internal
- 9:15:58server eror, потому что у нас почему-то
- 9:16:01не получилось самую обычную структурку
- 9:16:02перевести в JSON. Ну хотя мы должны это
- 9:16:04уметь. Какая-то внутренняя ошибка
- 9:16:05сервера у нас получилась. Хорошо. В
- 9:16:07переменной B у нас лежит GSON, который
- 9:16:09мы получили из этой структурки. Теперь
- 9:16:11нам нужно этот Jon вернуть. А тело HTTP
- 9:16:13ответа у нас записывается через respons,
- 9:16:15я вам напоминаю. Поэтому я пишу response
- 9:16:18writer точка WR и передаю туда сй by,
- 9:16:21который я хочу записать. А это именно те
- 9:16:23от B, которые я получил при переводе
- 9:16:25нашей структуры в GSON. Хорошо, врано
- 9:16:27возвращает integer, который мы помним,
- 9:16:29нас не особо интересует, и ошибку. И я
- 9:16:30тут воспользуюсь вот этой вот
- 9:16:31сокращённой записью, что если у меня там
- 9:16:33ошибка неравна Нил, тогда выведу её
- 9:16:36опять на экран. Ну и всё. И тут я
- 9:16:38завершаю хендр. Не буду ничего вот в
- 9:16:40этом случае писать в статус-код ответа,
- 9:16:43потому что если у меня произошла ошибка
- 9:16:45во время попытки записать какое-то тело
- 9:16:47ответа, то у меня, ну, почти наверняка
- 9:16:49не получится и статус-код ответа
- 9:16:50записать. Мы, в принципе, это с вами уже
- 9:16:52обсуждали, поэтому тут я просто вывожу
- 9:16:53ошибку на экран и завершаю наш хендлер.
- 9:16:55Но если мы продвигаемся дальше, это
- 9:16:57значит, у нас успешно получилось нашу
- 9:16:59структуру HTP Response преобразовать в
- 9:17:01GSON. Вот этот сй by, он у нас
- 9:17:03представляет Jon, который получился
- 9:17:04после преобразования вот этой вот
- 9:17:05структуры. После этого мы этот Gon
- 9:17:07записали в ответ. В принципе, всё. У нас
- 9:17:10остаются вот эти вот два вывода на
- 9:17:11экран. Они нам, наверное, не нужны. Если
- 9:17:13хотите, можете оставить, хотите, можете
- 9:17:14убрать. Теперь мы всё равно информацию о
- 9:17:16том, какой у нас новый баланс,
- 9:17:18информацию о том, какая у нас новая
- 9:17:19Payment History, возвращаем в Gsone в
- 9:17:22HTTP ответе. Я вот это вот всё стираю,
- 9:17:24сохраняю файл и запускаю программу.
- 9:17:27Программа запустилась. Ну, теперь
- 9:17:29давайте попробуем купить бургер за 5
- 9:17:32долларов. Так и оставлю тут Иванов Иван
- 9:17:34Иванович, адрес оставлю. Ну и всё,
- 9:17:36попробую сейчас сделать запрос.
- 9:17:37Отправляю. Опа. И мы видим, что у нас, в
- 9:17:40принципе, Gсон, который мы отправляли в
- 9:17:42теле запроса, он был нашей программой
- 9:17:44принят и качественно обработан. Но в
- 9:17:47ответе нам пришло что-то непонятное. На
- 9:17:49самом деле, то, что нам пришло в ответе
- 9:17:50- это самый настоящий GSON, корректно
- 9:17:52оформленный. Но у нас он почему-то в
- 9:17:54одну строчку. В одну строчку он у нас,
- 9:17:56потому что мы воспользовались функцией
- 9:17:57Marshall. Можно было воспользоваться
- 9:17:59немножко другой функцией при переводе
- 9:18:01структурки в GSON. Функция эта
- 9:18:03называется marshall intent. Marshall
- 9:18:06intent. Первым аргументом она точно
- 9:18:07также принимает структуру, которую мы
- 9:18:09будем переводить в GSON, а вторым
- 9:18:11аргументом она принимает какой-то
- 9:18:12префикс. Сейчас нам не важно, что это за
- 9:18:14префикс, вообще его пропускаем. И что
- 9:18:16нас интересует - это intent. Это так
- 9:18:18называемый отступ. Отступ, который будет
- 9:18:20у нас форматировать наш ответный GON.
- 9:18:22Здесь я тоже вставлю пустую строчку и в
- 9:18:24ней нажимаю Tab. Нажал Tab. И вот у меня
- 9:18:27получился Tab. Это будет мой отступ.
- 9:18:29Давайте я сохраню файл, перезапущу
- 9:18:31программу и ещё раз отправлю этот же
- 9:18:33самый запрос. Хоба. И я вижу в ответе у
- 9:18:35меня уже отформатированный же
- 9:18:37получается. Но вообще Postman довольно
- 9:18:39умный инструмент, и поэтому нам не было
- 9:18:40с вами никакой необходимости как-то
- 9:18:42форматировать jon стороне нашего
- 9:18:44пйхенра. Мы могли оставить всё как есть.
- 9:18:46Давайте я верну. У меня обычная функция
- 9:18:48Marshall, которая вроде бы как не
- 9:18:50форматирует нам GSON, делает его просто
- 9:18:52в одну строчку, но Postman с этим умеет
- 9:18:54тоже красиво работать. Я перезапускаю
- 9:18:56программу, ещё раз отправляю HTTP запрос
- 9:18:59из Постна, получаю вот этот вот какой-то
- 9:19:01непонятный Gсон. Но тут у нас в Постмане
- 9:19:03есть такая вот менюшечка. Здесь текст. И
- 9:19:06здесь мы можем выбрать то, что мы
- 9:19:07ожидаем получить именно Jon. Хоп. И
- 9:19:10Postman уже сам автоматически будет
- 9:19:11форматировать нам этот HTTP ответ. Мало
- 9:19:14того, будет ещё и подсвечивать поля и
- 9:19:16значения разными цветами. Просто более
- 9:19:17красиво выглядит. Также попрошу обратить
- 9:19:19внимание, что мы в структуре Payment,
- 9:19:22которая нужна была для того, чтобы
- 9:19:23прочитать входящий GSON в теле HTTP
- 9:19:27запроса. Мы в этой структуре указывали
- 9:19:29вот эти вот jon теги. Мы указывали,
- 9:19:31какое поле в структуре должно
- 9:19:32соответствовать какому полю в джине. Но
- 9:19:35в случае со структурой HTTP Response,
- 9:19:37которая уже служила для того, чтобы мы
- 9:19:38наполнили её какими-то данными и
- 9:19:40преобразовали в GSON в HTTP ответе, мы
- 9:19:44тут уже никакие вот эти вот jon теги не
- 9:19:45указывали. На самом деле эти теги было и
- 9:19:48не обязательно указывать даже в случае
- 9:19:50со структурой Payment. Просто как будет
- 9:19:52работать Гошка, если мы не укажем здесь
- 9:19:54вот эти jon теги и попробуем вот эту вот
- 9:19:56нашу структуру Payment наполнить из
- 9:19:59входящего джесона. А Гошка будет просто
- 9:20:01пытаться угадать, какое поле из
- 9:20:02входящего джисона будет соответствовать
- 9:20:04какому полю структуры в языке
- 9:20:06программирования. Иногда это удобно,
- 9:20:08иногда неудобно. Для того, чтобы
- 9:20:10однозначно фиксировать за каждым полем в
- 9:20:12структуре каждое поле в джисоне, обычно
- 9:20:15всё-таки пишут эти jon теги, чтобы точно
- 9:20:17не было никакой ошибки. И ровно таким же
- 9:20:19образом происходит во время ответа. Нам
- 9:20:21тут не обязательно указывать никакие
- 9:20:23дсон теги. Всё равно во время
- 9:20:25преобразования этого ответа в Gson Гошка
- 9:20:27попробует угадать поля Джисона, которые
- 9:20:29должны представлять поля вот этой вот
- 9:20:31нашей структуры. Но мы, в принципе, это
- 9:20:33можем и видеть. Мы видим, что у нас вот
- 9:20:34это вот поле мани, оно преобразовалось в
- 9:20:36такое же одноимённое поле Money в Gsony.
- 9:20:39Ну и поле payment History точно также у
- 9:20:40нас преобразовалось в поле Payment
- 9:20:42History в этом самом ДО в HTP ответе. Но
- 9:20:44всё-таки хорошей практикой является
- 9:20:46фиксировать эти названия через jonги,
- 9:20:49чтобы уж точно Гошка тут случайно что-то
- 9:20:51там не надумала, то, чего мы совсем не
- 9:20:52ожидаем. Ну, для того, чтобы явно
- 9:20:54видеть, что вот эти джесон теги, они
- 9:20:56влияют на то, какой в итоге получится
- 9:20:57jon, я здесь поле маoney назову m есть у
- 9:21:01меня будет три буквы м в начале. И
- 9:21:03payment history.
- 9:21:05Я назову P history. Вот так вот.
- 9:21:08Перезапускаю нашу программу. Ещё раз
- 9:21:10отправляю http запрос. Опять видим, нам
- 9:21:12пришло что-то непонятное. выбираем
- 9:21:14jonдим, что то, как мы прописали jon
- 9:21:17теги в нашей структуре, напрямую
- 9:21:19повлияло на то, как называются
- 9:21:21соответствующие поля в ответном джисоне.
- 9:21:24Ну, можно сделать вывод, что если у нас
- 9:21:25какие-то структуры используются для
- 9:21:27того, чтобы передавать данные по сети,
- 9:21:29для того, чтобы как-то парсить входящий
- 9:21:31же, либо, наоборот, собирать исходящий
- 9:21:33JON, то хорошей практикой является явное
- 9:21:36указание вот этих вот jн тегов, чтобы
- 9:21:39для нас не было никаких сюрпризов ни во
- 9:21:41время того, когда мы обрабатываем
- 9:21:42входящий GON, ни во время того, когда мы
- 9:21:44собираем исходящий. Также скажу для
- 9:21:46кого-то очевидную, для кого-то не совсем
- 9:21:48мысль, но проговорить её нужно. Json -
- 9:21:50это никакой не язык программирования.
- 9:21:53Нет. На Джисоне мы не описываем никакие
- 9:21:55алгоритмы, никакие функции, циклы,
- 9:21:58ничего такого в дсоне нету. JSON - это
- 9:22:00не язык программирования. JSON - это
- 9:22:03просто удобный формат для представления
- 9:22:05данных и для передачи этих данных по
- 9:22:06сети. Всёни больше и ни меньше. Мы на
- 9:22:09Дсоне ничего не программируем. Мы в
- 9:22:10дсоне просто описываем данные, которые
- 9:22:12хотим передать с клиента на сервер,
- 9:22:14либо, наоборот, вернуть с сервера на
- 9:22:15клиент. удобный формат, легко читаемый,
- 9:22:18отлично переводится в различные
- 9:22:19структуры на уровне языка
- 9:22:20программирования и также отлично из
- 9:22:23структур языка программирования обратно
- 9:22:25в GSON позволяет переводиться. Ну
- 9:22:26давайте попробуем совершить ещё одну
- 9:22:28покупочку. Я куплю себе телефон за 500
- 9:22:30долларов флагман. Full name оставлю тот
- 9:22:33же адрес тот же. Ну как бы тот же самый
- 9:22:34покупатель просто другой товар покупает
- 9:22:36и по другой цене. Отправляю запрос,
- 9:22:38опять получаю какой-то непонятный текст,
- 9:22:40перевожу в jon
- 9:22:43payment History, то есть история моих
- 9:22:45покупок, уже наполнилась второй
- 9:22:46покупкой. У меня маoney осталась 495
- 9:22:49долларов. То есть я сделал изначально
- 9:22:50покупку на 5 долларов, когда покупал
- 9:22:52бургер. Это всё, кстати, отражается в
- 9:22:54истории покупок. Потом купил телефон на
- 9:22:56500 долларов. В итоге 505 долларов
- 9:22:57потратил, из 1.000 осталось 495. Мало
- 9:23:00того, видим, как отрабатывает время,
- 9:23:02которое мы с вами сохраняли, аля время
- 9:23:04проведения оплаты. Первую оплату мы с
- 9:23:06вами совершили 22 июля 2025 года в 19:1
- 9:23:10мину 30 секунд. Тут даже он более мелкое
- 9:23:12деление секунды сохраняется, более
- 9:23:14точное время. Ну и часовой поязд +3. А
- 9:23:16вот покупку телефона мы совершили уже в
- 9:23:1819:20, то есть спустя 6 минут примерно.
- 9:23:21И плюсом, благодаря тому, что мы HTP
- 9:23:24ответы отдаём тоже в Gсоне, те
- 9:23:26программы, которые делали запросы к нам
- 9:23:28на сервер, смогут также со своей стороны
- 9:23:31удобно из приходящего HTTP ответа
- 9:23:33вычитать вот этот вот самый GSON и
- 9:23:35преобразовать его уже в какую-нибудь
- 9:23:36структурку на уровне своего языка
- 9:23:38программирования. То есть в браузере
- 9:23:39даже когда мы что-то с вами в адресной
- 9:23:41строке вбиваем, например, youtube.com,
- 9:23:43на самом деле делается запрос на бэкэнд
- 9:23:45сервер Ютуба и потом нам приходит
- 9:23:47какой-то HTTP-твет. В этом HTTP ответе
- 9:23:49может быть вообще много всякой разной
- 9:23:51информации. Помимо прочего, вот такой
- 9:23:53вот GSON с какими-то данными. Ну а
- 9:23:54браузер делает свои запросы уже
- 9:23:56благодаря такому языку, как JavaScript.
- 9:23:58То есть, по сути, когда мы отдаём вот
- 9:23:59этот вот Gon со своего бэкэнда, то
- 9:24:02frontend, то есть это сам сайтик, сама
- 9:24:04веб-страничка, принимает в HTTP ответе
- 9:24:07этот GSON. И точно так же, как мы на
- 9:24:09бэкэнде преобразовывали GSON во
- 9:24:10внутренние свои структуры, также фронт
- 9:24:13из HTTP ответа может преобразовывать
- 9:24:15GSON, который мы отдали, уже в свои
- 9:24:17внутренние структуры. и благодаря этому
- 9:24:19отрисовывать корректную страницу. Ну,
- 9:24:20если рассматривать опять же тот же
- 9:24:22YouTube, что вот мы можем с бэкэнда,
- 9:24:24допустим, мы разрабатываем эээкэнд
- 9:24:25Ютуба, что мы можем отдавать в качестве
- 9:24:27ответа? Ну, когда пользователь заходит
- 9:24:28на главную страницу, наверное, у него
- 9:24:29там сразу появляются видео с
- 9:24:31рекомендациями. Так вот, мы можем в
- 9:24:33своём HTTP ответе с бэкэнда отдавать вот
- 9:24:36такой вот массив этих самых видео,
- 9:24:38которые нужно показать на главной
- 9:24:39странице. И всё, фронтEN будет получать
- 9:24:41вот этот вот массив и точно также в
- 9:24:44цикле идти по каждому элементу, по
- 9:24:47каждому видео, которое мы вернули, и
- 9:24:49показывать его пользователю. То есть там
- 9:24:50у видео будут поля типа название,
- 9:24:52превьюшка, автор канала, ну и так далее.
- 9:24:55И потом это всё отрисовывается на
- 9:24:56веб-страничке, что ещё раз нам говорит о
- 9:24:58том, что GSON - это очень удобный формат
- 9:25:00для общения по сети. Понятно, не
- 9:25:02единственный, но когда речь заходит
- 9:25:04именно про протокол HTTP, то Gon тут
- 9:25:06просто must have. Резюмируя, можно
- 9:25:09сказать, что GSON - это просто очень
- 9:25:10удобный формат, а что самое главное,
- 9:25:12стандартизированный для передачи данных
- 9:25:14по сети.
- 9:25:17[музыка]
- 9:25:21Уже совсем скоро в рамках этого урока мы
- 9:25:23с вами познакомимся с таким понятием,
- 9:25:25как RP, и напишем небольшое RP backend
- 9:25:27приложение. Но для того, чтобы нам
- 9:25:29подступиться к этому самому RSTP, нам
- 9:25:31для начала нужно изучить, что такое
- 9:25:32query параметры. Темка такая небольшая,
- 9:25:35но достаточно важная. Qy параметры - это
- 9:25:37ещё одна важная и неотъемлемая часть
- 9:25:40HTTP протокола. Вы можете возмутиться,
- 9:25:42как так? Изначально вообще мы думали,
- 9:25:44что всё, что нам нужно знать про HTTP -
- 9:25:45это только три пунктика. Потом
- 9:25:47оказалось, что пунктиков больше. А
- 9:25:49сейчас, оказывается, у нас есть ещё
- 9:25:50какие-то quy параметры. И там вроде
- 9:25:51дальше, дальше, дальше ещё что-то про
- 9:25:53HTP может быть неизвестное. Я вам на это
- 9:25:55отвечу, да. Вот так оно происходит во
- 9:25:57время погружения в IT-сферу. Причём
- 9:25:59неважно, что это, фронт-энд,
- 9:26:01тестирование, мобильная разработка.
- 9:26:03всегда будет оказываться, что в уже
- 9:26:05изученных и пройденных технологиях есть
- 9:26:08какие-то вещи, которые мы с вами не
- 9:26:10знаем. Поэтому по ходу обучения и потом
- 9:26:12по ходу всей вашей дальнейшей карьеры
- 9:26:14очень часто будут встречаться ситуации,
- 9:26:16что уже в изученных технологиях вы
- 9:26:19будете встречать что-то совершенно новое
- 9:26:21и неизученное. С этим можно только
- 9:26:23смириться, это можно принять и не
- 9:26:25расстраиваться, когда, оказывается,
- 9:26:26нужно изучить ещё какую-то тему. В
- 9:26:28принципе, работа программистом, она
- 9:26:30подразумевает обучение каждый день. Даже
- 9:26:32когда вы уже 5 лет работаете, вы всё
- 9:26:34равно практически каждый день учитесь.
- 9:26:36Поэтому не будем терять времени и пойдём
- 9:26:38смотреть, что же вообще такое query
- 9:26:40параметры. Рассмотрим следующий пример.
- 9:26:42У нас есть запрос на домен youtube.com
- 9:26:45pattern/watch и потом какой-то
- 9:26:47непонятный знак вопроса и вообще
- 9:26:49непонятные символы. Значит, что тут
- 9:26:50происходит? Тут происходит следующее. Мы
- 9:26:52делаем запрос на домен youtube.com. Наш
- 9:26:55компьютер автоматически сходит в DNS, то
- 9:26:56есть в domain namestem. и узнает
- 9:26:58IP-адрес серверов Ютуба, получит этот
- 9:27:01самый IP-адрес. И так как у нас здесь
- 9:27:03нигде явно не указан порт, на который мы
- 9:27:05делаем запрос, поэтому сначала запрос
- 9:27:07будет пытаться сделаться на 443 порт.
- 9:27:09Если запрос на 443 порт будет совершён
- 9:27:12удачно, это значит, что у нас соединение
- 9:27:14по протоколу HTPS.
- 9:27:16Напоминаю, HTPS - это то же самое, что и
- 9:27:19HTTP. Просто благодаря вот этой буковке
- 9:27:21S в конце мы понимаем, что соединение у
- 9:27:22нас зашифрованное. Но для нас, как для
- 9:27:25программистов, в данный момент времени
- 9:27:27это, в принципе, ничего не меняет.
- 9:27:29Хорошо получается, на момент, когда мы
- 9:27:30дошли вот до этого слэша, мы уже знаем,
- 9:27:32на какой IP-адрес делать запрос, и мы
- 9:27:34знаем, на какой порт делать запрос.
- 9:27:36Потом мы видим сwatch. Это паттерн или
- 9:27:39же endpint, на который мы делаем запрос.
- 9:27:41Ну, что такое паттерны, мы с вами уже
- 9:27:42разобрали. То есть мы достучались до
- 9:27:44серверов Ютуба, достучались до
- 9:27:46приложения Ютуба, и мы конкретно хотим
- 9:27:47сделать запрос в хендлер, который у нас
- 9:27:49лежит по паттерну/watch для того, чтобы
- 9:27:51нам посмотреть какое-то видео. Хорошо.
- 9:27:54Но потом у нас идёт знак вопроса и
- 9:27:56какая-то непонятная шляпа. Что это
- 9:27:57такое? Это как раз-таки и есть наши
- 9:28:00самые query параметры. То есть мы в
- 9:28:01конце нашего запроса, после его основной
- 9:28:04части можем поставить знак вопроса и
- 9:28:06потом пойдут у нас K valueue пары. K
- 9:28:10value, то есть ключ значения. Если мы
- 9:28:12встречаем амперсант, это значит
- 9:28:13закончилась предыдущая пара, ключ
- 9:28:15значения, начинается новая. Опять ключ
- 9:28:18значения. Что мы тут видим? А мы видим
- 9:28:21благодаря знаку вопроса, что дальше идут
- 9:28:23quy параметры. Какие? V = 123
- 9:28:27и T = 10S.
- 9:28:32Хорошо? То есть с синтаксисом мы
- 9:28:33разобрались. Quy параметры - это
- 9:28:35какие-то опциональные параметры, которых
- 9:28:37может не быть, но которые могут быть.
- 9:28:39Если они есть, то они начинаются со
- 9:28:41знака вопроса и потом идут в парах ключ
- 9:28:44равно значение. И благодаря мперсанду мы
- 9:28:46можем сказать, где первая пара
- 9:28:48ключзначения закончилась, а значит,
- 9:28:49должна начинаться другая. В принципе,
- 9:28:51этого амперсанта может не быть. У нас
- 9:28:52может быть только одна пара
- 9:28:53ключзначения. В данном случае это было
- 9:28:55бы просто V = 123. Но если амперсан
- 9:28:58встречается, это значит дальше пойдёт
- 9:28:59ещё одна пара ключзначения. Ключ
- 9:29:02значения. Если бы тут был ещё один
- 9:29:04амперсант, это значит, что у нас была бы
- 9:29:05в этом запросе третья пара ключ
- 9:29:07значения. Был бы четвёртый амперсант,
- 9:29:10была бы четвёртая пара ключ значения. Ну
- 9:29:11и так далее. Хорошо, мы поняли. Знак
- 9:29:13вопроса говорит, что дальше пойдут квари
- 9:29:15параметры. Амперсант разделяет один
- 9:29:16квари параметр от второго. А сами квари
- 9:29:18параметры - это пары ключ значения. Вот
- 9:29:20ключ значение. Хорошо. Для чего они нам
- 9:29:23нужны? Это какая-то уточняющая
- 9:29:25информация во время нашего запроса. По
- 9:29:27сути то же самое, что и тело запроса.
- 9:29:28Просто эта уточняющая информация у нас
- 9:29:30передаётся не в теле запроса, а в
- 9:29:32quyпараметрах. Вы можете сказать, зачем
- 9:29:34нам нужны эти quyпараметры, если мы ту
- 9:29:36же самую инфу могли бы передать в теле
- 9:29:38входящего запроса. Вот у нас такой же
- 9:29:40запрос на youtube.com/watch.
- 9:29:42Вот он. И мы вместо того, чтобы
- 9:29:44передавать V = 123 и T = 10S в query
- 9:29:47параметрах, мы это передаём в теле
- 9:29:48запроса в Gsony. Всё удобно, понятно и
- 9:29:50легко читаемо. Зачем же нам тогда вообще
- 9:29:52нужны эти самые QY-параметры? И вы
- 9:29:54окажетесь от части правый.
- 9:29:55Действительно, вот эту вот всю
- 9:29:56информацию, что V = 123тt = 10, сейчас
- 9:29:58мы разберём, что за V, что за т, мы
- 9:30:00могли бы передать в теле запроса в
- 9:30:02Gsony. Но у тело запроса есть один
- 9:30:05минус, то, что его нужно заполнять
- 9:30:08благодаря каким-то программам, например,
- 9:30:10благодаря постману, либо ещё благодаря
- 9:30:12каким-то другим утилитам. То есть просто
- 9:30:14взять и вставить в адресную строку
- 9:30:15запрос и автоматически к нему прикрепить
- 9:30:17тело запроса мы не можем. Нам нужно
- 9:30:19открывать постма, нужно открывать вот
- 9:30:20эту вот вкладочку Bди, нужно прописывать
- 9:30:22вот этот вот запрос. И это неудобно. Ну,
- 9:30:25представим такую ситуацию. Я смотрю
- 9:30:26Versus Battle Оксимирон против СТ. И тут
- 9:30:29мне попадается какой-то очень смешной,
- 9:30:31необычный момент. Тот самый легендарный
- 9:30:33Pro.
- 9:30:35Вот такой. Мне смешно, весело. Я хочу
- 9:30:37поделиться этим тайм-кодом с другом. Я
- 9:30:39хочу просто взять, отмотать на 6 минут 1
- 9:30:42секунда, нажать тут кнопку поделиться. И
- 9:30:45тут я могу поставить галочку, что я хочу
- 9:30:47в ссылке зашить этот самый тайм-код.
- 9:30:49Потом эту ссылку я скину другу. вдруг её
- 9:30:50вставит себе в адресную строку, перейдёт
- 9:30:53по ней и в конце концов попадёт на это
- 9:30:55же самое видео, на этот же самый
- 9:30:57тайм-код. Давайте более подробно
- 9:30:58проанализируем ссылку, которая тут у нас
- 9:31:00в итоге получается. А у нас тут, в
- 9:31:02принципе, всё то же самое, что мы с вами
- 9:31:03только что разбирали. У нас есть домен,
- 9:31:05у нас есть паттерн и потом знак вопроса
- 9:31:07и quy параметры. Смотрите, первый quy
- 9:31:09параметр у нас - это V = 4 RNL F9.
- 9:31:13Короче, это просто какой-то
- 9:31:14идентификатор видео. То есть на Ютубе у
- 9:31:15каждого видео есть какой-то
- 9:31:16идентификатор, благодаря которому
- 9:31:18YouTube понимает, какое видео надо
- 9:31:19открывать. Потом у нас идёт амперсан и T
- 9:31:21= 361S. То есть 361S - это тайм-код в
- 9:31:25секундах. 361 секунда - это как раз-таки
- 9:31:276 минут 1 секунда. Тот самый тайм-код,
- 9:31:30на котором мы нажали поделиться ссылкой.
- 9:31:32Получается, у нас благодаря вот этим
- 9:31:34самым query параметрам я смог прямо в
- 9:31:36ссылке зашить дополнительную информацию
- 9:31:38для серверов Ютуба, какое видео я хочу
- 9:31:40открыть и на каком тайм-коде я хочу это
- 9:31:42видео открыть. А почему я не мог эту же
- 9:31:44самую информацию передавать в теле HTTP
- 9:31:47запроса? Да, потому что тогда я бы не
- 9:31:48смог так просто взять и поделиться этой
- 9:31:50самой ссылочкой со своим другом. Мне
- 9:31:52пришлось бы другу давать ссылку
- 9:31:53youtube.com/watch,
- 9:31:55потом говорить ему: "Зайди вот там вот в
- 9:31:57специальные настройки в браузере, вот
- 9:31:59нажми-то такую-то кнопочку, там ты
- 9:32:01сможешь открыть тело запроса входящего,
- 9:32:03и в этом теле запроса вот передай вот
- 9:32:05это вот такой вот же." Мой друг бы на
- 9:32:06меня посмотрел пятирублёвыми глазами и
- 9:32:08сказал: "А что так сложно? Давай не
- 9:32:10надо, не хочу я, короче, твои мемы
- 9:32:11смотреть". Но благодаря тому, что у нас
- 9:32:13вот эта вот дополнительная информация в
- 9:32:14виде идентификатора видео и тайм-кода
- 9:32:17зашита не в теле запроса, а в query
- 9:32:19параметрах, я смог взять и поделиться
- 9:32:21ссылкой со своим другом. А он без
- 9:32:23особого напряга просто берёт эту ссылку,
- 9:32:25вставляет в адресную строку и уже в
- 9:32:28самой ссылке зашиты дополнительные
- 9:32:29параметры, которые нужны для отображения
- 9:32:31нужного видео на нужном тайм-коде. Ну и
- 9:32:33вот такой вот момент, что мы, в
- 9:32:35принципе, да, всю эту инфу мы могли бы
- 9:32:36зашить в теле HTTP запроса, но в query
- 9:32:39параметрах это делать тупо удобнее, тупо
- 9:32:42удобнее для конечного пользователя.
- 9:32:43Такое очень часто можно встретить на
- 9:32:45всяких озонах или Вайберисах, когда вы
- 9:32:47там делаете какой-то запрос, какой-то
- 9:32:49поиск каких-то товаров, ставите там
- 9:32:51какую-то фильтрацию, например, товары
- 9:32:53только с такой-то ценой, и вся вот эта
- 9:32:54вот информация потихонечку начинает
- 9:32:56появляться в query параметрах в запросе.
- 9:32:59И чем больше мы там с вами натыкаем
- 9:33:00всяких разных параметров при поиске, тем
- 9:33:02больше у нас будет quy параметров. Ну
- 9:33:04вот, к примеру, я зашёл на сайт DNS, и
- 9:33:06тут я смотрю телефоны. Смотрим, какой у
- 9:33:08нас вообще путь. У нас уже есть quy
- 9:33:10параметр, категория. Вот категория и
- 9:33:12какой-то номер категории. Теперь я
- 9:33:14давайте поставлю ценовой сегмент вот от
- 9:33:1610 до 18.000 руб. Показать. Опа. И я
- 9:33:20вижу, у меня появился амперсан и
- 9:33:22появилась K value пара. Праice равно
- 9:33:2410.001 тире1.000. А теперь давайте я,
- 9:33:27например, выберу ещё бренд. Вот тут
- 9:33:29Honor. Нажимаю показать. И вижу, что у
- 9:33:31меня ещё quy параметры появились. В
- 9:33:32частности, вот этот последний амперсан
- 9:33:34бренд равно Honor. То есть опять же эта
- 9:33:36всякая дополнительная информация,
- 9:33:38которая идёт на бэкэнд сервера ДНСА, она
- 9:33:40у нас зашивается не в теле входящего
- 9:33:42запроса, а именно в query параметрах.
- 9:33:44Почему? Да потому что я потом смогу вот
- 9:33:46так вот взять, скопировать эту ссылку,
- 9:33:47отправить своему другу. вдруг вставит
- 9:33:49эту ссылку в адресную строку, откроет, и
- 9:33:51у него сразу же все мои параметры поиска
- 9:33:53сохранятся, и он сможет посмотреть, что
- 9:33:55я вообще искал, какой поиск я
- 9:33:56рассматривал и так далее. А если бы вся
- 9:33:58эта инфа была зашита в теле запроса,
- 9:34:00тогда просто друг бы, ну, не смог
- 9:34:01открыть этот сайт. Ему бы пришлось там
- 9:34:03скачивать постма, устанавливать все эти
- 9:34:05параметры в теле HTTP запроса,
- 9:34:07отправлять запрос, смотреть ответ,
- 9:34:09сохранять ответ в какой-то файл,
- 9:34:10открывать его. Короче, это было бы очень
- 9:34:12жёстко. А так у нас вся эта информация
- 9:34:14зашита в QRY параметрах. Мы можем
- 9:34:15спокойненько эту ссылочку копировать,
- 9:34:16отправлять другу. вдруг её будет у себя
- 9:34:18в адресной строке вбивать и сразу же
- 9:34:20открывать в случае с Ютубом нужное видео
- 9:34:22на нужном тайм-коде, в случае с ДНСом
- 9:34:24нужную категорию в нужном ценовом
- 9:34:25сегменте с нужными фильтрами. Просто
- 9:34:27иногда в некоторых сценариях
- 9:34:28использование quy параметров удобнее для
- 9:34:31конечного пользователя, для
- 9:34:32пользователя, который будет использовать
- 9:34:33нашу систему. Потому что не все в этом
- 9:34:35мире программисты жёсткие. Кто-то
- 9:34:36обычный юзер, который хочет вставить
- 9:34:38ссылочку в браузер и открыть сразу же
- 9:34:40нужные материалы. Значит ли это, что
- 9:34:42тело HTTP запроса - это ненужная вещь? И
- 9:34:45вообще нам всегда нужно использовать
- 9:34:47query параметры. Нет, не значит. Иногда
- 9:34:49гораздо удобнее использовать тело
- 9:34:51запроса, чем queryпараметры. И тут сразу
- 9:34:53ещё один вопрос: когда использовать
- 9:34:55queryпараметры, когда использовать нам
- 9:34:57тело запроса. Вообще, если говорить
- 9:34:59глобально, то тогда, когда вы захотите.
- 9:35:02Как в вашей команде принято, так обычно
- 9:35:04и делается. Где-то вообще никогда не
- 9:35:06используются параметры, а всё передаётся
- 9:35:08в теле запроса. Действительно, такое
- 9:35:10где-то есть. Где-то наоборот в теле
- 9:35:11запроса ничего не передаётся и вообще
- 9:35:13всё пихается в query параметры. И на
- 9:35:14самом деле программисты довольно давно
- 9:35:16столкнулись с этой проблемой, что нет
- 9:35:17какой-то стандартизации, когда
- 9:35:18использовать quyery параметры, когда
- 9:35:20использовать тело запроса, как называть
- 9:35:22вот эти вот наши паттерны, какие HTTP
- 9:35:24методы использовать. И как раз-таки
- 9:35:26ответом на все эти бесконечные вопросы
- 9:35:28стал Rest AP. Ну давайте на практике
- 9:35:30быстренько пощупаем, что же такое эти
- 9:35:31наши qy параметры. Я буду отправлять
- 9:35:33следующий запрос в наше backend
- 9:35:34приложение. Понятное дело, local host на
- 9:35:3690.0001 порт с/default, то есть я буду
- 9:35:39использовать pattern default и потом
- 9:35:41знак вопроса и передам два query
- 9:35:43параметра. Первый фускай равно x и
- 9:35:46второй буска равно y. Всё. Моя задача -
- 9:35:50описать httpх handler, который на
- 9:35:51паттернет будет принимать два вот этих
- 9:35:53вот query параметра. Ну давайте это и
- 9:35:55начнём делать. Я сделаю сам хдр, назову
- 9:35:58его хнler.
- 9:36:00И каким образом я изнутри хендлера могу
- 9:36:02достать вот эти вот самые query
- 9:36:04параметры. На самом деле всё довольно
- 9:36:05просто. Я обращаюсь к HTTP-реквесту,
- 9:36:07потому что это тот самый аргумент, в
- 9:36:08котором содержится вообще вся инфа о
- 9:36:10входящем запросе. R. URL.Query.
- 9:36:14И тут я могу писать точка get и сюда
- 9:36:18передавать ключ. Как видите, точка get.
- 9:36:20То есть этот метод, он у меня принимает
- 9:36:21какой-то ключ. В моём случае у меня два
- 9:36:23ключа: фу и бу, но я их буду получать по
- 9:36:25очереди. Сначала фу, передаю фу. И get у
- 9:36:28меня возвращает какую-то строку. Тут
- 9:36:30написано, что мы с вами получим
- 9:36:31значение, которое лежит по переданному
- 9:36:33ключу. Но если ключа нет, тогда нам
- 9:36:35просто пустая строка вернётся. Ну,
- 9:36:36значит, сохраняю переменную фурам.
- 9:36:39Ну, и то же самое, в принципе, я буду
- 9:36:40делать для бурам. r.l.
- 9:36:45И тут бу. После этого просто выведу их
- 9:36:47на экран. Фурам. Фупарам и бу парам. Вот
- 9:36:53он наш буpрам. Сохраняю. Теперь всё, что
- 9:36:55мне нужно сделать - это зарегистрировать
- 9:36:56этот хендр http handle funk
- 9:36:59pattern/default.
- 9:37:01Ну и вот он наш handler. После этого я
- 9:37:03буду запускать наш HTTP сервер http len
- 9:37:06and serve на 9.000 девно перво порту. И
- 9:37:09если у меня ошибка неравна, тогда просто
- 9:37:12выведу об этом информацию на экран.
- 9:37:13Failed to run http server.
- 9:37:17Вот так вот. Всё, запускаю эту
- 9:37:18программу.
- 9:37:20Программа запущена. И теперь я могу
- 9:37:22начать описывать свой запрос. Запрос у
- 9:37:24меня будет на local host, на 991 порт,
- 9:37:26на Patter/default. И поехали. Отправим
- 9:37:29сначала просто этот запрос без всяких
- 9:37:31quy параметров. отправляю и вижу у меня
- 9:37:33фу парам - это вообще пустая строка и
- 9:37:34буp парам тоже пустая строка. Ну, потому
- 9:37:36что мы не передали вот эти самые
- 9:37:37query-параметры. Теперь поехали. Я
- 9:37:39оставлю знак вопроса и тут пишу фу = x,
- 9:37:43то есть передам только query параметam.
- 9:37:46Отправляю оп. И теперь у меня фу парам
- 9:37:48уже есть, но бурамы ещё нету. Теперь я
- 9:37:50ставлю амперсан и пишу бу.
- 9:37:53Отправляю и вижу, что я теперь смог
- 9:37:55передать и фу парам, и бурам. Ну, в
- 9:37:58принципе, я тут могу писать не только X
- 9:37:59y, вообще могу писать что угодно. могу
- 9:38:00тут написать hello world, а бу я могу
- 9:38:03написать how are you. Таким вот образом
- 9:38:07отправляю и вижу, что я эти самые
- 9:38:08параметры принял. Да, в целом и всё. Мы
- 9:38:10с вами посмотрели, что такое quy
- 9:38:11параметры. Мы поговорили, для чего они
- 9:38:13вообще нужны, какая информация в них
- 9:38:15содержится. Мы поговорили о том, почему
- 9:38:17мы не передаём эту же самую информацию в
- 9:38:19теле запроса в Gsony. Хотя, казалось бы,
- 9:38:21это такой хороший, удобный формат. Мы с
- 9:38:23вами посмотрели, как в нашем запросе эти
- 9:38:24самые quy параметры передать, и
- 9:38:26посмотрели, как эти самые query
- 9:38:27параметры получить изнутри HTTPх
- 9:38:29хендлера. Теперь я считаю, что тема Quy
- 9:38:31параметры пройдена, и мы можем
- 9:38:33приступать к следующей.
- 9:38:36[музыка]
- 9:38:40И наконец-таки мы с вами дошли до темы
- 9:38:42rest. Вообще тут произношение может быть
- 9:38:44разное. Кто-то называет REST AP, кто-то
- 9:38:46называет Rest, кто-то называет REST API.
- 9:38:49Я буду называть Rest AP, потому что мне
- 9:38:51так просто привычнее. Так вот, что же
- 9:38:53такое REST AP? Мы с вами изучили очень
- 9:38:54много тем касаемо HTTP протокола. И
- 9:38:57каждый раз, когда мы с вами будем писать
- 9:38:59какой-то новый хендр, новый endpoint
- 9:39:01описывать, перед нами может встать
- 9:39:03огромное количество вопросов. Первый
- 9:39:05вопрос: как мне правильно назвать мой
- 9:39:07паттерн? Или же как мне правильно
- 9:39:08назвать мой endpoint? Например, у нас
- 9:39:10какой-нибудь хендлер, который проводит
- 9:39:11оплату, и нам его как назвать? Сpay или
- 9:39:14с/transaction или с/money? Там короче,
- 9:39:16вариантов может быть много. И вот первый
- 9:39:18вопрос, который перед нами каждый раз
- 9:39:19будет вставать - это как назвать этот
- 9:39:21самый паттерн. О'кей. Второй вопрос:
- 9:39:23какой HTTP метод использовать? помним,
- 9:39:25да, у нас есть куча методов get, po,пу и
- 9:39:28так далее, и так далее, и так далее. И
- 9:39:29непонятно, какой брать. Можно вообще на
- 9:39:31них, наверное, забить и просто везде
- 9:39:33пост использовать или везде использовать
- 9:39:34get. Тоже вопрос возникает: а какой в
- 9:39:36итоге HTTP метод нам нужно использовать?
- 9:39:38Далее, когда мы с вами описываем
- 9:39:40какой-то хендлер, какой-то endpoint мы с
- 9:39:41вами описываем и хотим передавать туда
- 9:39:43какую-то добавочную информацию,
- 9:39:45например, у нас опять хендлер, который
- 9:39:46проводит оплату, и я хочу передавать
- 9:39:48количество долларов, на которое нужно
- 9:39:50эту самую оплату совершить. И мне как
- 9:39:51передавать эту информацию? В теле HTTP
- 9:39:54запроса в джине или в query параметрах?
- 9:39:56Или, может, ещё как-нибудь нужно
- 9:39:57передавать? Тоже вопрос возникает. Далее
- 9:39:59вопрос: что нам нужно возвращать в теле
- 9:40:01ответа? То есть вот у нас произошёл
- 9:40:03запрос и, допустим, всё было успешно. А
- 9:40:05что нам нужно вернуть в теле ответа? Что
- 9:40:06конкретно? Вот чтобы это было правильно,
- 9:40:08тоже непонятно. Вот, ну, чтобы это хотя
- 9:40:10бы работало как-то. А если у нас ошибка
- 9:40:11произошла, что возвращать? Как-то текст
- 9:40:14или, может, какой-нибудь Gсон надо
- 9:40:15вернуть? А если Gсон, то какой? Короче,
- 9:40:16тоже очень много вопросов и так далее, и
- 9:40:18так далее, и так далее. Очень много
- 9:40:19вопросов будет возникать по мере того,
- 9:40:21как мы с вами будем описывать наши
- 9:40:22эндпоинты. Где искать ответы на все эти
- 9:40:24вопросы? Как раз-таки REST AP - это и
- 9:40:26есть тот самый набор правил и принципов,
- 9:40:29который призван дать ответы на все эти
- 9:40:30вопросы. Это не значит, что Rest - это
- 9:40:32единственная правильная парадигма для
- 9:40:34разработки каких-то наших HTTP-сервисов.
- 9:40:36Это не значит, что Resta используется
- 9:40:38везде и повсеместно. Нет, я вам даже
- 9:40:39скажу, что Resta P используется далеко
- 9:40:41не везде, но какие-то отдельные части
- 9:40:44так или иначе используются практически
- 9:40:46везде. То есть прямо полностью от и
- 9:40:48доста обычно не используется либо
- 9:40:50используются довольно редко. Но вот
- 9:40:52какие-то отдельные принципы из этого RP
- 9:40:54их можно встретить практически везде.
- 9:40:55Поэтому в целом понимать, что такое RP,
- 9:40:57какие правила он в себе содержит, это
- 9:40:59очень полезно для нас, как для
- 9:41:00разработчиков. Понятное дело, после
- 9:41:02обсуждения всех принципов RESTP мы с
- 9:41:03вами закрепим это на практике. А именно
- 9:41:05у нас в практике будет следующий проект
- 9:41:07тудулист, список дел. По сути, мы его
- 9:41:09уже писали в конце первой части полного
- 9:41:12курса пон в качестве учебного проекта.
- 9:41:14Поэтому сейчас писать, объяснять весь
- 9:41:16тудулист, который мы с вами уже писали,
- 9:41:18я не буду, потому что времени, к
- 9:41:19сожалению, у нас в этом видео осталось
- 9:41:20крайне мало, а обсудить нам нужно ещё
- 9:41:22крайне много. Код там совсем не сложный,
- 9:41:24но если кому-то хочется посмотреть на
- 9:41:26то, как мы этот код писали, и объяснение
- 9:41:28этого самого кода, то приглашаю в наше
- 9:41:29приватное сообщество. Но также
- 9:41:31напоминаю, что код этого тудолиста, он в
- 9:41:32открытом доступе абсолютно на нашем
- 9:41:34гитхабе, ссылочка в описании. Так вот,
- 9:41:36мы возьмём в основу написанный ранее
- 9:41:38todдулист, чуть-чуть его модифицируем, и
- 9:41:40у нас уже будет готова функциональность
- 9:41:42по добавлению, сохранению, удалению
- 9:41:43задач. Но для того, чтобы мы могли
- 9:41:46добавлять, удалять и искать задачи по
- 9:41:48сети благодаря нашим HTTP запросам,
- 9:41:50чтобы мы наши приложение могли
- 9:41:51развернуть где-то на удалённом сервере и
- 9:41:53слать туда запросы и получать какие-то
- 9:41:55осмысленные ответы. Для этого нам вот
- 9:41:57эту вот всю функциональность, написанную
- 9:41:58уже тудолиста, нужно будет покрыть
- 9:42:00HTTP-сервером, HTTP хендлерами. Да не
- 9:42:03просто HTTPхлерами, а такими хендлерами,
- 9:42:05которые у нас будут удовлетворять
- 9:42:06принципам RSP. Ну давайте посмотрим, что
- 9:42:08наш тудулист должен уметь. Тут по сути
- 9:42:10всё, что было в нашем учебном проекте,
- 9:42:11кроме четвёртого пункта, получать список
- 9:42:14всех невыполненных задач. То есть раньше
- 9:42:16мы могли просто получать список всех
- 9:42:17задач, а сейчас я хочу уметь как
- 9:42:19получать список всех задач, так и
- 9:42:21получать список всех невыполненных
- 9:42:22задач. Ну то есть, допустим, у меня вот
- 9:42:24куча куча куча каких-то задач. раз
- 9:42:26задача, два задача, три задачи. И
- 9:42:28какие-то из этих задач у меня уже
- 9:42:29выполнены, а я хочу посмотреть только
- 9:42:31то, что у меня не выполнено. Поэтому я
- 9:42:33хочу уметь получать список всех
- 9:42:34невыполненных задач. Ну а в остальном я
- 9:42:36говорю всё то же самое, что у нас уже с
- 9:42:37вами было на учебном проекте в конце
- 9:42:38первой части. То есть мы должны уметь
- 9:42:40создавать новую задачу, получать
- 9:42:41информацию по этой созданной задаче,
- 9:42:43получать информацию по всем существующим
- 9:42:45задачам, получать список всех
- 9:42:47невыполненных задач, отмечать какую-то
- 9:42:48задачу как выполненную. Ну то есть вот у
- 9:42:50меня там есть список дел, я когда
- 9:42:51какое-то дело сделал, я беру и отмечаю
- 9:42:53задачу как выполненную. Всё, я теперь
- 9:42:55понимаю, что мне не нужно возвращаться
- 9:42:56больше к этой задаче. Ну и также я хочу
- 9:42:58уметь удалять какую-то уже ранее
- 9:42:59созданную задачу. Ну то есть логично,
- 9:43:01да? Неважно, задача выполнена или нет.
- 9:43:03Иногда бывают ситуации, когда я просто
- 9:43:04не хочу больше эту задачу в своём списке
- 9:43:06дел видеть. Я хочу уметь её удалять. Ну
- 9:43:08я думаю, всё понятно. То есть мы берём в
- 9:43:09основу тот код туду листа, который мы с
- 9:43:11вами писали в учебном проекте для первой
- 9:43:13части. Оборачиваем этот код в HTTP
- 9:43:15сервер, то есть предоставляем доступ к
- 9:43:17функциональности тудулиста по сети. То
- 9:43:19есть теперь можно будет делать сетевые
- 9:43:20запросы в наше приложение и уже какие-то
- 9:43:22HTTPхлеры будут принимать эти самые
- 9:43:24запросы и пользоваться ранее описанной
- 9:43:26функциональностью туду листа. Ну и
- 9:43:28потом, соответственно, мы будем отдавать
- 9:43:29какие-то HTTP ответы. Максимально быстро
- 9:43:31пробежимся по структуре того, как мы
- 9:43:32будем эти самые задачи хранить. То есть
- 9:43:34у нас каждая задача будет из себя
- 9:43:35представлять структуру из следующего
- 9:43:37набора полей. Заголовок. То есть у
- 9:43:38каждой задачи у нас будет заголовок,
- 9:43:40например, домашка. Потом у каждой задачи
- 9:43:42будет описание. То есть это уже более
- 9:43:43подробное описание, что именно нужно
- 9:43:45сделать. То есть заголовок, домажка,
- 9:43:46описание, сделать домашку по математике
- 9:43:48на такое-то число, там такая-то страница
- 9:43:49учебника и так далее, и так далее, и так
- 9:43:51далее. Также у каждой задачи будет
- 9:43:52информация о том, выполнена ли она или
- 9:43:54нет. Ну, понятно, булевская переменная.
- 9:43:55То есть true false выполнена или нет.
- 9:43:56Дата создания будет у каждой задачи и
- 9:43:58дата выполнения. То есть я смогу
- 9:44:00посмотреть, когда я задачу создал и
- 9:44:02когда я задачу выполнил. Вот это
- 9:44:03структура нашей задачи. Далее вопрос,
- 9:44:05как нам задачи хранить? То есть опять же
- 9:44:06мы можем хранить там в статическом
- 9:44:07массиве, в слайсе в маapпе, но тут, в
- 9:44:09принципе, ровно такая же логика у нас
- 9:44:10применяется, как и в случае с Payment
- 9:44:12модулем, когда мы в самом начале данного
- 9:44:14урока рассматривали интерфейсы и думали,
- 9:44:16что же нам выбрать для хранения
- 9:44:17информации об оплате слайс или мапу. Вот
- 9:44:19тут ситуация ровно такая же. То есть,
- 9:44:20допустим, я создал там три каких-то
- 9:44:22задачи и хочу получить одну из них. То
- 9:44:25есть, как я буду это делать? Ну, я,
- 9:44:26наверное, посмотрю. У меня там вот будет
- 9:44:28приложение, в нём все заголовки задач, и
- 9:44:30я, когда буду нажимать на какую-то
- 9:44:31задачу, то есть на заголовок, я хочу
- 9:44:33получать полное описание задачи. Я как
- 9:44:35бы мог хранить бы это и в слайсе, но
- 9:44:36потом я вот, допустим, нажму на
- 9:44:38заголовок один, и мне придётся весь
- 9:44:39слайс пробегать и искать задачу с
- 9:44:41заголовком один. Это время Он N. Это не
- 9:44:43так уж и оптимально, опять же, здесь у
- 9:44:45нас куча задач будет, поэтому я
- 9:44:46предпочитаю хранить тут всё в мапе.
- 9:44:48Ключом будет заголовок задачи, а
- 9:44:49значением сама задача. И таким образом я
- 9:44:51по заголовку задачи смогу сразу же
- 9:44:53получать полную информацию об этой самой
- 9:44:54задаче. Например, я нажму на заголовок
- 9:44:56Домашка, и мне сразу же вся информация
- 9:44:58об этой задаче будет дана. Я смогу
- 9:44:59узнать, я выполнил эту задачу, не
- 9:45:01выполнил, когда я её создал, полное
- 9:45:03описание задачи и так далее. О'кей, тут
- 9:45:04я надеюсь разобрались. Повторюсь, более
- 9:45:06подробное объяснение по функциональности
- 9:45:08самого тудулиста, именно добавление,
- 9:45:10удаление, получение, изменения задач -
- 9:45:13это всё у нас в приватном сообществе.
- 9:45:15Сейчас, к сожалению, времени совсем у
- 9:45:17нас не остаётся, чтобы максимально
- 9:45:18разбирать всю эту функциональность
- 9:45:19тудолиста. Но я уже верю, что если вы
- 9:45:21досмотрели до настоящего момента и
- 9:45:22прорешали все домашки до этого, то вы
- 9:45:24вполне в состоянии понять тот код,
- 9:45:26который сейчас будет перед нами открыт.
- 9:45:27Ну, поехали смотреть нулевой принцип
- 9:45:30restp. Нумерация принципов, она была
- 9:45:32выбрана лично мной. Это во-первых. А
- 9:45:34во-вторых, у нас сейчас будут с вами
- 9:45:35перечислены далеко не все принципы, но
- 9:45:38основные мы, конечно же, перечислим.
- 9:45:40Просто потому, что там этих принципов
- 9:45:41слишком много, некоторые максимально
- 9:45:43расплывчатые. И по большому счёту Rest
- 9:45:45AP - это парадигма. Парадигма, в которой
- 9:45:47мы должны описывать наши сетевые
- 9:45:49приложения. И вот как я вынес нулевой
- 9:45:51пункт этой парадигмы - это то, что у нас
- 9:45:53должна быть клиент серверная
- 9:45:54архитектура. Что значит клиент серверная
- 9:45:56архитектура? Это значит, что если у нас
- 9:45:57есть множество пользователей, то эти
- 9:45:59пользователи должны общаться не каждый с
- 9:46:01каждым, генерируя просто какое-то
- 9:46:03бешеное количество сетевых
- 9:46:04взаимодействий, а эти все пользователи
- 9:46:06должны общаться с каким-то сервером. То
- 9:46:08есть у нас есть сервер и есть
- 9:46:09пользователи, которые в качестве
- 9:46:10клиентов будут в этот сервер ходить.
- 9:46:12Сервер будет как-то обрабатывать их
- 9:46:14запросы и отдавать ответы. Это и есть
- 9:46:15клиент серверная архитектура. И вот
- 9:46:17нулевой, так сказать, принцип - это то,
- 9:46:19что должна быть именно клиент серверной
- 9:46:21архитектура, а никакая другая. Мы
- 9:46:22описываем наш сервер и предоставляем
- 9:46:24клиентам контракт для взаимодействия с
- 9:46:26нашим сервером. Что такое контракт в
- 9:46:28нашем случае? В нашем случае контракт -
- 9:46:30это набор HTTP паттернов, набор методов,
- 9:46:32которые эти HTTP паттерны принимают,
- 9:46:34набор значений, которые хендлеры
- 9:46:35дополнительно требуют в качестве
- 9:46:37уточняющей информации, возвращаемый
- 9:46:38статус кода этих хендлеров, возвращаемая
- 9:46:40в теле ответа информация из этих
- 9:46:42хендлеров и так далее, и так далее, и
- 9:46:43так далее. То есть просто буквально
- 9:46:44декларация того, как нужно с нашим
- 9:46:46сервером работать, какие запросы куда и
- 9:46:48как отправлять, какие при этом ответы
- 9:46:49стоит ждать. Это есть контракт. И этот
- 9:46:51самый контракт, он должен удовлетворять
- 9:46:53требованиям RP, которые мы с вами
- 9:46:54рассмотрим далее. Значит, смотрим далее.
- 9:46:56Первое правило нашего RP - это
- 9:46:59определённый стиль именования паттернов
- 9:47:01или же энпоинтов. Давайте посмотрим,
- 9:47:02какое тут правило. Паттерны или же
- 9:47:04энпоинты должны именоваться
- 9:47:06существительными. Никаких глаголов. А
- 9:47:09существительное - это название ресурса,
- 9:47:11над которым производится работа. У нас
- 9:47:13дулист. Над каким ресурсом у нас будет
- 9:47:16производиться работа? Над задачей. То
- 9:47:18есть у нас есть задачи наши, и это
- 9:47:20основной ресурс, над которым наше
- 9:47:21приложение будет производить работу. Мы
- 9:47:23эти задачи будем хранить, мы эти задачи
- 9:47:25будем менять, мы эти задачи будем
- 9:47:26удалять, мы эти задачи будем отдавать в
- 9:47:28наших ответах. Именно поэтому можно
- 9:47:30сказать, что над задачами мы будем
- 9:47:32производить какую-то работу. То есть
- 9:47:33задача - это ресурс, над которым
- 9:47:35производится работа в нашем приложении.
- 9:47:37А задача с английского tas та с к.
- 9:47:41Соответственно, паттерн у нас должен
- 9:47:42именоваться существительным, то есть
- 9:47:44названием ресурса, но обычно во
- 9:47:46множественном числе именуют, поэтому
- 9:47:47tasks вот это наш паттерн будет. И что
- 9:47:49самое сейчас, наверное, для вас будет
- 9:47:50удивительное, что этот самый
- 9:47:52pattern/asks у нас будет одинаковый для
- 9:47:54абсолютно всех хендлеров. Да. Да, вот
- 9:47:57такие вот дела. Ну давайте посмотрим на
- 9:47:58эту таблицу. Действие у нас создать
- 9:48:00новую задачу. Как бы мы с вами раньше бы
- 9:48:02назвали вот этот самый endpint для
- 9:48:04создания новой задачи. Ну, логично
- 9:48:05назвать это create task, то есть создать
- 9:48:07задачу. Далее наше действие - это
- 9:48:09получить информацию по уже созданной
- 9:48:10задаче. То есть мы создали какую-то
- 9:48:11задачу, потом хотим получить информацию
- 9:48:13по ней. Ну, get task, то есть получить
- 9:48:15задачу. Если получить информацию по всем
- 9:48:17задачам, то это get all task. Ну, типа
- 9:48:19логично вроде, да? Если мы хотим
- 9:48:21получить список всех невыполненных
- 9:48:23задач, то это get unfulfilled tasks. Ну,
- 9:48:25то есть unfulfield - это типа
- 9:48:26невыполненные. Ну, тоже логично, да.
- 9:48:28Потом отметить определённую задачу как
- 9:48:30выполнену. Don't task. Ну, типа
- 9:48:31выполнить задачу. Далее удали
- 9:48:33существующую задачу, дали task. И
- 9:48:34казалось бы, всё круто, но нет. Rest AP
- 9:48:36нам говорит, что мы должны называть все
- 9:48:38наши паттерны, все наши эндпоинты.
- 9:48:40каким-то существительным. То есть сразу
- 9:48:42же все эти глаголы, get, get, там,
- 9:48:46delete, нет никаких глаголов. Мы должны
- 9:48:48назвать это существительным. А
- 9:48:49существительное - это название ресурса,
- 9:48:50над которым производится работа. То
- 9:48:52есть, если бы у нас был какой-то
- 9:48:53мессенджер, то ресурс, над которым
- 9:48:54производится работа - это сообщение.
- 9:48:56Если у нас там социальная сеть, то
- 9:48:57ресурс - это у нас там пользователи.
- 9:48:59Если у нас какой-то видеохостинг, то
- 9:49:00ресурс - это видео. У нас туду лист, у
- 9:49:02нас ресурс - это задача. Соответственно,
- 9:49:04мы должны все наши паттерны именовать
- 9:49:06этим самым ресурсом во множественном
- 9:49:07числе. tasks везде на любое действие у
- 9:49:10нас Tasks. Вы спросите, как же тогда нам
- 9:49:12разные хендлеры прописывать для
- 9:49:14обработки разных действий, если у нас
- 9:49:15всё висит на одном паттерне? Вопрос
- 9:49:17валидный. Посмотрим далее, как он
- 9:49:18решается. Но пока мы поняли, что мы
- 9:49:20определяем ресурс, над которым у нас
- 9:49:21будет производиться работа, и называем
- 9:49:23соответствующим образом наш endpoint.
- 9:49:25Далее у нас второе правило Rest AP.
- 9:49:27Строго определённые HTTP методы под
- 9:49:29каждый вид манипуляции над ресурсами. То
- 9:49:32есть в предыдущем правиле мы определили
- 9:49:34над каким именно ресурсом у нас будет
- 9:49:35производиться работа. Мы поняли, что это
- 9:49:37у нас задачи, то есть tasks. Теперь нам
- 9:49:40под каждый отдельный хендлер нужно
- 9:49:42понять, какие именно манипуляции над
- 9:49:44этими ресурсами, то есть над нашими
- 9:49:46задачами, этот самый хендлер будет
- 9:49:48производить. Давайте смотреть. Создание
- 9:49:50нового ресурса. То есть создание нового
- 9:49:52ресурса в нашем случае - это создание
- 9:49:54новой задачи. То есть ресурс - это
- 9:49:55задача. Если мы создаём новую задачу,
- 9:49:57значит, мы создаём новый ресурс на нашем
- 9:49:59сервере. И если у нас производится
- 9:50:00создание нового ресурса, тогда HTTP
- 9:50:02метод должен быть пост, так как именно
- 9:50:05пост метод он предназначен для того,
- 9:50:06чтобы описывать хендлеры, которые
- 9:50:08создают новые ресурсы. Поэтому, если у
- 9:50:10нас идёт создание новой задачи, то мы
- 9:50:12должны выбрать не какой-то рандомный
- 9:50:13метод, как мы с вами до этого делали,
- 9:50:14вообще пофигу, какой, а мы конкретно
- 9:50:16должны выбрать именно метод пост. О'кей.
- 9:50:19Далее у нас получение уже существующего
- 9:50:21ресурса. То есть мы с вами там
- 9:50:23понасоздавали всяких разных задачек и
- 9:50:25потом хотим получить о них информацию.
- 9:50:26либо одну какую-то задачу, либо сразу
- 9:50:28целый список всех задач. И вот получение
- 9:50:31каких-то уже существующих ресурсов
- 9:50:32сервера - это getметод. Опять же, ни
- 9:50:35какой-то рандомный ни пост, ни патч, ни
- 9:50:37пут, ни delлит, нет. Если мы с вами
- 9:50:38получаем информацию с нашего сервера, мы
- 9:50:41никак не меняем ресурсы на сервере, мы
- 9:50:43не обновляем их, мы их не удаляем, мы не
- 9:50:45создаём новые, мы просто получаем уже
- 9:50:47существующие. Это getзапрос.
- 9:50:50Далее смотрим у нас получение информации
- 9:50:52по уже созданной задаче, получение
- 9:50:54информации по всем задачам. То же самое,
- 9:50:56несмотря на то, что мы вместо одной
- 9:50:57задачи получаем все задачи. Это всё ещё
- 9:50:59просто обычное получение ресурсов
- 9:51:00сервера. Тоже GT запрос. Далее, получить
- 9:51:03список всех невыполненных задач.
- 9:51:05Несмотря на то, что у нас будет
- 9:51:06фильтрация по невыполненным, то есть мы
- 9:51:08будем отфильтровывать выполненные и
- 9:51:10оставлять невыполненные, всё равно мы
- 9:51:11просто получаем ресурсы с сервера. Мы
- 9:51:13никак их не меняем, не обновляем, не
- 9:51:15создаём новые, не удаляем. Поэтому это
- 9:51:17опять же getзапрос, то есть просто
- 9:51:19получение уже существующих ресурсов
- 9:51:20сервера. Смотрим далее. Частичное
- 9:51:22изменение существующего ресурса. Что это
- 9:51:25значит? Это значит, что у нас, допустим,
- 9:51:26есть какая-то задача на нашем сервере, и
- 9:51:28мы хотим её какое-то одно поле изменить,
- 9:51:31например, изменить её описание или
- 9:51:33изменить информацию о том, выполнена ли
- 9:51:34эта задача или нет. Это частичное
- 9:51:36изменение существующего ресурса. Для
- 9:51:38такого у нас предусмотрен метод pat
- 9:51:39патч. Также у нас есть полное изменение
- 9:51:41существующего ресурса. Для этого у нас
- 9:51:43предусмотрен метод пут. На самом деле
- 9:51:44полное изменение существующего ресурса -
- 9:51:46это несколько более редкая операция по
- 9:51:48сравнению со всеми остальными. Поэтому
- 9:51:50даже в нашем примере с тудулистом у нас
- 9:51:51не будет полного изменения существующего
- 9:51:53ресурса. Но в нашем случае, что бы такое
- 9:51:55было полное изменение существующего
- 9:51:56ресурса? Это полное изменение заголовка,
- 9:51:58описания, времени создания. То есть
- 9:52:00буквально просто мы создаём новый
- 9:52:02ресурс, но идентификатор этого ресурса
- 9:52:04остаётся старым. А что в нашем случае
- 9:52:06идентификатор ресурса, мы с вами
- 9:52:08рассмотрим в следующем правиле. Поэтому
- 9:52:09в нашем случае у нас не будет метода пт
- 9:52:11в нашем приложении, но это ничего
- 9:52:13страшного. То есть не обязательно, чтобы
- 9:52:14у нас абсолютно все методы были в нашем
- 9:52:16приложении для того, чтобы оно считалось
- 9:52:18приложением. Ну вот частичное изменение
- 9:52:20существующего ресурса у нас, конечно же,
- 9:52:22будет, а это именно отмечание задачи как
- 9:52:25выполненной. То есть вот наша какая-то
- 9:52:26задача, и мы, когда будем отмечать
- 9:52:28задачу как выполненную, мы будем менять
- 9:52:30вот это вот поле, выполнено ли. То есть
- 9:52:32мы меняем всего лишь одно поле, в
- 9:52:33остальном весь ресурс у нас остаётся
- 9:52:35прежним, поэтому это лишь частичное
- 9:52:37изменение уже существующего ресурса. А
- 9:52:39если у нас частичное изменение
- 9:52:40существующего ресурса - это метод патч.
- 9:52:43Ну и поэтому повторюсь, когда мы будем
- 9:52:44отмечать задачу как выполненную, мы лишь
- 9:52:46частично будем менять какой-то единичный
- 9:52:47ресурс на сервере. Поэтому это у нас
- 9:52:49метод patch. Ну и далее удаление
- 9:52:51существующего ресурса - это уже,
- 9:52:52очевидно, delete. Ну и у нас тут тоже
- 9:52:54удалить какую-то существующую задачу.
- 9:52:56Это удаление ресурса с сервера. Это
- 9:52:58метод delete. Опять же у нас тут неверно
- 9:53:01это если какой-то рандомный метод у нас,
- 9:53:02как у нас раньше было, или как иногда
- 9:53:04делают везде метод пост или вообще везде
- 9:53:06метод get. Это неверно с точки зрения
- 9:53:08AP. А верно - это смотреть на то, какие
- 9:53:11манипуляции над ресурсами производятся
- 9:53:13твоими хендлерами, и выбор
- 9:53:15соответствующих HTTP методов. не
- 9:53:17рандомный выбор, а именно конкретно под
- 9:53:19манипуляцию над ресурсами. Значит,
- 9:53:21третье правило - это строго определённые
- 9:53:24места для передачи дополнительной
- 9:53:25информации в запросе. Это ответ на тот
- 9:53:28самый вопрос, когда использовать quy
- 9:53:29параметры, когда использовать тело
- 9:53:31запроса, когда использовать ещё
- 9:53:33что-нибудь. Вот давайте смотреть
- 9:53:35создание нового ресурса. То есть у нас
- 9:53:36есть какой-то хендлер, который принимает
- 9:53:38HTTP запросы и потом идёт в наш дулист и
- 9:53:42создаёт новую задачу какую-нибудь. Так
- 9:53:44вот, эту дополнительную информацию, то
- 9:53:45есть в нашем случае, что будет
- 9:53:47дополнительной информацией, это описание
- 9:53:49и заголовок задачи. То есть я, когда
- 9:53:50создаю задачу, я хочу знать, какой у неё
- 9:53:52заголовок и какое у неё описание. Вот
- 9:53:54заголовок я хочу знать и хочу знать
- 9:53:55описание. Выполнена ли дата создания,
- 9:53:57дата выполнения, я уже сам смогу
- 9:53:59проставить. То есть очевидно изначально
- 9:54:00выполнена ли фолс, потому что задача она
- 9:54:02только создалась, она не может быть
- 9:54:03выполнена сразу. Дата создания - это
- 9:54:05именно время, вот когда прямо была
- 9:54:07создана задача, тоже со стороны бэкэнды
- 9:54:08это всё смогу узнать. Ну и дата
- 9:54:10выполнения я тоже сам смогу проставить,
- 9:54:12когда мне придут с запросом выполнить
- 9:54:13задачу. Единственное, что я сам не смогу
- 9:54:15со стороны своего эээнд приложения
- 9:54:17додумать - это какой у задачи заголовок
- 9:54:18и какое у неё описание. Именно поэтому
- 9:54:20заголовок и описание я буду требовать в
- 9:54:22качестве дополнительной информации во
- 9:54:24входящем HTTP запросе. И так как у нас
- 9:54:26создание новой задачи - это создание
- 9:54:28нового ресурса, это значит, что эту
- 9:54:30самую дополнительную информацию я буду
- 9:54:32принимать в Gсоне в телезапросы. Опять
- 9:54:35же, тут не обязательно Gon, но
- 9:54:36практически всегда, если у нас Rest AP,
- 9:54:38то предусматривается взаимодействие
- 9:54:40именно с джисоном. И этот GSON я буду
- 9:54:41ждать в теле запроса. То есть я буду
- 9:54:43ждать заголовок задачи, её описания в
- 9:54:45query параметрах, ни ещё где бы то ни
- 9:54:47было ещё. Я буду ждать её именно в
- 9:54:49Gсоне, в телезапросы, потому что это
- 9:54:50создание нового ресурса. О'кей, ну
- 9:54:52давайте посмотрим на эту нашу табличку.
- 9:54:54То есть вот у нас создание новой задачи
- 9:54:55- это значит создание нового ресурса. И
- 9:54:58давайте посмотрим, как бы мы могли с
- 9:54:59вами передавать эту самую дополнительную
- 9:55:01информацию. А именно то, что мы
- 9:55:03обсудили, что дополнительная информация,
- 9:55:04которая мне нужна для создания задачи -
- 9:55:06это её заголовок и описание. Я бы мог
- 9:55:08передавать это всё в теле запроса. Ну,
- 9:55:10по сути, да, то есть неважно в каком
- 9:55:11формате: в дсоне, там, вмле, в
- 9:55:13каком-нибудь в HTМле или вообще в
- 9:55:15каком-нибудь моём кастомном формате,
- 9:55:16неважно. То есть я бы мог это передавать
- 9:55:18в телезапросы. О'кей. Далее, я бы мог
- 9:55:21передавать заголовок и описание задачи в
- 9:55:23query параметрах. То есть мне ничего не
- 9:55:25мешает просто зашить эти самые query
- 9:55:26параметры в мой HTTP запрос, который
- 9:55:28призван создавать новую задачу. И потом
- 9:55:30изнутри хендлера я просто прочитаю,
- 9:55:32какой у меня tйтle и какой у меня
- 9:55:34дерипtion. То есть какой у меня
- 9:55:35заголовок, какое описание. Вот он наш
- 9:55:36вопросик, вот он наш амперсандик. Ну в
- 9:55:38принципе, да, это наш РY параметры. То
- 9:55:40есть я бы мог передавать и так. И
- 9:55:41третье, как бы я мог передавать - это
- 9:55:43зашить заголовок и описание задачи прямо
- 9:55:46в паттерн. Как бы я это мог сделать? Я
- 9:55:48бы мог взять зарегистрировать хендлер,
- 9:55:50который бы обрабатывал у меня/tasks, но
- 9:55:53при этом отправить запрос/task/title
- 9:55:561/description. И всё равно весь этот
- 9:55:58запрос, он попал бы в handler. сшas и
- 9:56:01уже изнутри этого хендлера я бы взял вот
- 9:56:02этот вот весь полный путь, распарсил бы
- 9:56:04его по слэшам и достал оттуда tilтle и
- 9:56:07достал оттуда декрипtionн так тоже могу
- 9:56:09сделать. Я вам даже больше скажу, мы так
- 9:56:10делать будем, но не в этом случае. Ну
- 9:56:13давайте посмотрим на то, о чём я говорю.
- 9:56:14То есть я описываю самый обычный хендр
- 9:56:16на http и внутри этого хендлера я буду
- 9:56:18вводить на экран r.l.pasps.
- 9:56:21Что это такое? Это будет полный паттерн,
- 9:56:24который был прислан мне в запросе. То
- 9:56:26есть тут у меня не будет local host,
- 9:56:27двоеточия, что-то там, какие-то порты.
- 9:56:29Нет, тут будет именно паттерн, то есть
- 9:56:31всё, что у меня идёт после порта в моём
- 9:56:33запросе. И это будет выводиться на
- 9:56:34экран. Давайте я зарегистрирую этот
- 9:56:36самый хенр
- 9:56:38паттерн у меня/ tasks. И вот он мой
- 9:56:41handler. После этого я запускаю htp
- 9:56:43сервер http len and serve 91 port не
- 9:56:47равно new. Ну и просто есть какая-то
- 9:56:50ошибка. Я вот так вот её на экран
- 9:56:51выведу, да, и всё. Запускаю программу.
- 9:56:55Ну и давайте попробую какой-нибуд запрос
- 9:56:56отправить. local host 901 порт/tasks.
- 9:56:59Отправляю и вижу, что да, действительно,
- 9:57:01pattern/ tasks. Теперь я хочу добавить
- 9:57:03какую-то дополнительную тут информацию.
- 9:57:05СASKS, например, title 1. И мы увидим,
- 9:57:08что у нас page not found, то есть у нас
- 9:57:10не нашлось такого паттерна, не нашлось
- 9:57:12хендлера, который бы обработал этот
- 9:57:13паттерн. Почему? Потому что для того,
- 9:57:15чтобы обрабатывать вот такие вот сложные
- 9:57:17паттерны, многосоставные, нам нужно вот
- 9:57:19здесь вот в конце паттерна в нашей
- 9:57:21функции handle funkть ещё один слэш. Вот
- 9:57:24таким вот образом этот слэш в конце
- 9:57:25будет означать, что я хочу обрабатывать
- 9:57:27не только запросы на сшas, а также
- 9:57:30запросы на сшas и если после них ещё
- 9:57:32что-то есть. То есть я вот все эти
- 9:57:33запросы, неважно, что там будет, если у
- 9:57:35них в начале сAS, я хочу их принимать. И
- 9:57:37вот этот вот слэш в конце как раз-таки
- 9:57:39позволит нам это сделать. Давайте
- 9:57:40попробуем ещё раз. Я перезапускаю
- 9:57:42программу. Перезапустил. Вот. Отправляю
- 9:57:44на slash tasks. Да, был принят. Теперь
- 9:57:47сks/ title 1. Оп. И я тоже это принимаю
- 9:57:51в этом же самом хендлере, в одном и том
- 9:57:52же. Теперь сtask/title 1/description
- 9:57:561. И всё, я это всё принимаю. Потом я
- 9:57:59это всё спокойно могу по слышам
- 9:58:00распарсить и потом уже из этого самого
- 9:58:02входящего паттерна получать, что
- 9:58:04заголовок задачи, что её описание. Это
- 9:58:06как раз-таки вот тот случай, когда у нас
- 9:58:07дополнительная информация зашита в
- 9:58:09паттерне. И то есть мы бы могли по сути
- 9:58:10что-то рандомное из этого выбрать, но
- 9:58:12это уже было бы не REST AP. Rest нам
- 9:58:15говорит следующее, что если у нас идёт
- 9:58:17создание нового ресурса, то
- 9:58:19дополнительная информация у нас должна
- 9:58:20быть не где-то, а именно в джисоне, в
- 9:58:22теле запроса. Опять же повторюсь, не
- 9:58:24обязательно Gson, но в теле запроса. И
- 9:58:26чаще всего это именно GSON. Поэтому в
- 9:58:28нашем случае верно с точки зрения стопи
- 9:58:30будет принимать из входящего HTTP
- 9:58:32запроса из его тела вычитывать джин и
- 9:58:34уже из этого джисона получать заголовок.
- 9:58:36Задача её описание. О'кей. Идём дальше.
- 9:58:38Получение одного уже существующего
- 9:58:40ресурса. Идентификатор ресурса зашит в
- 9:58:43паттерне. Что это вообще значит? Мы уже
- 9:58:45поняли, что для нашего приложения ресурс
- 9:58:47- это какая-то задача. И у каждого
- 9:58:48отдельного ресурса, в нашем случае у
- 9:58:50каждой отдельной задачи должен быть
- 9:58:52какой-то свой уникальный идентификатор,
- 9:58:54благодаря которому я смогу понимать, что
- 9:58:56это именно та задача, которую я хотел
- 9:58:59получить, а не какая-то другая. Тут
- 9:59:00есть, по сути, два способа. Первый - это
- 9:59:02выбрать из уже существующих полей
- 9:59:04какое-то одно поле и сказать, что это
- 9:59:06поле будет идентификатором. Либо второй
- 9:59:08путь - это мы можем просто взять и для
- 9:59:10каждой задачи отдельно генерировать
- 9:59:12какое-то случайное число. То есть ID,
- 9:59:14типа integer. И вот это ID и будет тем
- 9:59:16самым идентификатором. То есть делают
- 9:59:17по-разному. Зависит от того, какая перед
- 9:59:19нами стоит задача, зависит от того,
- 9:59:21какие у нас ресурсы, что у нас вообще за
- 9:59:22приложение. Но в данном случае я захочу
- 9:59:25в качестве идентификатора брать не
- 9:59:26какой-то сгенерированный айдишник, хотя
- 9:59:28это и в принципе хорошая идея. Я в
- 9:59:30качестве идентификатора буду брать
- 9:59:32заголовок задачи. То есть у меня тогда
- 9:59:34получается в моём тудулисте каждая
- 9:59:36задача должна иметь уникальный
- 9:59:37заголовок. И не должно быть такого, что
- 9:59:39у каких-то двух задач этот заголовок
- 9:59:41одинаковый. Получается, я ввожу
- 9:59:42требование на уникальность заголовка, и
- 9:59:44вместе с этим я выбираю заголовок в
- 9:59:46качестве идентификатора моего ресурса.
- 9:59:48То есть заголовок "Домашка, например, -
- 9:59:50это будет идентификатором для ресурса
- 9:59:51задача один." Заголовок "Погулять с
- 9:59:53собакой" - это будет у меня
- 9:59:54идентификатором для ресурса какой-нибудь
- 9:59:56уже другой задачи. То есть, повторюсь,
- 9:59:57идентификатор ресурса - это что-то
- 9:59:59такое, что позволяет однозначно
- 10:00:01идентифицировать какой-то отдельный
- 10:00:03ресурс и отделять один ресурс от другого
- 10:00:05ресурса. Если вспомнить наш пример с
- 10:00:07Ютубом, то мы помним, что там в QRY
- 10:00:09параметрах по ключу V, то есть видео,
- 10:00:12было зашито значение какой-то вот
- 10:00:14непонятный ключ. Так вот, этот самый
- 10:00:16ключ - это и есть идентификатор видео.
- 10:00:18То есть благодаря вот этому набору
- 10:00:19символов YouTube понимает, какое именно
- 10:00:21видео пользователь хочет открыть. То
- 10:00:23есть получается, YouTube на своём
- 10:00:24бэкэнде для каждого видео генерирует вот
- 10:00:26этот вот случайный набор букв. И этот
- 10:00:28случайный набор букв сохраняется за
- 10:00:29каждым видосом и служит идентификатором
- 10:00:32этого самого видоса, то есть
- 10:00:33идентификатором этого ресурса. У нас всё
- 10:00:34будет попроще. мы будем брать уже
- 10:00:36готовый заголовок нашей задачи, и этот
- 10:00:38самый заголовок и будет идентификатором
- 10:00:40нашего ресурса, идентификатором какой-то
- 10:00:42конкретной задачи. Хорошо, мы поняли,
- 10:00:44идентификатор ресурса - это заголовок
- 10:00:46задачи. И когда мы хотим получить
- 10:00:47какой-то один уже созданный ресурс, то
- 10:00:49есть какую-то одну уже созданную задачу,
- 10:00:51мы должны в паттерне зашить
- 10:00:54идентификатор ресурса. Давайте посмотрим
- 10:00:56на нашу таблицу. То есть вот мы получаем
- 10:00:57информацию по уже созданной задаче, и мы
- 10:00:59должны дополнительную информацию в нашем
- 10:01:02запросе, а именно заголовок нашей
- 10:01:04задачи, заголовок задачи, информацию, по
- 10:01:06которой мы хотим получить, передавать ни
- 10:01:07в телезапросы, ни в queryпараметрах. Мы
- 10:01:10должны зашить это в наш паттерн
- 10:01:11следующим образом. У нас есть
- 10:01:13endpoint/tasks. После этого мы добавляем
- 10:01:15ещё один слш и после него мы пишем
- 10:01:17заголовок задачи, информацию, по которой
- 10:01:19мы хотим получить. Потом со стороны
- 10:01:20нашего хендлера мы будем это всё
- 10:01:22парсить, будем вычленять заголовок
- 10:01:23задачи и тем самым понимать информацию,
- 10:01:25по какой именно задаче наш клиент хочет
- 10:01:28получить. Ну и мы видим, что у нас
- 10:01:29неверно будет это что-то случайно,
- 10:01:31непонятное выбирать. Тогда это у нас уже
- 10:01:32не Rest AP, а какой-то наш кастомный
- 10:01:34формат. Если мы придерживаемся правил
- 10:01:36именно REST P, тогда при получении уже
- 10:01:38существующего ресурса мы должны
- 10:01:41идентификатор этого ресурса зашить в
- 10:01:43паттерне. Потом со стороны хендлера мы
- 10:01:44будем получать весь паттерн, парсить его
- 10:01:47и узнавать конкретно заголовок задачи,
- 10:01:49информацию, которой хочет получить
- 10:01:50клиент. Ну и, кстати, мы уже можем
- 10:01:52посмотреть, что, исходя из этого правила
- 10:01:53YouTube, он не полностью следует
- 10:01:56правилам RP. Почему? Потому что по
- 10:01:57правилам REST P, если этот endpint
- 10:01:59предназначен для того, чтобы мне
- 10:02:01получать информацию о каком-то видео,
- 10:02:02ну, соответственно, видео это самое с
- 10:02:04сервера получать, то есть я не создаю
- 10:02:05никаких новых видосов, я не удаляю уже
- 10:02:07существующий, я не меняю уже
- 10:02:09существующий, я просто хочу получить
- 10:02:10существующий видос и посмотреть его, то
- 10:02:13получается вот этот вот самый
- 10:02:14идентификатор видоса, согласно Rp, я
- 10:02:16должен был бы передавать не в query
- 10:02:17параметрах, а зашить в паттерн, как бы
- 10:02:19это выглядело. То есть youtube.com/watch
- 10:02:23и уже не query параметры, а сш и вот
- 10:02:25этот вот самый идентификатор видоса. Вот
- 10:02:27таким вот образом, то есть вот таким вот
- 10:02:28образом. То есть это с точки зрения RP
- 10:02:30было бы правильно, что я идентификатор
- 10:02:32ресурса, который я хочу получить
- 10:02:33сервера, зашиваю в паттерне, а не
- 10:02:35передаю это каким-то quy параметрам. Это
- 10:02:37во-первых. Во-вторых, мы видим, что тут
- 10:02:39есть у них patтерn/watch. А watch - это
- 10:02:41что такое? Это смотреть. Это глагол. Аp
- 10:02:43подразумевает, что у нас должны быть не
- 10:02:45глаголы, а существительные. То есть с
- 10:02:46точки зрения стопи даже тут у Ютуба
- 10:02:48неправильно. Тут должно быть неч, тут
- 10:02:50должно быть видео. И тогда уже было бы с
- 10:02:52точки зрения растопи полностью правильно
- 10:02:54ресурс видео. И потом через слэш я в
- 10:02:57паттерне зашиваю идентификатор этого
- 10:02:59ресурса. И тем самым я говорю, что я
- 10:03:01хочу получить этот самый ресурс.
- 10:03:02Логично. Логично. Но, как я и говорил,
- 10:03:04очень многие компании далеко не всегда
- 10:03:06придерживаются принципов, поэтому мы тут
- 10:03:08видим с вами у Ютуба и глагол в
- 10:03:11паттерне, и вообще идентификатор ресурса
- 10:03:13при получении этого самого ресурса в
- 10:03:15query параметрах. Ну, что ещё раз нам
- 10:03:17говорит о том, что рестопи - это не
- 10:03:18какая-то панацея. Это не абсолютно везде
- 10:03:19должно использоваться и строго
- 10:03:21следоваться, но принципы РСТП знать
- 10:03:23полезно, потому что так или иначе
- 10:03:25какие-то из них используются практически
- 10:03:27везде. Не все сразу, но частично что-то
- 10:03:29где-то постоянно всплывает. О'кей.
- 10:03:31Смотрим дальше. Получение всех
- 10:03:33существующих ресурсов. То есть, если в
- 10:03:35нашем случае у нас ресурс - это задача,
- 10:03:37то получается получение всех
- 10:03:39существующих задач. И что нам тут
- 10:03:40сказано? Что дополнительная информация
- 10:03:42не нужна. Как это так? Ну давайте
- 10:03:44посмотрим на нашу таблицу. То есть
- 10:03:45неверно. Вот неверно. Это если бы мы там
- 10:03:48что-то пытались в телезапроса передать
- 10:03:50что-то типа title all и уже внутри
- 10:03:52хендлера смотрели, что если у нас tйтle
- 10:03:54all, это значит, что нужно типа все
- 10:03:56задачи вернуть. Также там что-то в query
- 10:03:57параметрах пытаться передать там title
- 10:03:59all или даже зашивать в pattern title
- 10:04:01all. Это всё неверно. То есть если мы
- 10:04:03хотим получить абсолютно все ресурсы,
- 10:04:04нам достаточно сделать get запрос на
- 10:04:08patтерn/tasks и всё. И если тут не будет
- 10:04:10через слш ещё каких-то дополнительных
- 10:04:12идентификаторов конкретного ресурса, это
- 10:04:14значит лишь одно, что я хочу получить
- 10:04:16абсолютно все ресурсы. Всё. Поэтому тут
- 10:04:17никая дополнительная информация ни в
- 10:04:19теле запроса, ни в кри параметрах, ни в
- 10:04:22паттерне требуется. Далее, получение
- 10:04:25всех существующих ресурсов, учитывая
- 10:04:27фильтры, это quy параметры. То есть с
- 10:04:30точки зрения REST query параметры как
- 10:04:32раз-таки предназначены для того, чтобы
- 10:04:34передавать в них какие-то фильтры
- 10:04:35получаемых ресурсов. В нашем случае
- 10:04:38получение всех невыполненных задач. То
- 10:04:40есть я хочу получить все задачи, но
- 10:04:42отфильтровать их по критерию
- 10:04:43выполненности, то есть выполнено или не
- 10:04:45выполнено. Получается, это фильтр. И
- 10:04:47значит, что с точки зрения R апи было бы
- 10:04:49неверно? Неверно - это опять же
- 10:04:50выбирать, вообще абсолютно не думая,
- 10:04:52способ передачи этой самой
- 10:04:53дополнительной информации. Там что-то
- 10:04:55пытаться в телезапросы передать или же
- 10:04:57как-то в паттерн это всё зашивать или
- 10:04:58там выбирать квыри параметры, но при
- 10:05:00этом вообще бездумно и как-то случайно.
- 10:05:02Это всё было бы неправильно. А с точки
- 10:05:03зрения P верным шагом было бы
- 10:05:06использовать фильтрацию именно в quy
- 10:05:08параметрах. То есть почему это тут
- 10:05:09фильтрация? Потому что я получаю вообще
- 10:05:11все задачи. То есть я получаю все
- 10:05:13ресурсы, но потом после знака вопроса у
- 10:05:15меня идут параметры фильтрации. То есть
- 10:05:17меня интересуют все задачи, но при этом
- 10:05:19из них нужно выбрать только те, у
- 10:05:21которых поле complet, то есть выполнено
- 10:05:23ли false, то есть не выполнено.
- 10:05:25Получается, следующим запросом я говорю,
- 10:05:27что меня интересуют все задачи, которые
- 10:05:28не выполнены. Всё, я благодаря query
- 10:05:30параметрам выполнил фильтрацию. Ну и тем
- 10:05:32самым я соблюдаю вот этот вот самый
- 10:05:33пункт, что когда я получаю все
- 10:05:35существующие ресурсы, но при этом я их
- 10:05:38каким-то образом фильтрую. Для этого мне
- 10:05:40нужно использовать quy параметры. Ну,
- 10:05:42это то самое, что мы с вами смотрели на
- 10:05:43уроке QY параметры в ДНСе. Ну, то есть
- 10:05:45магазин электроники. Там можно было
- 10:05:47выбрать ценовой сегмент, выбрать бренд и
- 10:05:49так далее, и так далее. И всё это шло у
- 10:05:51нас в query параметры. Это даже можно
- 10:05:53было наглядно видеть. Почему? Потому что
- 10:05:55все эти пункты, цена, бренд и так далее
- 10:05:58- это всего лишь фильтры всех ресурсов.
- 10:06:00А все ресурсы в данном случае, в случае
- 10:06:02с ДНСом, это там техника, либо ноутбуки,
- 10:06:04либо ещё что-то. И мы при помощи рей
- 10:06:06параметров эти самые ресурсы
- 10:06:07фильтровали. О'кей, идём дальше.
- 10:06:09частичное изменение существующего
- 10:06:11ресурса. То есть я какой-то конкретный
- 10:06:13ресурс выбираю и его изменяю. В нашем
- 10:06:16случае с задачами я выбираю какую-то
- 10:06:17конкретную задачу и хочу её изменить
- 10:06:19частично. В таком случае я должен
- 10:06:21идентификатор ресурса зашить в паттерне.
- 10:06:23То есть идентификатор конкретной задачи,
- 10:06:25которую я меняю, я должен зашить в
- 10:06:26паттерне. А именно информацию о том, что
- 10:06:29я меняю, я уже передаю в джисоне в
- 10:06:31телезапроса. В нашем случае частичное
- 10:06:33изменение задачи - это отметить задачу
- 10:06:35как выполненную. То есть у меня куча
- 10:06:37полей в задаче, но я меняю только лишь
- 10:06:38одно поле completed. И вот неверным с
- 10:06:40точки зрения RESTP было бы опять
- 10:06:42выбирать что-то случайное, непонятное,
- 10:06:44пытаться как-то это там в quy параметрах
- 10:06:45передать или что-то там пытаться в
- 10:06:47телезапросы передать. Нет, верным было
- 10:06:49бы зашить идентификатор ресурса в
- 10:06:51паттерне. В нашем случае это заголовок
- 10:06:53задачи, мы его зашиваем в паттерне, а в
- 10:06:55Gсо мы передаём то поле, которое хотим
- 10:06:58поменять и его новое значение. То есть
- 10:06:59мы хотим, когда мы задачу отмечаем как
- 10:07:01выполненную, поменять в этой задаче поле
- 10:07:03complet и ему поставить значение true.
- 10:07:04Именно это мы и передаём в дсоне, а
- 10:07:06идентификатор задачи мы передаём в
- 10:07:08паттерне. Абсолютно то же самое у нас
- 10:07:10для полного изменения существующего
- 10:07:11ресурса. То есть мы идентификатор
- 10:07:13ресурса зашиваем в паттерне, а полностью
- 10:07:16обновлённый ресурс передаём в дсоне. Но
- 10:07:18у нас такого кейса нету, поэтому просто
- 10:07:19на словах мы это обсудили. Далее - это
- 10:07:21удаление уже существующего ресурса. В
- 10:07:24принципе, всё то же самое, что и с
- 10:07:25получением уже существующего ресурса. Мы
- 10:07:27идентификатор ресурса, который мы
- 10:07:29удаляем, передаём в паттерне. Но
- 10:07:31благодаря тому, что у нас HTTP метод
- 10:07:33будет не get, вот у нас не GET, а
- 10:07:36Delete,
- 10:07:41благодаря этому хендлер поймёт, что если
- 10:07:43я обращаюсь к какому-то ресурсу, к его
- 10:07:46идентификатору и передаю метод HTTP
- 10:07:48delete, это значит я этот ресурс хочу
- 10:07:49удалить. Ну и смотрим в табличке, у нас
- 10:07:51удаление уже существующей задачи. То
- 10:07:53есть опять же неправильным было бы
- 10:07:55выбирать что-то рандомное, что-то
- 10:07:56случайное, то, что нам кажется наиболее
- 10:07:58понятным. Правильным будет зашить
- 10:08:01идентификатор удаляемого ресурса в
- 10:08:02паттерн. И благодаря тому, что у нас
- 10:08:04HTTP метод будет delete, то наш хендлер
- 10:08:06поймёт, что этот самый ресурс нужно
- 10:08:08взять и удалить. Теперь переходим к
- 10:08:10четвёртому правилу. Четвёртое правило у
- 10:08:11нас регулирует информацию в HTTP
- 10:08:14телеответа. Давайте посмотрим, что мы
- 10:08:16должны отдавать в HTTP телетвета при тех
- 10:08:18или иных ситуациях. У нас все ситуации
- 10:08:20глобально делятся на успешные и
- 10:08:22неуспешные. То есть успешные - это когда
- 10:08:24мы успешно создали новый ресурс, успешно
- 10:08:26удалили существующий ресурс, успешно
- 10:08:28поменяли существующий ресурс и так
- 10:08:30далее, и так далее, и так далее. А
- 10:08:31ошибка - это когда что-то не получилось
- 10:08:33делать по каким-то причинам. Ошибка там
- 10:08:35подключения к базе данных. Например, у
- 10:08:37нас там все ресурсы, да, там все
- 10:08:38пользователи или все задачи наши в туду
- 10:08:40листе хранились бы в какой-то сторонней
- 10:08:41базе данных, а эта стороняя база данных
- 10:08:43взяла, да, и сломалась, и у нас не
- 10:08:45получилось из базы данных достать
- 10:08:46какую-то информацию. Это на самом деле
- 10:08:48не самое редкое явление. Это ошибка, да?
- 10:08:50Ошибка. Ну и давайте посмотрим, как мы с
- 10:08:51вами должны реагировать в случае успеха
- 10:08:53и в случае ошибки. Успех, когда у нас
- 10:08:55идёт создание нового ресурса, мы создаём
- 10:08:56новую задачу, и у нас получилось успешно
- 10:08:59создать новую задачу. В HTTP теле ответа
- 10:09:01мы должны вернуть Gсон, который бы
- 10:09:04представлял новый созданный ресурс. В
- 10:09:06нашем случае мы должны после создания
- 10:09:07задачи вернуть GON, который бы
- 10:09:09представлял эту самую новую задачу.
- 10:09:11Зачем? Да, просто потому, что это
- 10:09:13дополнительное подтверждение для HTTP
- 10:09:15клиента, что сервер создал тот ресурс,
- 10:09:18который ты от него и просил. О'кей.
- 10:09:20Далее. Получение одного существующего
- 10:09:22ресурса. Ну, понятно, если я хочу по
- 10:09:23сети получить какую-то задачу, я точно
- 10:09:25так же в Gсорнуть найденный ресурс. Ну и
- 10:09:28всё, я это всё пихаю в тело ответа и
- 10:09:29возвращаю. Далее, получение всех
- 10:09:31существующих ресурсов. То же самое. Я
- 10:09:33нахожу все существующие ресурсы, кладу
- 10:09:35их в какой-нибудь массив, потом этот
- 10:09:37массив я перевожу в jon всё это в теле
- 10:09:39ответа я возвращаю. Далее у нас
- 10:09:40получение ресурсов с фильтрами.
- 10:09:42Абсолютно то же самое. Я нахожу все
- 10:09:44нужные мне ресурсы, отфильтровываю их,
- 10:09:46кладу в какой-нибудь слайс и потом этот
- 10:09:47слайс перевожу в jon телеответ
- 10:09:49возвращаю. Далее у нас частичное либо
- 10:09:51полное изменение уже существующего
- 10:09:53ресурса. Ну тут, в принципе, всё
- 10:09:55примерно то же самое. Я беру какой-то
- 10:09:56ресурс, я его меняю, потом этот новый
- 10:09:59изменённый ресурс я перевожу в GSON и
- 10:10:01отправляю в телеответа. Опять же, зачем?
- 10:10:03для того, чтобы HTTP клиент, когда
- 10:10:05поменял какой-то ресурс, он получил себе
- 10:10:07полное видение нового изменённого
- 10:10:09ресурса, чтобы можно было понять, всё ли
- 10:10:11произошло корректно, либо почему-то
- 10:10:12какая-то ошибка случилась. Далее у нас
- 10:10:14удаление существующего ресурса, а тут
- 10:10:16уже пустое тело ответа. То есть, если я
- 10:10:18удалил какой-то ресурс, то этого ресурса
- 10:10:21больше нету. Поэтому нет никакой нужды
- 10:10:23мне в теле ответа как-то жесон этого
- 10:10:25ресурса отдавать. Нет, всё, ресурс
- 10:10:26удалён, поэтому в теле ответа ничего не
- 10:10:28нужно класть. Хорошо. А что будет в
- 10:10:30случае ошибки? В случае ошибки я должен
- 10:10:31возвращать всегда JON, в котором есть
- 10:10:34поле message. Ну опять же, где-то может
- 10:10:36поле месдж, где-то может поле error,
- 10:10:38зависит от той команды, от той компании,
- 10:10:39где вы работаете. Но обычно, если
- 10:10:41ошибка, мы должны вернуть jon, в котором
- 10:10:43есть обязательно поле Message. И в этом
- 10:10:45поле Message у меня конкретно описано,
- 10:10:47что пошло не так. То есть почему я вдруг
- 10:10:49вернул ошибочный статус-код, да? То есть
- 10:10:51у нас какой-то будет запрос, в ответе
- 10:10:53клиент увидит статус код не 200, а
- 10:10:56какой-нибудь другой, там, например, 500
- 10:10:57внутренняя ошибка сервера. И уже в Gсо,
- 10:11:00который будет в теле ответа, клиент
- 10:11:02сможет достать поле месседж и из него
- 10:11:04вычитать конкретный сбой. Что же пошло
- 10:11:06не так? Ну и плюс благодаря тому, что у
- 10:11:07нас ошибки возвращаются в джисоне, в
- 10:11:09этот же самый GSON можно дополнительные
- 10:11:10опциональные поля добавить, если там
- 10:11:12есть желания или какие-то
- 10:11:13договорённости. Но мы с вами можем
- 10:11:14добавить время совершения ошибки. То
- 10:11:16есть я в случае ошибки буду возвращать
- 10:11:18JON и у меня там будет поле месдж. В
- 10:11:20этом поле месessдж у меня будет описание
- 10:11:22конкретного сбоя, что пошло не так.
- 10:11:24Например, не удалось подключиться к базе
- 10:11:25данных и так далее, и так далее, и так
- 10:11:26далее. И дополнительно полей, то есть
- 10:11:28время, когда у нас эта самая ошибка
- 10:11:30свершилась. Дальше у нас пятое правило -
- 10:11:32это строго определённая информация в
- 10:11:35HTTP статус-коде ответа. Давайте
- 10:11:37посмотрим. Когда мы с вами создаём новый
- 10:11:38ресурс, в HTTP статус-коде мы должны
- 10:11:41вернуть не 200 ок, а 2011 статус-код.
- 10:11:44Потому что именно статус-код 2011 он
- 10:11:46служит для того, чтобы дать клиенту
- 10:11:47понять, что на сервере новый ресурс
- 10:11:49создан. Поэтому мы возвращаем статус код
- 10:11:512011 согласно Restaпи. Хотя я в сотый
- 10:11:53раз повторюсь, что обычно на это
- 10:11:55кладётся большой болт, особенно внутри
- 10:11:58крупных компаний. И там вообще, если 200
- 10:12:01- это ОК. Если 500 - это не ок. Всё. И
- 10:12:03там же потом какие-то свои надстройки
- 10:12:05идут над всем этим, чтобы понять, что же
- 10:12:07пошло не так. Это на самом деле вообще
- 10:12:08такой большой зоопарк разных решений,
- 10:12:10что можно просто с ума сойти. Но мы
- 10:12:11сейчас рассматриваем именно RP, именно
- 10:12:14то, как оно должно быть, как оно
- 10:12:15задумывалось создателями этой самой
- 10:12:17парадигмы RP. А исходя из этой
- 10:12:19парадигмы, если у нас на сервере новый
- 10:12:20ресурс создан, мы должны вернуть статус
- 10:12:22код 2011. Далее, получение уже
- 10:12:25существующего ресурса. Если получилось
- 10:12:27его успешно достать и вернуть клиенту,
- 10:12:29статус код 200. То же самое для всех
- 10:12:31ресурсов, то же самое для фильтрации
- 10:12:33ресурсов. Далее у нас частичное и полное
- 10:12:35изменение ресурса. Это у нас тоже 200
- 10:12:38статус код. Почему? Да потому что RP так
- 10:12:40регулирует. Почему-то вот при создании
- 10:12:42ресурса у нас есть какой-то специальный
- 10:12:44отдельный статус-код, а при изменении
- 10:12:46нету. То есть при изменении мы
- 10:12:47возвращаем тот же самый статус-код, что
- 10:12:49и при получении ресурса. Почему? Да,
- 10:12:51честно говоря, я не знаю. Просто так
- 10:12:53говорит парадигма RP. Скорее всего, у
- 10:12:55этого есть какое-то логичное объяснение.
- 10:12:57Просто я в силу своей какой-то
- 10:12:58неопытности ещё не смог понять, почему у
- 10:12:59нас именно для создания нового ресурса
- 10:13:01есть отдельный статус-код, а для
- 10:13:03изменения нету. Ну, это довольно
- 10:13:04странно. Ну и последнее - это у нас
- 10:13:06удаление существующего ресурса. Мы
- 10:13:08должны вернуть 204 нонтент. Ну, то есть
- 10:13:11буквально мы говорим о том, что мы
- 10:13:12действительно успешно удалили ресурс, но
- 10:13:14никакого контента, возвращаемому
- 10:13:15значения мы не передаём, поэтому 204. Но
- 10:13:17контент. О'кей. Далее, в случае ошибок.
- 10:13:20На самом деле тут перечислены далеко не
- 10:13:22все вообще статус-коды, которые могут
- 10:13:23возникнуть в каких-то критичных
- 10:13:25ошибочных ситуациях. просто какие-то
- 10:13:26самые основные. То есть вот давайте
- 10:13:28обсудим, когда мы с вами создаём новый
- 10:13:29ресурс, что может пойти не так? Ну,
- 10:13:31во-первых, пользователь может прислать
- 10:13:32невалидный какой-нибудь запрос, то есть
- 10:13:34что-то непонятное в теле запроса,
- 10:13:36какие-то не те поля, которые мы ждали, и
- 10:13:38поэтому мы просто не можем корректно
- 10:13:40создать новую задачу. Это 400 B request,
- 10:13:42то есть плохой запрос от пользователя. И
- 10:13:44тут мы видим, что, в принципе, везде у
- 10:13:46нас в теории возможен этот BТ-реквест.
- 10:13:48Почему? Да потому что всегда можно
- 10:13:49придумать, как напортачить запросом.
- 10:13:51Поэтому всегда есть вероятность, что
- 10:13:53клиент отправит что-то не то, и мы со
- 10:13:55стороны наших хендлеров не сможем это
- 10:13:56нормально, корректно вычитать. Поэтому
- 10:13:58BДquest у нас всегда может быть. И если
- 10:14:00у нас этот бедреквест случился, мы
- 10:14:02должны именно эту ошибку и вернуть. Не
- 10:14:04какую-нибудь другую, то есть не
- 10:14:05какую-нибудь 500 internal server Eror,
- 10:14:07что означает, что у нас какая-то
- 10:14:08внутренняя ошибка сервера, не получилось
- 10:14:10там подключиться к базе данных или ещё
- 10:14:11что-то. Нет, если нам клиент прислал
- 10:14:13какую-то фигню в запросе, мы должны
- 10:14:15вернуть 400 Bреквест, мол, нам вообще не
- 10:14:17то прислали. Хорошо. Теперь представим,
- 10:14:19что у нас уже есть какая-то задача с
- 10:14:21заголовком домажка. И тут клиент захотел
- 10:14:23создать ещё одну задачу, тоже с
- 10:14:25заголовком домашка, хотя у нас такая
- 10:14:26задача уже есть. Что это значит? Это
- 10:14:28значит, идёт попытка создать ресурс на
- 10:14:31сервере с уже существующим
- 10:14:33идентификатором. А идентификатор, он как
- 10:14:36раз-таки должен быть уникальный для всех
- 10:14:38ресурсов. Так как у нас идентификатор -
- 10:14:39это заголовокзадачи, то получается, мы
- 10:14:41просто не можем позволить создать две
- 10:14:43задачи с одинаковым заголовком. Поэтому,
- 10:14:45если идёт попытка создать вторую задачу
- 10:14:48с таким же заголовком, с которым уже
- 10:14:49какая-то задача существует, мы должны
- 10:14:51вернуть статус код 409 конфликт. То есть
- 10:14:53у нас идёт конфликт при попытке создать
- 10:14:55новый какой-то ресурс. Почему конфликт?
- 10:14:57Да потому что у нас не может быть два
- 10:14:58идентификатора одинаковых у двух разных
- 10:15:01ресурсов. Ну и 500 internal server Eror.
- 10:15:03Почему тут он есть везде? Да потому что
- 10:15:05абсолютно всегда. Что в случае с
- 10:15:07пользователем, который может что-то не
- 10:15:08то написать в запросе, также и всегда
- 10:15:10есть какая-то вероятность, что у нас
- 10:15:11что-то не так пойдёт на сервере. Поэтому
- 10:15:13вообще всегда допускаются статус коды
- 10:15:15400 B request, если usеer отправил
- 10:15:17что-то не то в запросе, и 500 internal
- 10:15:19server Eror, если у нас что-то на
- 10:15:20стороне сервера там случилось или
- 10:15:21сломалось. О'кей. Далее получение уже
- 10:15:23существующего ресурса. Ну и тут опять же
- 10:15:25юзер что-то может запроси напортачить.
- 10:15:27Либо мы можем не найти запрашиваемый
- 10:15:29ресурс. Например, клиент хочет получить
- 10:15:31задачу с заголовком домашка, но такой
- 10:15:34задачи не было создано. То есть нам
- 10:15:35нечего возвращать. Если нам нечего
- 10:15:37возвращать, то мы статус код должны
- 10:15:39вернуть 404 Not found. То есть это
- 10:15:40стандартный такой статус-код, который
- 10:15:42служит для обозначения того, что не
- 10:15:43найден какой-то ресурс, либо что-либо
- 10:15:45другое не найдено, но в нашем случае
- 10:15:47именно ресурс. Получение всех
- 10:15:48существующих ресурсов тут уже немножко
- 10:15:50другое. То есть если даже у нас вообще
- 10:15:51нет ни одной задачи, то есть ни одного
- 10:15:53ресурса и идёт попытка получить все
- 10:15:55существующие ресурсы, то мы просто
- 10:15:57возвращаем пустой жен. То есть как бы
- 10:15:58массив, в котором ноль элементов, но не
- 10:16:01404 not found. Почему? Потому что тут
- 10:16:03идёт попытка получить именно все
- 10:16:05какие-то ресурсы, то есть массив. То же
- 10:16:07самое у нас и для получения всех
- 10:16:08ресурсов, учитывая фильтры. То есть
- 10:16:10просто мы можем наставить такие фильтры,
- 10:16:12под которые не подходит ни один ресурс.
- 10:16:14Тут принято не возвращать 404 not found,
- 10:16:16а возвращать 200 ОК и просто пустой
- 10:16:19массив в джине в теле ответа. Мол, всё
- 10:16:21типа нормально, но просто, братан, нету
- 10:16:23таких ресурсов, которые ты запрашиваешь,
- 10:16:25и всё. А вот если идёт попытка получить
- 10:16:28какой-то конкретный ресурс, тогда мы уже
- 10:16:30должны сказать, что не, братик, вот
- 10:16:32именно этого вот прямо конкретно этого
- 10:16:34нету. Это может показаться нелогично, но
- 10:16:36просто для разработчиков, для
- 10:16:38коммуникации бэкэнда и фронт-тенда
- 10:16:40именно такая парадигма, она наиболее
- 10:16:41удобна, потому что, допустим, мы с вами
- 10:16:43разрабатываем социальную сеть там
- 10:16:44ВКонтакте и хотим открыть страничку
- 10:16:46какого-нибудь конкретного пользователя.
- 10:16:48Вводим там в адресной строке ВКcom/ там
- 10:16:50ник пользователя, а такого пользователя
- 10:16:52нету. Тогда мы хотим увидеть ошибку об
- 10:16:54этом, что типа блин, что-то нету такого
- 10:16:56пользователя, пользователь не найден. То
- 10:16:57есть это прямо ошибка. Мы прямо
- 10:16:58какого-то конкретного пользователя
- 10:17:00хотели страничку открыть, а он не
- 10:17:01существует. Прямо мы вообще не туда
- 10:17:02пришли. Но если мы, например, хотим
- 10:17:04получить список всех пользователей, либо
- 10:17:06хотим получить список всех пользователей
- 10:17:07с какими-то фильтрами там по месту
- 10:17:09проживания, по имени, по университету, в
- 10:17:12котором этот человек учился, по
- 10:17:13возрасту, и просто, ну, не нашлось таких
- 10:17:15пользователей, их ноль. Ну, просто мы
- 10:17:17увидим, что таких пользователей нету, и
- 10:17:18у нас на страничке будет пустой список.
- 10:17:20Мы такие: "А, ну понятно". И это не
- 10:17:21будет каким-то ошибочным сценарием.
- 10:17:23Просто не нашлось таких пользователей.
- 10:17:24Но когда мы какого-то конкретного хотели
- 10:17:26найти, а его нету, это уже ошибочный
- 10:17:28сценарий. мы хотим об этом видеть
- 10:17:29информацию на экране, что нету такого
- 10:17:31пользователя, вы вообще не по той ссылке
- 10:17:33перешли. Вот такой вот довольно
- 10:17:34неочевидный момент, но повторюсь, с
- 10:17:35опытом придёт понимание, почему именно
- 10:17:37так сделано, а не иначе. О'кей. Далее,
- 10:17:39частичное и полное изменение каких-то
- 10:17:41уже существующих ресурсов. Опять же,
- 10:17:43ошибка в пользовательском запросе всегда
- 10:17:45может быть. Всегда мы можем не найти тот
- 10:17:47ресурс, который хотим изменить. То есть
- 10:17:48это опять же ошибочная ситуация, что я
- 10:17:50захотел поменять какую-то конкретную
- 10:17:51задачу, например, отметить её как
- 10:17:53выполненную, а её не существует. То есть
- 10:17:55а что а что ты собирался выполнять?
- 10:17:57Задачи это нету. Это ошибка, и я хочу
- 10:17:59конкретно клиенту сигнализировать о том,
- 10:18:01что это действительно ошибка. У нас
- 10:18:02может быть конфликт. Каким образом? Ну,
- 10:18:04было две задачи: домашка и погулять с
- 10:18:06собакой. Потом задачу с заголовком
- 10:18:08"Погулять с собакой" юзер решил поменять
- 10:18:10и сделать ей заголовок тоже домашка. В
- 10:18:12итоге у нас может получиться две задачи:
- 10:18:13домашка. Такого не может быть. Это
- 10:18:15конфликт, поэтому мы должны это
- 10:18:16предотвратить и вернуть ошибку, мол, у
- 10:18:18нас конфликт случился. Ну и как всегда
- 10:18:20500 статус код у нас вообще всегда может
- 10:18:22быть всегда может произойти. Удаление
- 10:18:23уже существующего ресурса. Опять может
- 10:18:25быть какой-то неверный запрос юзера. И
- 10:18:27мы можем не найти ресурс, который клиент
- 10:18:29хочет удалить. То есть вот он хочет
- 10:18:30удалить задачу, которая не существует.
- 10:18:32Это ошибка. Конфликса тут, в принципе,
- 10:18:33произойти никакого не может, а вот
- 10:18:34пятисотая ошибка всегда может у нас
- 10:18:36произойти. Опять же повторюсь, это
- 10:18:37далеко вообще не весь набор
- 10:18:38статус-кодов, которые мы можем с вами
- 10:18:40возвращать. Это лишь некоторая
- 10:18:41иллюстрация того, что перед тем, как
- 10:18:43вернуть какой-то статус-код в какой-то
- 10:18:45конкретно сложившейся ситуации, мы
- 10:18:46должны с вами 10 раз подумать, какой
- 10:18:48именно статус-код мы должны вернуть.
- 10:18:50Возможно, погуглить, спросить у неронки,
- 10:18:52либо ещё где-то поискать. Потому что,
- 10:18:53если мы будем просто на все успешные
- 10:18:55случаи возвращать 200 Ок, а на все
- 10:18:57ошибки 500 internal server Eror, это уже
- 10:18:59не будет RP. Это будет что-то другое, но
- 10:19:01точно не Rest AP. Ну что, начинаем
- 10:19:04писать код. У меня тут пустой main файл
- 10:19:06и какая-то странная папка to-do. Тут три
- 10:19:08файла: task, list errors. Что это такое?
- 10:19:12А это я просто взял из нашего с вами
- 10:19:14GitHub репозитория из первой части
- 10:19:16учебный проект и список дел. И тут вот
- 10:19:19эта самая папочка to. Тут содержится
- 10:19:21весь код, который нам поможет сохранять,
- 10:19:22добавлять, удалять новые задачи. Вот эти
- 10:19:24три файла я просто взял сюда и
- 10:19:25перекопировал. Объяснение всего этого
- 10:19:27кода есть в приватном сообществе,
- 10:19:29поэтому сейчас я не буду тратить на это
- 10:19:30время. Повторюсь, времени у нас осталось
- 10:19:32крайне мало. Быстронько всего лишь
- 10:19:34пробежимся по тому, что тут написано.
- 10:19:35Тут у нас есть структура Task - это как
- 10:19:37раз-таки та самая наша какая-то
- 10:19:39отдельная задача. У неё вот есть
- 10:19:40заголовок, есть описание, информация о
- 10:19:42том, выполнена она или нет, время
- 10:19:44создания, время выполнения, время
- 10:19:45выполнения по указателю, потому что оно
- 10:19:47у нас может быть, когда только задача
- 10:19:49создалась. То есть у нас ещё нету время
- 10:19:50выполнения задачи, когда задача только
- 10:19:52создалась. Поэтому указатель тут вот у
- 10:19:54нас есть конструктор для этой задачи.
- 10:19:55Видим, что конструктор требует лишь
- 10:19:57заголовок и описание задачи. Далее все
- 10:19:59поля мы с вами можем сами уже
- 10:20:01предугадать. То, что у нас задача не
- 10:20:02выполнена, когда она только создаётся.
- 10:20:04Это логично. Время создания - это как
- 10:20:06раз-таки время, когда задача была
- 10:20:07фактически создана в конструкторе. Вот.
- 10:20:08Time now. И время выполнения изначально.
- 10:20:10Ну, потому что она как бы не выполнена у
- 10:20:12нас ещё. И у этой задачи есть метод DAN.
- 10:20:13То есть это метод, который можно
- 10:20:14вызвать, и задача выполнится. Что значит
- 10:20:17задача выполнится? Это значит, мы
- 10:20:18запомним время её выполнения и переведём
- 10:20:20из дан, то есть переменную, которая
- 10:20:22отвечает за то, выполнена ли задача или
- 10:20:24нет, в true. То есть задача выполнена.
- 10:20:26Ну всё, сохраним время выполнения и
- 10:20:27можно считать, что задача выполнена.
- 10:20:29О'кей. То есть фафайл task - это просто
- 10:20:30у нас описание именно задачи. Далее у
- 10:20:32нас есть файл list, то есть это уже сам
- 10:20:33to-ду list. Что тут есть? Тут есть
- 10:20:34структура листа, и в этой структуре есть
- 10:20:36одно поле tasks, то есть задачи. И это у
- 10:20:38нас мапа, где ключом является строка,
- 10:20:39значением задачи. Ну, соответственно,
- 10:20:40ключ - это у нас будет заголовок задачи
- 10:20:42значением сама задача. Мы, в принципе, с
- 10:20:43вами это уже обсуждали и в приватном
- 10:20:45сообществе, и сейчас тоже, когда мы
- 10:20:47разбирали, мы обсуждали, почему именно
- 10:20:49такое хранение будет у нас задачи,
- 10:20:50никакой другое. Есть у нас конструктор
- 10:20:52для этого листа. Тут мы по большому
- 10:20:53счёту просто инициализируем мапу. О'кей.
- 10:20:55Далее у нас есть возможность добавить
- 10:20:57задачу. То есть мы просто берём и по
- 10:20:58какому-то ключу кладём задачу, но тут
- 10:21:01уже мы встречаем первую проблему, что у
- 10:21:02нас здесь не предусмотрено случая, когда
- 10:21:04идёт попытка перезаписать задачу. У нас
- 10:21:06просто перезапишется уже существующая
- 10:21:08задача. Если нам второй раз пришла
- 10:21:10задача с уже существующим дублирующимся
- 10:21:12заголовком, и задача просто будет
- 10:21:13переписана, это нужно отслеживать. List
- 10:21:15tasks у нас просто тут будет
- 10:21:16возвращаться мапа сразу же всех наших
- 10:21:18задач. И, кстати, мы уже с вами
- 10:21:19разбирали, почему вот так делать нельзя.
- 10:21:21Мы это будем переделывать. Don't task -
- 10:21:23это, собственно, выполнить задачу. То
- 10:21:24есть мы находим задачу вообще в нашем
- 10:21:26списке задач. Если мы её не нашли, тогда
- 10:21:28мы возвращаем, что задача не найдена.
- 10:21:30Иначе мы эту задачу выполняем и
- 10:21:31перезаписываем уже новое значение
- 10:21:33задачи. О'кей. Ну и task. Мы как бы
- 10:21:35пытаемся найти задачу. Если её нет, то
- 10:21:37not found. Если она есть, тогда мы её
- 10:21:38удаляем. Что бы я тут хотел поменять?
- 10:21:40Во-первых, у нас в файле errors, тут
- 10:21:42ошибка у нас в строке. Почему? Да потому
- 10:21:44что на время выполнения вот этого
- 10:21:46задания туду листа, после первого урока
- 10:21:47мы с вами ещё не знали, что такое
- 10:21:49ошибки, поэтому использовали строки для
- 10:21:50индикации ошибок. Теперь, что такое
- 10:21:52ошибки, мы с вами знаем, поэтому мы
- 10:21:53будем использовать именно их. У нас
- 10:21:55есть, task, note, found. Это у нас
- 10:21:59errors, new. И тут уже можно передавать
- 10:22:02task not found. Это нам больше не нужно.
- 10:22:05Также у нас есть ещё ошибка, то есть она
- 10:22:06должна у нас подразумеваться. Task
- 10:22:09already exist. Это когда у нас идёт
- 10:22:12попытка создать задачу, которая уже
- 10:22:13существует. Errors new task already
- 10:22:17exists. О'кей. Теперь переходим в наш
- 10:22:19лист и смотрим. Когда у нас идёт
- 10:22:20добавление задачи, мы должны сначала
- 10:22:22проверить, что есть ли у нас вообще
- 10:22:23такая задача уже. Каким образом я это
- 10:22:25сделаю? Я возьму и обращусь к нашему
- 10:22:27списку задач. Попробую оттуда достать
- 10:22:29задачу с заголовком той задачи, которую
- 10:22:32хочет создать пользователь, и посмотрю
- 10:22:33таким вот образом. Что если у меня ок,
- 10:22:36видите, я таким образом в одну строчку
- 10:22:38беру, да, и проверяю, есть ли значение в
- 10:22:39мапе. И если есть, это значит, что
- 10:22:41задача с таким заголовком уже есть.
- 10:22:43Тогда я буду возвращать ошибку. Y task
- 10:22:45already exist. Ну и, соответственно, у
- 10:22:46нас этот сам метод тоже должен
- 10:22:47возвращать ошибку. Если всё хорошо,
- 10:22:49тогда я просто создам новую задачу и
- 10:22:52вернул. Вот так вот. Значит, list tasks.
- 10:22:54Тут мы должны с вами переделать это. А
- 10:22:56почему мы это должны переделать, мы с
- 10:22:57вами обсуждали в начале этого урока,
- 10:22:59когда разбирали пример с payйment
- 10:23:00модулем. Поэтому повторяться я не буду.
- 10:23:02Создаём новую мапу.
- 10:23:04map string, task и len l.tasks. Потом я
- 10:23:08просто беру k value range l tasks и в
- 10:23:13новую мапу просто перегоняю все
- 10:23:14значения. K рано val. Всё. После этого я
- 10:23:16могу спокойно вернуть эту самую
- 10:23:18временную мапу. И таким образом, если
- 10:23:20юзер поменяет что-то во временной мапе,
- 10:23:21он не сломает нашу основную. О'кей.
- 10:23:23Don't task, то есть выполнить задачу. У
- 10:23:24нас может быть такое, что задачи нет.
- 10:23:26Тогда мы возвращаем с вами task not
- 10:23:28found. Ну и, соответственно, метод
- 10:23:30должен у нас уже не строку возвращать, а
- 10:23:31ошибку. Если всё о'кей, возвращаем.
- 10:23:34Удаление задачи. То же самое. Task not
- 10:23:36found
- 10:23:39eror. И тут тоже возвращаем л. Ну и плюс
- 10:23:41я бы хотел переименовать метод don't
- 10:23:43task, то есть не, а complete task.
- 10:23:46Complete task. И у самой задачи тоже это
- 10:23:48я бы назвал методом complete.
- 10:23:52И переменную эту не is, а compleded.
- 10:23:56Просто я вот сейчас понял, что вот так
- 10:23:57вот будет, наверное, получше. Ну и тут
- 10:23:59тоже не time, а complete time.
- 10:24:02Это, в принципе, нейминг, это не
- 10:24:04является чем-то супер обязательным и
- 10:24:05критичным. Ну и, соответственно, уже не,
- 10:24:07это completed,
- 10:24:10повторюсь просто. Вот сейчас посмотрел
- 10:24:12на этот ней, мне показался он неудачный,
- 10:24:14и я переименовал в competited. Также
- 10:24:16помним, что я хочу уметь получать список
- 10:24:18всех невыполненных задач. Я бы это, в
- 10:24:20принципе, мог делать на уровне HTTP
- 10:24:22хендлера. То есть я бы просто из туду
- 10:24:23листа через метод list tasks получал все
- 10:24:26задачи и потом уже как-то вручную это
- 10:24:27всё отфильтровывал. Но мне было бы тут
- 10:24:29гораздо удобнее вынести этот метод сразу
- 10:24:31же в сам to list. Как это будет
- 10:24:33выглядеть? Я назову метод list not
- 10:24:35completed tasks. Возвращать мы будем
- 10:24:38также мапу ключ, строка, значение,
- 10:24:41задача. Только уже буду возвращать мапу
- 10:24:43не всех задач, а только лишь тех,
- 10:24:45которые не были выполнены. То есть not
- 10:24:47completed tasks. Это у меня мапа.
- 10:24:52ключ, строка, значение задач и просто
- 10:24:54пойду по всем задачам, которые у меня
- 10:24:55сохранены в моём списке задач. То есть
- 10:24:57title и task. Range l tasks. И тут я
- 10:25:01буду смотреть, что если задача
- 10:25:03completed, но со знаком нет. То есть,
- 10:25:05если задача не выполнена, тогда я
- 10:25:07добавляю её в мою мапу not completed
- 10:25:09tasks. Not completed tasks title равно
- 10:25:12task. Всё. И в конце мне останется
- 10:25:13вернуть эту самую мапу. Вот таким вот
- 10:25:15образом получается, все задачи, которые
- 10:25:17у меня есть в моём списке задач, и они
- 10:25:19были не выполнены, я их добавлю в эту
- 10:25:20мапу и верну её из этого метода. Опять
- 10:25:22же, всё безопасно, потому что я просто
- 10:25:24перекопирую ключи значения в новую мапу.
- 10:25:26Если в этой новой мапе что-то там клиент
- 10:25:28будет менять, то это основную мапу мою,
- 10:25:30в которой хранятся все задачи, это никак
- 10:25:31не затронет. Хорошо. Вот он наш
- 10:25:33тудулиistст. Это то самое наше ядро
- 10:25:34приложение, благодаря которому мы с вами
- 10:25:36будем манипуляции над задачами
- 10:25:37производить. По сути можно сказать, что
- 10:25:39этот дулист - это у нас уровень так
- 10:25:41называемого репозитория. Репозиторием
- 10:25:44принято в бэкэнд-приложениях называть
- 10:25:45сущность, которая ответственна за
- 10:25:47сохранение, изменение, поиск каких-то
- 10:25:50ресурсов. В нашем случае ресурс - это
- 10:25:51задача. И наш тудулист как раз-таки
- 10:25:53ответственен за то, чтобы корректно
- 10:25:55сохранить новую задачу, корректно
- 10:25:57получить все задачи, корректно
- 10:25:59отфильтровать задачи, корректно
- 10:26:00выполнить задачу, удалить задачу. То
- 10:26:02есть основная манипуляция над ресурсом -
- 10:26:04это как раз-таки удел репозитория. То
- 10:26:06есть поэтому можно считать, что у нас
- 10:26:07тут toдулиistст - это репозиторий.
- 10:26:08По-хорошему, у нас должно быть ещё
- 10:26:10несколько уровней, но мы сделаем только
- 10:26:12лишь один уровень, уровень транспорта.
- 10:26:14То есть вот он наш дулист- это как
- 10:26:15раз-таки наш репозиторий, который может
- 10:26:17сохранять, как-то фильтровать, удалять
- 10:26:18задачи. Но для того, чтобы доступ к
- 10:26:20этому тудулисту, для того, чтобы доступ
- 10:26:22вот к этой вот функциональности он был
- 10:26:23по сети доступен, нам нужно обернуть это
- 10:26:25всё в слой транспорта. Мы накидаем сюда
- 10:26:27HTTP хендлеров, которые смогут принимать
- 10:26:29входящие HTTP запросы и потом уже
- 10:26:32пользоваться нашим репозиторием.
- 10:26:33Повторюсь, по-хорошему, тут должно быть
- 10:26:34ещё несколько слоёв, но у нас сейчас
- 10:26:37тема не архитектурные паттерны, у нас
- 10:26:39сейчас тема RESTP, поэтому мы будем
- 10:26:41делать всё довольно просто. Повторюсь, у
- 10:26:42нас есть уровень репозитория. Это наш
- 10:26:44тудулист, который ответствен за
- 10:26:45сохранение, удаление, получения задач. И
- 10:26:47мы это всё обернём в уровень транспорта.
- 10:26:49В нашем случае транспорт - это HTTP, то
- 10:26:51есть это что-то, что позволяет по сети
- 10:26:53приходить в наш сервер и пользоваться
- 10:26:56его ключевой функциональностью. У нас
- 10:26:57ключевая функциональность - это вот
- 10:26:58todдули list. Хорошо, давайте начнём это
- 10:27:00писать. Я создам себе пакет HTTP. И в
- 10:27:02принципе, я уже понимаю, у меня тут
- 10:27:03по-любому будет использование пакетов. А
- 10:27:05для того, чтобы мне пользоваться
- 10:27:06пакетами, мне нужно быть модулем.
- 10:27:08Поэтому я делаю go mode init restp. Я
- 10:27:10это назову. Всё, я создал модуль restp.
- 10:27:12Теперь я могу пользоваться пакетами. В
- 10:27:14пакете http я первое, что создам - это
- 10:27:17filh handlers.go.
- 10:27:19Пакет http. Тут я опишу наши httpхлеры,
- 10:27:22которые будут обрабатывать входящие
- 10:27:23запросы. Это всё у меня будет
- 10:27:24структурой. Type http handlers strctct.
- 10:27:29Что мне нужно в этих HTTPхлерах
- 10:27:31запомнить, чтобы я мог обрабатывать
- 10:27:32ходящие запросы? Todду list, чтобы
- 10:27:35производить над ним операции. Todo.list.
- 10:27:38Всё. И делаю конструктор для наших HTTP
- 10:27:40хендлеров. New HTTP handlers. Я буду
- 10:27:43принимать тудулиist.
- 10:27:48Возвращать наш конструктор будет HTTP
- 10:27:49хендлеры. Return HTTP handlers. И тут
- 10:27:52todдули list. Todдуist. Всё. То есть я
- 10:27:54где-то в каком-то месте создам себеду и
- 10:27:56потом передам его в конструктор
- 10:27:57HTTPхлеров. HTTPхлеры запомнят этот
- 10:28:00дулист и смогут потом с ним делать
- 10:28:01какие-то операции. Теперь поехали
- 10:28:03описывать каждый отдельный хендлер.
- 10:28:04Каждый отдельный хендлер у меня будет
- 10:28:05просто методом моих HTTP хендлеров.
- 10:28:08Первый хенр - это у нас handle create
- 10:28:10task. То есть мы тут будем с вами
- 10:28:12создавать нашу задачу. Значит, наши
- 10:28:14response writer е теперь квесты. Теперь
- 10:28:16давайте подумаем, как этот хендлер
- 10:28:17вообще должен выглядеть, что он должен
- 10:28:18делать, как он должен принимать запросы,
- 10:28:20как он должен отдавать согласно RP. Ну
- 10:28:22давайте смотреть, я создам многосрочный
- 10:28:24комментарий. И тут мы первое, что
- 10:28:25рассмотрим - это паттерн. То есть какой
- 10:28:27должен быть паттерн у этого нашего
- 10:28:28хендлера? Ну, этот хендлер будет
- 10:28:29создавать новую задачу, то есть работать
- 10:28:31над ресурсом задача. Получается, паттерн
- 10:28:33должен называться существительным, а
- 10:28:34существительно - это тот ресурс, над
- 10:28:36которым производится работа, то есть tas
- 10:28:37tks. Окей. Pattern tasks. Метод. Какой
- 10:28:40http метод должен быть у этого нашего
- 10:28:42хендлера? Мы тут создаём новый ресурс,
- 10:28:44значит, метод пост. Далее нам нужно
- 10:28:46определить место, где мы с вами будем
- 10:28:48дополнительную информацию принимать. Так
- 10:28:50как у нас создание нового ресурса, то
- 10:28:52эту самую дополнительную информацию мы
- 10:28:53должны принимать из входящего
- 10:28:55телозапроса. Ну, в нашем случае это
- 10:28:57будет GSON формат GSON in HTTP request
- 10:29:00body, то есть GSON в теле входящего HTTP
- 10:29:03запроса. Хорошо, тут мы с вами
- 10:29:04определили входящие параметры нашего
- 10:29:06хендлера, то есть паттерн, метод,
- 10:29:08дополнительная информация, где будет
- 10:29:09передаваться. Теперь нужно определиться
- 10:29:11с тем, что мы будем возвращать в ответе.
- 10:29:13У нас ответ может быть в случае успеха,
- 10:29:16то есть suксит, и в случае ошибки, то
- 10:29:18есть, значит, suксит, давайте смотреть,
- 10:29:20у нас статус код какой в случае успеха.
- 10:29:23Если у нас успешно создался новый ресурс
- 10:29:25на сервере, это 2011 статус код created.
- 10:29:28Хорошо. А в теле ответа что? Response
- 10:29:30body. В response body у нас должен быть
- 10:29:32отражён созданный ресурс. Хорошо. То
- 10:29:34есть мы в джине представим созданную
- 10:29:36задачу. Json represent created task.
- 10:29:41Всё. То есть у нас в ответе будет GSON,
- 10:29:43который представляет только что
- 10:29:44созданную задачу. Статус код 2011.
- 10:29:46Хорошо. Если у нас ошибка, тогда статус
- 10:29:48код, в принципе, у нас может быть любой.
- 10:29:50Там четырёхсотый BДЕС. 409 конфликт,
- 10:29:54пятисотый внутренняя ошибка сервера либо
- 10:29:56ещё какой-нибудь другой. Тут, в
- 10:29:57принципе, довольно большое разнообразие
- 10:29:59всяких статус-кодов у нас может быть.
- 10:30:01Значит, response body, то есть, что у
- 10:30:02нас в теле ответа? В теле ответа у нас
- 10:30:04должен быть jon ошибкой JSON with error
- 10:30:07плюс time, то есть время, когда эта
- 10:30:09самая ошибка у нас случилась. О'кей.
- 10:30:10Таким образом, мы с вами описали
- 10:30:12контракт для вот этого нашего метода. И
- 10:30:14этот контракт у нас удовлетворяет
- 10:30:16парадигме. Паттерн, метод, место
- 10:30:18передачи дополнительной информации,
- 10:30:20статус-коды и спонс bodyди ответа. Всё
- 10:30:22это у нас соответствует тем самым
- 10:30:24принципам, которые мы только что с вами
- 10:30:25разбирали в теории. Хорошо. Дальше у нас
- 10:30:27должен быть с вами handler
- 10:30:30handle get task.
- 10:30:34Соответственно, этот хендлер у нас
- 10:30:35служит для получения какой-то конкретной
- 10:30:37одной задачи. Давайте разберём, как он
- 10:30:39должен выглядеть. Значит, паттерн у нас,
- 10:30:41очевидно, будет сASKS с/идентификатор
- 10:30:44этой самой задачи. Идентификатор у нас -
- 10:30:46это title, поэтому после tasks через ещё
- 10:30:49один слэш у нас должен быть заголовок
- 10:30:51задачи. Такой вот паттерн. Хорошо.
- 10:30:52Метод. Так как мы получаем с вами
- 10:30:54ресурсы сервера, не создаём, не удаляем,
- 10:30:56не меняем, а получаем. HTTP метод должен
- 10:30:58быть get. Значит, дополнительная
- 10:30:59информация в запросе у нас как раз-таки
- 10:31:01передаётся через паттерн. Вот у нас
- 10:31:03заголовок задачи, которую мы хотим
- 10:31:04получить. Поэтому дополнительная
- 10:31:06информация у нас не в теле запроса, не в
- 10:31:07query параметрах, а именно в паттерне
- 10:31:10зашита. Вот она. Хорошо. В случае успеха
- 10:31:13у нас статус код должен быть 200. 200.
- 10:31:16Ок. Response body,
- 10:31:19собственно, GSON represented font task,
- 10:31:23то есть GSON, который у нас будет
- 10:31:24представлять найденную по переданному
- 10:31:26заголовку задачу. Хорошо. В случае
- 10:31:28ошибки у нас статус код может быть
- 10:31:32также, в принципе, любой: 404 not found,
- 10:31:35пятисотый и так далее, и так далее, и
- 10:31:37так далее. То есть ситуаций, что может
- 10:31:38пойти не так, действительно много. И
- 10:31:40поэтому действительно большое количество
- 10:31:42статус-кодов у нас может в теории быть в
- 10:31:43ответе. Нужно просто, чтобы статус код
- 10:31:45соответствовал ситуации и отражал ту
- 10:31:48ошибку, что произошла. Ну и response
- 10:31:50body. У нас, как обычно, уже, наверное,
- 10:31:52всегда будет же, который представляет
- 10:31:54ошибку, плюс время этой самой ошибки.
- 10:31:55Хорошо. Дальше у нас handlerр,
- 10:31:58который бы позволял нам получать
- 10:32:00абсолютно все задачи, то есть handle,
- 10:32:02get all tasks.
- 10:32:05По сути, в нём будет всё то же самое,
- 10:32:07что и в хендлере, который нам нужен для
- 10:32:09получения одной задачи. Только тут
- 10:32:12паттерн будет не сitle, а просто tasks
- 10:32:15метод get. Дополнительная информация нам
- 10:32:17не нужна здесь никакая. Статус код в
- 10:32:19ответе 200, если всё хорошо и Gson,
- 10:32:22который нам бы показывал найденные
- 10:32:23задачи. Поэтому я тут S добавляю.
- 10:32:25Статус-коды, в принципе, вот абсолютно
- 10:32:27те же самые, только 404 тут, наверное,
- 10:32:28не к месту, то есть не может быть у нас
- 10:32:30такого. Дальше у нас метод
- 10:32:34для получения всех невыполненных задач.
- 10:32:36То есть это handle get all uncompleted
- 10:32:39tasks.
- 10:32:42Всё, что этот метод у нас будет
- 10:32:43принимать? Давайте я скопирую и будем
- 10:32:46смотреть. Значит, patтерn/tasks. Ну, всё
- 10:32:48верно, да? Метод get. Дополнительная
- 10:32:50информация у нас будет передаваться в
- 10:32:51query параметрах. Query params. Ну и,
- 10:32:53соответственно, в паттерне тут у нас
- 10:32:54будет вопрос и completed true. Вот такой
- 10:32:58паттерн мы с вами ожидаем. То есть у нас
- 10:33:00дополнительная информация, она вот в
- 10:33:01query параметрах будет передаваться.
- 10:33:03Хорошо, если всё хорошо у нас было
- 10:33:04статус код 200 и JON, который у нас бы
- 10:33:07показывал найденные задачи. Всё верно.
- 10:33:09Статус-код то же самое, ошибка то же
- 10:33:10самое. Далее у нас метод, который нужен
- 10:33:12для того, чтобы отметить уже какую-то
- 10:33:14существующую задачу как выполненную. То
- 10:33:17есть handle complete task.
- 10:33:23Значит, паттерн какой будет у него?
- 10:33:25СASKs. И дальше нам нужно передать
- 10:33:27идентификатор ресурса, который мы с вами
- 10:33:30будем изменять. То есть у нас изменение
- 10:33:31уже существующего ресурса. Поэтому
- 10:33:33напоминаю, что у нас идентификатор этого
- 10:33:35ресурса будет передаваться в паттерне,
- 10:33:37то есть tйтle, метод. При этом у нас
- 10:33:40будет патчи, то есть мы меняем частично
- 10:33:43уже существующий ресурс. Не полностью, а
- 10:33:44частично. Только лишь одно поле
- 10:33:46completed. Частично меняем существующий
- 10:33:48ресурс - это метод patch. Значит,
- 10:33:49дополнительная информация у нас,
- 10:33:51во-первых, будет передаваться в
- 10:33:52паттерне. Вот этот тайтл. Плюс в дсоне в
- 10:33:56теле запроса JSON in request body мы
- 10:34:00будем передавать, что именно, какое
- 10:34:02именно поле мы с вами меняем. Ну, в
- 10:34:03нашем случае это у нас поле completed. Я
- 10:34:06вот это вот скопирую. Значит, если у нас
- 10:34:08всё было хорошо, тогда у нас статус код
- 10:34:10200. И, в принципе, Jon, который у нас
- 10:34:13будет возвращаться в теле, он должен
- 10:34:15представлять изменённую задачу. Change
- 10:34:17task. Всё. Если что-то не так, то тут, в
- 10:34:20принципе, у нас может быть и 409
- 10:34:22статус-код в ошибке, какой-нибудь ещё.
- 10:34:25Ну а в Респонс Баде, как всегда, у нас
- 10:34:26JSON с ошибкой плюс время. Ну и поехали.
- 10:34:28Хенр для удаления задачи. А delete task.
- 10:34:34Ну тут, в принципе, думаю, уже всё
- 10:34:35понятно, что у нас
- 10:34:37по терм у нас будет сASKS
- 10:34:40с/идентификатор ресурса, который мы
- 10:34:42удаляем. метод у нас будет delete.
- 10:34:46Ну и дополнительная информация, то есть
- 10:34:48заголовок этой самой задачи, которую мы
- 10:34:50удаляем идентификатор ресурса, он у нас
- 10:34:52в паттерне зашит. Дальше я вот это вот
- 10:34:53опять скопирую.
- 10:34:55У нас, если всё произошло успешно, у нас
- 10:34:57статус код 204, но контент и response
- 10:35:01body у нас пустое. Всё. То есть мы с
- 10:35:03вами какой-то ресурс удалили. Если мы
- 10:35:04удалили его успешно, тогда мы просто
- 10:35:06ничего не возвращаем в ответе.
- 10:35:07Возвращаем 204 статус код. что типа да,
- 10:35:09мы удалили, но никакого контента в теле
- 10:35:12ответа нету, потому что, мол, зачем
- 10:35:13ресурс удалён, ты об этом знаешь, всё,
- 10:35:15значит, какие-то ошибки у нас могут
- 10:35:17быть, конечно же, то есть у нас может
- 10:35:18быть 404 not found, да, то есть мы
- 10:35:20пытаемся удалить несуществующий ресурс,
- 10:35:23у нас может быть неверный какой-то
- 10:35:24запрос пользователя, у нас может быть
- 10:35:26какая-то внутренняя ошибка сервера. Ну и
- 10:35:28как обычно у нас тело ответа при ошибке
- 10:35:30содержит Jon с ошибкой и временем
- 10:35:32наступления этой самой ошибки. О'кей, мы
- 10:35:35с вами описали заголовки наших
- 10:35:36хендлеров, и вернёмся мы к имплементации
- 10:35:39этих самых хендлеров немножко позже. А
- 10:35:41пока я хочу в пакете HTTP создать ещё
- 10:35:43один файл сервер и тут описать наш HTTP
- 10:35:46сервер. Значит, у нас будет сервер. Это
- 10:35:48какая-то тоже у нас будет структура.
- 10:35:52И что у нас сервер должен иметь в своих
- 10:35:54полях? То есть от чего у нас зависит
- 10:35:55этот сервер? А сервер у нас будет
- 10:35:57зависеть от хендлеров, то есть входящие
- 10:35:59какие-то подключения. сервер будет
- 10:36:01понимать, что из этих входящих
- 10:36:03подключений чем является, и после этого
- 10:36:05будет вызывать соответствующие хендлеры.
- 10:36:07Поэтому наш сервер должен зависеть от
- 10:36:09HTTP handlers. Это наши самые вот эти
- 10:36:12вот HTTP handlers. О'кей. То есть у нас
- 10:36:14будет конструктор new HTTP север, будет
- 10:36:17он принимать HTTP handlers
- 10:36:20и будет возвращать у нас HTTP сервер.
- 10:36:23Значит, return, http server, http
- 10:36:25handlers. Всё, создали наш сервер.
- 10:36:27Дальше у нас должен быть какой-то метод
- 10:36:29у этого сервера, который будет этот
- 10:36:30самый сервер запускать. Вот он метод у
- 10:36:33http сервера. Старт сервер. Возвращать
- 10:36:36он будет какую-то ошибку. И далее
- 10:36:39интересный момент. Значит, смотрите, нам
- 10:36:40перед тем, как сервер запустить, нужно
- 10:36:42для начала зарегистрировать какие-то
- 10:36:44хендлеры, вот эти вот наши самые
- 10:36:45хендлеры. Но у нас тут довольно сложные
- 10:36:48правила роутинга. Что такое роутинг?
- 10:36:50Роутинг - это по входящим параметрам,
- 10:36:53паттерн, метод и так далее понять, какой
- 10:36:56именно хендлер нужно вызвать. В
- 10:36:58принципе, мы всё это могли бы с вами
- 10:37:00написать по старинке на нашем Net HTTP
- 10:37:03пакете. Тут бы мы писали handle funk, но
- 10:37:06смотрите, какой момент. Handle Fun у нас
- 10:37:08принимает какой-то паттерн, но
- 10:37:10совершенно никак не регулирует HTTP
- 10:37:12метод, который нужен для вызова того или
- 10:37:14иного хендлера. То есть мы, во-первых,
- 10:37:16метод, который нам присылают, должны
- 10:37:17были бы регулировать изнутри хендлера и
- 10:37:19смотреть уже, что если HTTP метод не
- 10:37:21равен метод get, например, тогда это там
- 10:37:23ошибка, status not allow. Это первый
- 10:37:26момент. Второй момент - это то, что у
- 10:37:27нас в каких-то запросах ожидается
- 10:37:29дополнительная информация в паттернах, а
- 10:37:31в каких-то запросах не ожидается. Это
- 10:37:33второй момент. Третий момент. У нас в
- 10:37:36каких-то запросах помимо паттерна ещё и
- 10:37:38query парамсы в этих самых паттернах
- 10:37:40ожидаются. А мы при помощи стандартного
- 10:37:41не HTTP пакета можем организовывать
- 10:37:43автоматический роутинг. только по
- 10:37:45паттерну. Всё остальное, методы,
- 10:37:47дополнительная информация, зашитая в
- 10:37:48паттерн, query параметры, всё это мы бы
- 10:37:51должны были руками как-то обрабатывать,
- 10:37:53руками писать миллион и if эса и
- 10:37:55смотреть, какой же в итоге нам нужно
- 10:37:57выбрать хендлер. Это, в принципе, можно
- 10:37:59сделать, в этом нет какой-то огромной
- 10:38:00проблемы, но у нас времени на это просто
- 10:38:02нету. Поэтому я воспользуюсь уже готовой
- 10:38:04функциональностью. Я воспользуюсь
- 10:38:06готовой библиотекой, которая мне
- 10:38:08позволит автоматически фильтровать
- 10:38:09запросы, выбирать только те, где есть
- 10:38:12нужный паттерн, нужный метод. крипа
- 10:38:14параметры, если мне нужно, и поможет мне
- 10:38:16в соответствии с этими данными вызывать
- 10:38:18нужный хендлер. Это очень удобно, и это
- 10:38:20избавит меня сейчас от большого
- 10:38:21количества времени, потраченного на то,
- 10:38:23чтобы руками всё это отслеживать. Но
- 10:38:25благодаря тому, что мы с вами уже
- 10:38:26объявили себя как модуль, мы вполне
- 10:38:28можем стать зависимыми от этой самой
- 10:38:30сторонней библиотеки. Нам нужно
- 10:38:31прописать goet github.com/gorilla/mux.
- 10:38:35Что это за библиотека? Эта библиотека
- 10:38:37предоставляет очень удобный роутер
- 10:38:39входящих запросов. То есть входящие
- 10:38:41запросы будут иметь какие-то параметры,
- 10:38:43паттерн, метод и так далее. И мы сможем
- 10:38:45говорить, какой хендлер нужно вызвать в
- 10:38:48зависимости от этих самых комбинаций
- 10:38:50входящих параметров. Ну, пишу в консоли
- 10:38:52Google.com Gorilla Max. Опа, скачалась.
- 10:38:55Теперь я могу во время запуска сервера
- 10:38:57сначала создать себе роутер mux.
- 10:39:01New router. Опа. И как мы видим, Mux
- 10:39:04автоматически импортировалась. Вот эта
- 10:39:05вот самая библиотека Gorill Mux. После
- 10:39:07этого я на вторую половину экрана открою
- 10:39:08наши хендлеры, чтобы мы прямо по вот
- 10:39:10этому описанию могли задавать сейчас с
- 10:39:12вами правила в Gorilla роутере. Смотрим
- 10:39:14роутер точка. И у нас тут первый самый
- 10:39:17хендлер - это на создание задачи. У него
- 10:39:20первое правило - это паттерн должен быть
- 10:39:22сASKs. Хорошо. Точка pass. Pass - это,
- 10:39:26то есть путь, по которому будет
- 10:39:27вызываться этот хендлер. Опять же, это
- 10:39:29ещё одно, третье название для вот этой
- 10:39:30вот сущности. То есть у нас есть
- 10:39:31паттерн, у нас есть endpint, вот теперь
- 10:39:34у нас есть ещё и путь какой-то, то есть
- 10:39:35пас. Значит, pass у меня будет такой,
- 10:39:37который у меня тут прописан, то есть
- 10:39:39сASKs. С /asks. Окей. Потом я вижу, что
- 10:39:42у меня тут метод должен быть задан.
- 10:39:44Хорошо. Точка меods. Методs. И тут можно
- 10:39:47перечислить все методы, которые подходят
- 10:39:49для вот этого моего хендлера. Для моего
- 10:39:50хендлера подходит только лишь метод
- 10:39:52пост. Пост. Дальше я вижу, что
- 10:39:53дополнительная информация у меня должна
- 10:39:55быть в теле HTP запроса, но это я уже
- 10:39:57буду руками сам вычитывать изнутри
- 10:39:58своего хендлера. Хорошо, получается, я
- 10:40:00по сути задал все эти правила, которые
- 10:40:01должны быть для вызова вот этого
- 10:40:03хендлера. Теперь мне нужно лишь сказать,
- 10:40:05что я именно этот хендлер должен
- 10:40:06вызывать, если мне придёт запрос на
- 10:40:08pattern/asks с методом post. Для этого я
- 10:40:10пишу ещё одну точку, тут handle funk и
- 10:40:13передаю вот этот вот наш самый handle
- 10:40:15create task. Соответственно, а его я
- 10:40:17могу достать из сервера из
- 10:40:18httphandlers.handle
- 10:40:21task. Давайте я пока сокрою проводник,
- 10:40:24чтобы побольше было видно. Вот таким вот
- 10:40:25образом получается. Я тут сказал, что я
- 10:40:27сделал новый роутер. И в этом роутере я
- 10:40:29уже указал, что я хочу обрабатывать
- 10:40:31handle create task. То есть я хочу
- 10:40:32вызывать этот хендр, если был какой-то
- 10:40:34входящий запрос на patтерn/tasks на
- 10:40:36metthod post, всё, тогда будет вызван
- 10:40:38вот этот вот хенр. Отлично. Теперь мы
- 10:40:39можем идти дальше. Смотрим следующий
- 10:40:41handlerр. Следующий хендр у меня handle
- 10:40:43get task. Хорошо, поехали. Я пишу roуter
- 10:40:46тоp pass. Какой у меня тут путь? У меня
- 10:40:49тут тоже сtasks с/ дополнительный
- 10:40:53параметр у меня зашит в паттерне
- 10:40:55идентификатор ресурса. А этот наш роутер
- 10:40:57gorilla Mux умеет обрабатывать даже
- 10:40:59такие случаи. Мы должны тут также в
- 10:41:00фигурных скобочках написать какое-то
- 10:41:02название какого-то параметра. В нашем
- 10:41:04случае это параметр tйтle, поэтому я его
- 10:41:06так и назову. Тут ti title. И потом мы
- 10:41:08уже изнутри хендлера сможем
- 10:41:10автоматически получать вот эту вот часть
- 10:41:12паттерна в переданном запросе. И это
- 10:41:14очень удобно. Но мы скоро увидим, как
- 10:41:15это делается. Мы описали путь. Дальше
- 10:41:18нам нужно описать метод. Метод у нас
- 10:41:20get. То есть methods. И тут я передаю
- 10:41:22get. Дальше у нас тут никаких quyery
- 10:41:24параметров, ничего нету. Дополнительную
- 10:41:25информацию мы будем вычитывать из
- 10:41:27паттерна. Хорошо. Теперь я могу написать
- 10:41:29handle funk. Ну и, собственно,
- 10:41:31захandдлить вот этот вот handle get
- 10:41:32task. S. http handlers handle get task.
- 10:41:37Отлично. Всё, теперь я сказал, что если
- 10:41:39будет какой-то входящий запрос с/то
- 10:41:43там, и при этом метод будет get, это
- 10:41:45значит, что кто-то хочет получить
- 10:41:46информацию по какой-то задаче. И
- 10:41:48заголовок этой задачи был передан мне в
- 10:41:50пути. Соответственно, нужно вызвать вот
- 10:41:52этот вот хендлер. Потом уже изнутри
- 10:41:53этого хендлера я смогу достать вот этот
- 10:41:55вот переданный тайтл. В этом нам всём,
- 10:41:57опять же, поможет вот эта вот наша
- 10:41:58сторонняя библиотека. Хорошо. Идём
- 10:42:00дальше. Следующий нашхенр. Пишем роутер
- 10:42:02точка. Значит, путь. Путь у нас опять
- 10:42:05просто сtasks. Pass/tasks.
- 10:42:08Хорошо.
- 10:42:10Всё, этой информации нам с вами
- 10:42:11достаточно для того, чтобы понять, что
- 10:42:13кто-то хочет получить все имеющиеся
- 10:42:15задачи. Хорошо. Handle funk и этот самый
- 10:42:19наш handlerр. Handle get all tasks.
- 10:42:22Передаю. Отлично. Идём дальше. Дальше у
- 10:42:25нас хендлер для получения всех
- 10:42:26невыполненных задач. О'кей, поехали.
- 10:42:29roуter.pass у нас/tasks.
- 10:42:32Опять у нас этот путь сtasks. Метод у
- 10:42:35нас get. Но в отличие от предыдущего
- 10:42:38примера, мы уже получаем не абсолютно
- 10:42:40все задачи, а задачи, которые у нас
- 10:42:42подвергнуты фильтрации благодаря
- 10:42:44параметрам. Мы это должны указать
- 10:42:46queries. И тут мы должны передать две
- 10:42:48строки: первую и вторую. Первое - это
- 10:42:50ключ, который мы ожидаем, то есть
- 10:42:52completed, а второе - это значение,
- 10:42:54которое мы ожидаем, то есть true. Ну и,
- 10:42:56соответственно, handle funk, то есть
- 10:42:57функция, которая будет обрабатывать
- 10:42:59такой запрос. Это у нас HTTP handlers
- 10:43:01тоleget all completed tasks. Смотрите,
- 10:43:04какая длинная у нас строка вообще
- 10:43:05получилась на 126 символов. Да, это
- 10:43:07жёстко. Что мы тут сказали? Мы тут
- 10:43:09сказали, что если к нам придёт запрос на
- 10:43:12patterns/tasks с методом get, и там
- 10:43:14будут переданы query параметры completed
- 10:43:16= true, тогда вызови мне вот этот вот
- 10:43:18http handler, который как раз-таки у нас
- 10:43:20описан и будет предназначен для того,
- 10:43:22чтобы обрабатывать запрос на получение
- 10:43:23всех невыполненных задач. Отлично.
- 10:43:25Значит, поехали дальше. roутер. Значит,
- 10:43:29путь у нас опять tasks и опять у нас
- 10:43:31какой-то дополнительный параметр
- 10:43:33передаётся прямо в пути запроса. Мы это,
- 10:43:35конечно же, указываем. /itLE это у нас
- 10:43:38хендлер для того, чтобы выполнить
- 10:43:39какую-то задачу. И как раз-таки
- 10:43:40заголовок этой задачи у нас передаётся в
- 10:43:42паттерне. Хорошо. Смотрим метод. Метод у
- 10:43:45нас должен быть patch. QY параметров у
- 10:43:47нас тут каких-то нету. У нас вся
- 10:43:48информация будет передаваться в паттерне
- 10:43:50и в jon теле. Ну, jon тело входящего
- 10:43:52запроса, мы просто с вами вычитаем
- 10:43:53внутри хендлера. Это нам никаких проблем
- 10:43:55не составит. Хорошо. И говорю handle
- 10:43:57funk handlers
- 10:44:00handle complete task. Вот так вот. То
- 10:44:02есть я говорю, что если мне присылается
- 10:44:04патч запрос на patterns task/title, это
- 10:44:07значит, что кто-то хочет изменить
- 10:44:08ресурс, а значит изменить какую-то
- 10:44:09задачу. А заголовок задачи у нас передан
- 10:44:11в паттерне. Ну и потом уже внутри вот
- 10:44:13этого хендлера мы с вами дополнительно
- 10:44:14провалидируем, что же нам там в входящем
- 10:44:16джисоне передают. О'кей. Что у нас
- 10:44:18остался? Последний хендлер, который
- 10:44:20нужен нам, чтобы что-то удалить, ну,
- 10:44:21конкретно удалить нашу задачу. Router
- 10:44:23pass. Опять у нас путь tasks/title
- 10:44:27methods
- 10:44:29delete. Ну и этой информации нам уже
- 10:44:30достаточно, чтобы понять, что кто-то
- 10:44:31хочет удалить какую-то задачу. Handle
- 10:44:33funk s http handlers delete task. Всё,
- 10:44:37мы с вами описали тут роутинг наших
- 10:44:40входящих запросов. Повторюсь, то же
- 10:44:42самое мы бы могли с вами сделать
- 10:44:43благодаря стандартному Net HTTP пакету,
- 10:44:45но это сейчас заняло бы слишком большое
- 10:44:47количество времени, чтобы разрулить все
- 10:44:49входящие запросы, понять, какой у них
- 10:44:51там метод, где есть параметры, где нету,
- 10:44:53что вызывать и так далее, и так далее, и
- 10:44:54так далее. А так, благодаря сторонней
- 10:44:56библиотеке Gorill Max, мы использовали
- 10:44:58её роутер, который очень удобненько за
- 10:44:59нас весь этот роутинг и будет исполнять.
- 10:45:01Нам лишь остаётся задать корректные
- 10:45:03правила для вызова каких-то определённых
- 10:45:05хендлеров. После этого я хочу запустить,
- 10:45:07собственно, сам наш HTTP сервер.
- 10:45:09Делается это стандартным образом
- 10:45:10http.listen and surve. Первый у нас
- 10:45:12параметр, как обычно, наш адрес, то есть
- 10:45:14наш порт, на котором мы запустим наше
- 10:45:16приложение. А второе, раньше мы
- 10:45:18указывали здесь, потому что мы всегда
- 10:45:20регистрировали наши хендлеры через
- 10:45:22handle funkмую из пакета HTTP, а теперь
- 10:45:25мы тут будем указывать не NIL, а будем
- 10:45:26указывать наш роутер вот таким вот
- 10:45:28образом. То есть мы наполнили наш роутер
- 10:45:31правилами роутинга и потом передали его
- 10:45:33в HTTP listen and serve. И уже там под
- 10:45:35капотом эти две библиотеки подружатся. И
- 10:45:37стандартная библиотека Net HTTP, которая
- 10:45:39будет принимать входящее HTTP
- 10:45:40подключение, благодаря вот этому роутеру
- 10:45:42из сторонней библиотеки будет правильно
- 10:45:44роутить входящие запросы, исходя из
- 10:45:46заданных правил. Ну и всё. И тут мне
- 10:45:48даже нет смысла как-то отдельно получать
- 10:45:49ошибку. Я могу просто написать следующим
- 10:45:51образом. И если в функции listen and
- 10:45:52Surf возникнет какая-то ошибка, она
- 10:45:54вернётся из метода старт сервер. Если
- 10:45:56ошибки не будет, то и тут вернётся. Всё
- 10:45:58отлично. О'кей, теперь у нас организован
- 10:46:00роутинг входящих запросов наши хендлеры.
- 10:46:02Осталось эти самые хендлеры описать. Ну
- 10:46:04о'кей, поехали. Первый хендр handle
- 10:46:06create task. То есть нам нужно создать
- 10:46:07задачу. Сама задача нам придёт в Gsony
- 10:46:09во входящем HTTP запросе. Получается,
- 10:46:11нам нужна какая-то структура, которая
- 10:46:13описывает эту входящую задачу, чтобы мы
- 10:46:15могли читать тело входящего запроса,
- 10:46:17преобразовывать его в GSON и потом этот
- 10:46:19GSON преобразовывать в нашу структуру.
- 10:46:21Мы бы могли с вами взять задачу, которая
- 10:46:23у нас лежит в пакете. Но в чём проблема?
- 10:46:25То, что в этой задаче слишком много
- 10:46:27полей. Помимо заголовка и описания, тут
- 10:46:29ещё есть информация, выполнена она или
- 10:46:30нет. Есть информация про время создания,
- 10:46:32время выполнения. Нам это всё не нужно.
- 10:46:34Нам нужно по сети принять только
- 10:46:36заголовок и описание. Поэтому данная
- 10:46:38структура Task нам не подойдёт. А что
- 10:46:40делать тогда? Создавать ещё одну
- 10:46:41структуру Task. Да, всё верно. Такие
- 10:46:43структуры, которые нужны для того, чтобы
- 10:46:45передавать какие-то данные, называются
- 10:46:47DTO, то есть data transfer object. Я
- 10:46:49создам для этой ДТОшки дополнительный
- 10:46:51файл в пакете HTTP и опишу её следующим
- 10:46:54образом. Это у меня будет структура task
- 10:46:56DTO.
- 10:46:57Напоминаю, DTO - это data transfer
- 10:47:01object, то есть это какой-то объект,
- 10:47:03который нужен нам не для того, чтобы
- 10:47:04хранить как-то информацию, не для того,
- 10:47:06чтобы что-то там какие-то ресурсы на
- 10:47:08сервере сохранять, не для того, чтобы
- 10:47:10запоминать там время выполнения или там
- 10:47:12выполнена задача или нет. Нет, нам эта
- 10:47:13структура нужна лишь для того, чтобы
- 10:47:15принять входящий HTTP запрос. А это
- 10:47:18значит, что нам тут нужно всего лишь два
- 10:47:20поля. Это заголовок задачи и её
- 10:47:22описание.
- 10:47:25Всё, этого нам хватит для того, чтобы
- 10:47:26принять входящий HTTP запрос на создание
- 10:47:28новой задачи. Поэтому это и называется
- 10:47:30DTO, то есть дата transfer object.
- 10:47:32Просто объекты для того, чтобы передать
- 10:47:33данные по сети и не более. Хорошо, я
- 10:47:35беру внутри хендлера для создания
- 10:47:37задачи, делаю так, как мы уже умеем.
- 10:47:38Сначала создаю структуру Task DTO, в
- 10:47:41которую я буду читать входящий GSON. Ну
- 10:47:43и потом поехали. Json.newde decoder
- 10:47:45r.bud.ddec.
- 10:47:48Сюда передаю по указателю наш TASD. И у
- 10:47:51нас Decд возвращает ошибку.
- 10:47:52Соответственно, если у нас какая-то
- 10:47:53ошибка произошла, у нас R не равно, в
- 10:47:55таком случае мне нужно каким-то образом
- 10:47:57вернуть ошибку из этого нашего хендлера.
- 10:47:59Плюс ещё вернуть ответ тоже в дсонеoneне
- 10:48:01и статус код вернуть. Хорошо, давайте
- 10:48:03начнём с respons. То есть нам нужен
- 10:48:04какой-то jon, где будет у нас ошибка
- 10:48:06плюс время, да? Это тоже, в принципе,
- 10:48:07можно назвать DTO, то есть это data
- 10:48:09transfer object, только это уже не task
- 10:48:11dto, а error dto?
- 10:48:13У нас тут есть, собственно, messageдж,
- 10:48:15то есть это сообщение об ошибке, и у нас
- 10:48:17тут есть время совершение этой самой
- 10:48:19ошибки. Хорошо, я беру сдуto, это у меня
- 10:48:24dto, message у меня - это то. И время -
- 10:48:28это time now, то есть время, когда эта
- 10:48:30самая ошибка была совершена. А дальше в
- 10:48:32стандартном пакете HTTP есть
- 10:48:33замечательная функция error. Первым
- 10:48:35аргументом она принимает respons writer.
- 10:48:37Вторым аргументом она принимает ошибку,
- 10:48:39но в строке. Как раз-таки в эту строку я
- 10:48:41бы хотел передавать вот эту вот самую
- 10:48:42нашу структуру, которая у нас
- 10:48:43преобразована в GSON. Ну и третьим
- 10:48:45аргументом эта самая функция Erorра у
- 10:48:47нас принимает статус код. О'кей. То есть
- 10:48:48нам каким-то образом нужно вот эту
- 10:48:49структуру уметь преобразовывать в jonщ,
- 10:48:51чтобы этот jon был строкой, и потом бы
- 10:48:53мы смогли это вот сюда записать. Да, на
- 10:48:54самом деле, в этом проблем-то никаких
- 10:48:55нету. Мы можем открыть эту нашу error
- 10:48:57dto и сделать ей метод to string. То
- 10:49:00есть вот у меня это error dto to string.
- 10:49:03Возвращать этот метод будет строку. И
- 10:49:05тут я буду просто преобразовывать эту
- 10:49:06самую структуру в jon, который буду
- 10:49:08преобразовывать в строку и возвращать
- 10:49:10отсюда. Всё просто. Вызываю пакет jon.
- 10:49:12тут функцию Marshall Indent, потому что
- 10:49:14я хочу, чтобы у меня были всё-таки
- 10:49:15красивые отступы, независимо от того, я
- 10:49:18посылаю запросы из постмана или ещё
- 10:49:20откуда-нибудь из другого места. Значит,
- 10:49:21я буду преобразовывать в GSON.
- 10:49:23Собственно, первый аргумент - это наша
- 10:49:24самая структура ДТО. Второй аргумент
- 10:49:26префикс мне никакой не нужен. И отступ у
- 10:49:28меня будет это Tab. Ну или же четыре
- 10:49:30пробела. 1 2 3 4. Отлично. Marshaal
- 10:49:33intent нам возвращает два значения. Это
- 10:49:35слай и это какая-то ошибка. Но тут какой
- 10:49:37момент? Ошибка у нас вернётся только в
- 10:49:40самых каких-то краевых случаях. Первый
- 10:49:42случай - это если у нас, к примеру, в
- 10:49:44этой структуре, а канал мы никак не
- 10:49:45можем преобразовать в GSON, потому что
- 10:49:47это сущность именно языка
- 10:49:48программирования, и тут уже никак её не
- 10:49:50представить в текстовом формате. И
- 10:49:51второе, если бы тут были какие-то
- 10:49:53цикличные зависимости, ну, давайте
- 10:49:54покажу, что я имею в виду. Вот у нас
- 10:49:55есть какое-то поле, это указатель на
- 10:49:57ермотрим, какую вообще непонятную вещь я
- 10:50:00сейчас буду делать. То есть я создаю вот
- 10:50:02это вот в одной строчке, во второй
- 10:50:04строчке я указателю внутреннему
- 10:50:07присваиваю самого себя таким вот
- 10:50:10образом. То есть я в этом поле с
- 10:50:12указателем начал указывать сам на себя.
- 10:50:15Это зацикливание указателей. И с таким
- 10:50:17тоже пакет Jon не умеет работать. Но это
- 10:50:19точно не наш случай. У нас тут никаких
- 10:50:21указателей нету. Каналов, ничего такого.
- 10:50:23У нас очень простая структура, состоящая
- 10:50:25из одной строки и из одного времени. Мы
- 10:50:27по-любому должны уметь это
- 10:50:29преобразовывать в GSON. Ну, по-любому. И
- 10:50:31мы даже никак не можем обработать эту
- 10:50:33ошибку, потому что она в корне своём
- 10:50:35будет просто фатальной, если мы какую-то
- 10:50:37базовую структуру не можем преобрать в
- 10:50:38GSON. Поэтому я не буду тут никак
- 10:50:40обрабатывать эту ошибку. Это глупость. Я
- 10:50:42буду кидать панику. Если у меня какая-то
- 10:50:44ошибка есть, тогда я просто буду панику
- 10:50:46кидать и всё. Потому что я не вижу
- 10:50:47смысла тут пытаться как-то обработать
- 10:50:49эту ошибку, если у меня не получилось
- 10:50:51делать базовые вещи. То есть мы же,
- 10:50:52когда с вами к одной целочисленной
- 10:50:54переменной прибавляем другую, мы же не
- 10:50:55получаем никаких ошибок. А если ошибка
- 10:50:57есть, ну, значит, в принципе, наша
- 10:50:58программа не должна работать. Вот тут то
- 10:51:00же самое. То есть если мы не смогли
- 10:51:01базовую какую-то вещь совершить с
- 10:51:02базовой структурой, тогда нужно панику
- 10:51:04кидать. Тут я не вижу смысла это как-то
- 10:51:06обрабатывать. Но если всё хорошо, я буду
- 10:51:08возвращать.
- 10:51:09наш слайт преобразованный в строку.
- 10:51:11Таким образом, я получу из нашей
- 10:51:13структуры DTO gson, который у меня будет
- 10:51:15представлен в строке. Таким образом, я
- 10:51:17тут в error функцию из пакета HTTP смогу
- 10:51:20передавать y ddto тоt to string. И таким
- 10:51:24образом я буду обеспечивать вот этот вот
- 10:51:26jon с ошибкой и временем совершения
- 10:51:28ошибки в теле ответа. Отлично. И значит,
- 10:51:30третье - это я должен передать
- 10:51:31статус-код. Ну, если тут у меня уже не
- 10:51:33получилось преобразовать входящее тело
- 10:51:35запроса в какую-то структуру при помощи
- 10:51:37пакета GSON, значит, с вероятностью 99%
- 10:51:39клиент передал какой-то кривой запрос.
- 10:51:41Поэтому я стаat код буду возвращать
- 10:51:43BДвест. Ну и после всего этого я
- 10:51:46завершаю наш хендлер. Всё. Если я
- 10:51:48хендлер ещё не завершил, это значит я
- 10:51:51прочитал входящий запрос в структуру
- 10:51:53Task DTO. Дальше нам нужно
- 10:51:55провалидировать эту самую Task DTO,
- 10:51:57потому что напоминаю, если бы нам просто
- 10:51:59передали вот такой вот пустой же, он
- 10:52:01хоть и пустой, но благодаря вот этим
- 10:52:02двум скобкам это всё ещё jon, то тут
- 10:52:04никаких ошибок, к сожалению, бы нам не
- 10:52:06вернулось. Поэтому нам нужно
- 10:52:07дополнительно руками своими прописать
- 10:52:09валидацию для вот этой вот TASДTO. И я
- 10:52:11это сделаю. Я просто напишу метод ей
- 10:52:13validate for create. Возвращать он будет
- 10:52:17ошибку. И что он будет делать? просто
- 10:52:19смотреть, что если у меня title - это
- 10:52:21пустая строка, тогда я буду возвращать
- 10:52:23ошибку errors new title is empty. Всё.
- 10:52:28Если у меня description - это пустая
- 10:52:30строка, тогда я буду возвращать errors
- 10:52:32new description и с empty. А если всё
- 10:52:36хорошо, я буду возвращать new. Всё.
- 10:52:37Почему я назвал этот метод не просто
- 10:52:39validate, а validate for create? Потому
- 10:52:41что эта у нас валидация, она
- 10:52:42предназначена именно для того, чтобы
- 10:52:44провалидировать task doo. когда мы будем
- 10:52:46эту tasк dto использовать для создания
- 10:52:48новой задачи, потому что у нас могут
- 10:52:49быть в будущем всякие другие методы,
- 10:52:51которые используют эту Task DTO, и там
- 10:52:53уже может быть другая валидация. Поэтому
- 10:52:55я просто оставляю validate for create.
- 10:52:56Хорошо, прочитал входящее тело запроса,
- 10:52:58и теперь я буду смотреть на валидацию.
- 10:53:00Если ошибка была совершена во время
- 10:53:03того, как я валидирую tasк DTO,
- 10:53:06в таком случае опять я создаю ДТО. р
- 10:53:09DTOдж у меня - это сообщение об ошибке.
- 10:53:12Время - это у меня текущее время. То
- 10:53:14есть, когда эта самая ошибка была у меня
- 10:53:16совершена. После этого я записываю HTTP
- 10:53:18ответ,
- 10:53:20передаю туда response writer, саму
- 10:53:22ошибку. Это у меня dto to string. Ну и
- 10:53:25статус код опять B request. Почему? Да
- 10:53:27потому что если мы не провалидировали
- 10:53:28входящий запрос, значит точно ошибка на
- 10:53:30стороне клиента, что он передал какой-то
- 10:53:32неправильный запрос. Хорошо, мы
- 10:53:33прочитали входящий запрос, мы его
- 10:53:35провалидировали, теперь можем,
- 10:53:36наконец-таки, уже начать добавлять
- 10:53:38задачу в наш-дулист. Для начала мы
- 10:53:39посмотрим, что наш дулист при добавлении
- 10:53:41задачи ждёт задачу тоже из пакета
- 10:53:43tod-ду. То есть мы должны сначала её
- 10:53:44создать. Я это назову task.
- 10:53:48Пакет todo.
- 10:53:49Ну и тут я title возьму из нашей дшки.
- 10:53:53Taskdt. title. И desption тоже из нашей
- 10:53:55дшки. То есть мы tйтle и desрипtion
- 10:53:56приняли по сети. Хорошо, я создал эту
- 10:53:58новую задачу именно как переменную.
- 10:54:00Теперь я должен её добавить в todo list.
- 10:54:02Todдуist у нас лежит в хендлерах
- 10:54:03h.tulist.
- 10:54:05И пробую добавить вот эту вот самую
- 10:54:06задачу. Но тут у нас могут какие-то
- 10:54:08ошибки возникнуть. Я тоже должен их
- 10:54:09отслеживать. если у меня ошибка какая-то
- 10:54:12если у меня ошибка не равна. Хорошо,
- 10:54:14давайте посмотрим, какие тут могут быть
- 10:54:16ошибки. Y task already exist. Хорошо,
- 10:54:18получается, я тут могу проверить, какая
- 10:54:21ошибка мне пришла, потому что мне помимо
- 10:54:23Task already exist в теории тут могли бы
- 10:54:25прийти какие-то другие ошибки. И в
- 10:54:27зависимости от того, какая именно ошибка
- 10:54:28мне пришла, такой статус-код я и должен
- 10:54:31отдавать. Если у меня TASK already
- 10:54:32exist, в таком случае я должен отдавать
- 10:54:34409 статус код, то есть конфликт. Если
- 10:54:36какая-то другая ошибка, то я бы вернул
- 10:54:38просто уже пятисотый статус-код, что
- 10:54:39что-то непонятное у нас случилось.
- 10:54:41Поэтому я буду смотреть. Если erors is,
- 10:54:44есть в пакете EORS метод is, который
- 10:54:46позволяет понять, является ли полученная
- 10:54:48ошибка экземпляром какой-то конкретной
- 10:54:50ошибки. В нашем случае Task already
- 10:54:52exist. Хорошо, я смотрю, что если у нас
- 10:54:54наша ошибка это to do то. Task already
- 10:54:58exist. В таком случае я знаю, что именно
- 10:55:00пошло не так во время добавления новой
- 10:55:01задачи, а именно я попробовал добавить
- 10:55:03задачу, которая уже существует. Ну, а
- 10:55:05иначе я получил совсем какую-то
- 10:55:06неизвестную ошибку. Я просто статус код
- 10:55:08500 верну. Так вот, значит, я должен
- 10:55:10объявить ddt. Оно у меня, в принципе, и
- 10:55:12для того, и для того случая будет общее.
- 10:55:14DТО - это у меня error dto message у
- 10:55:17меня. И - это time now. Хорошо. Теперь,
- 10:55:22если у меня ошибка Task already exist,
- 10:55:24тогда я буду HTTP error вызывать,
- 10:55:28передавать туда ydto тоту. Но статус код
- 10:55:32у меня будет HTTP conflict. Вот статус
- 10:55:35confнкт. Это наш самый вот этот вот 409
- 10:55:37код. И получается, я буду сообщать о
- 10:55:38том, что у меня не получилось создать
- 10:55:40новую задачу, потому что такая уже
- 10:55:42существует. Иначе я буду делать
- 10:55:43абсолютно всё то же самое. Я даже это
- 10:55:45скопирую. Вот. Но возвращать я уже буду
- 10:55:47статус internal server, потому что я
- 10:55:50здесь получил какую-то ошибку. Я даже не
- 10:55:51знаю, что это за ошибка. Ну, в таком
- 10:55:53случае какая-то внутренняя ошибка
- 10:55:54сервера даже мне неизвестная. Ну, если
- 10:55:56что, всё равно текст ошибки у меня будет
- 10:55:57в рто, поэтому можно будет посмотреть. А
- 10:55:59что ж, что ж именно-то пошло не так? Ну
- 10:56:01и всё. И после этого я завершаю
- 10:56:02выполнение хендлера. Хорошо, но если мы
- 10:56:04не получили тут ошибку, значит, мы
- 10:56:05успешно добавили нашу задачу. Что это
- 10:56:08значит? Это значит, мы должны отдать наш
- 10:56:09успешный ответ. Успешный ответ - это у
- 10:56:11нас статус код 2011. И в GSON теле я
- 10:56:13должен отдать GON, который представляет
- 10:56:15созданную задачу. То есть уже не Task
- 10:56:17DTO я должен в Gson вернуть, а я должен
- 10:56:19вернуть вот эту вот туду задачу. которая
- 10:56:21у нас полноценная, которая там вот с
- 10:56:22большим количеством полей. Хорошо,
- 10:56:24значит, первым делом я захочу из этой
- 10:56:25задачи получить jon. Ну, я это и сделаю.
- 10:56:27Json Marshaal Indent. Это наша-ду tasк,
- 10:56:30я её передаю. Префикс никакой. А вот
- 10:56:32отступ я сделаю. Четыре пробела. 1 2 3
- 10:56:344. Marshallent у нас возвращает слай и
- 10:56:37ошибку. Хорошо, с этой ошибкой я буду
- 10:56:39поступать точно так же, как и с ошибкой
- 10:56:41при преобразовании eror dto, то есть я
- 10:56:43просто буду панику кидать, потому что
- 10:56:44тут у нас никакой ошибки быть просто не
- 10:56:46должно.
- 10:56:48Паник. А если всё хорошо, я наконец-таки
- 10:56:51получил jon, который я буду возвращать в
- 10:56:53ответе, и я могу спокойно записать
- 10:56:55статус код HTTP created. Вот он наш 2011
- 10:56:58статус код HTTP created. И потом я
- 10:57:01запишу в responsт наш слай. Опять же WR
- 10:57:05у нас может вернуть какую-то ошибку, но
- 10:57:07нам всё, что останется с этой ошибкой
- 10:57:08сделать - это вывести её в консоль,
- 10:57:11потому что у нас не получилось записать
- 10:57:13а HTTP ответ. В таком случае мы уже, в
- 10:57:15принципе, не сможем поддержать вот этот
- 10:57:17вот наш контракт, что мы там в случае
- 10:57:18возникновения ошибки будем jon отдавать
- 10:57:20вот эту нашурdто. Нет, мы уже этого
- 10:57:22сделать не сможем, потому что у нас
- 10:57:23возникла ошибка во время записи ответа.
- 10:57:25Тут уже ничего не поделаешь, тут просто
- 10:57:27я выведу на экран fail it to wr http
- 10:57:30response. Ну и вот саму ошибочку выведу
- 10:57:33и завершу наш хенбер. А иначе всё. Иначе
- 10:57:35мы с вами успешно прочитали тело
- 10:57:36входящего запроса, успешно его
- 10:57:38провалидировали, успешно добавили новую
- 10:57:41задачу в наштуду лист и дали ответ об
- 10:57:43этом. Хорошо, поехали к следующему
- 10:57:44хендлеру. Следующий хендлер у нас нужен
- 10:57:46для того, чтобы получать информацию о
- 10:57:47какой-то одной отдельно взятой задаче.
- 10:57:50Получается, мы из входящего запроса в
- 10:57:51этот раз должны уже читать не JON в теле
- 10:57:54запроса, а должны получить вот этот вот
- 10:57:56заголовок входящей задачи из паттерна.
- 10:57:58Как это делается при помощи вот этой вот
- 10:57:59самой библиотеки Gorilla Mux, которую мы
- 10:58:02с вами установили? Мы берём, пишем MUК
- 10:58:05тоws.
- 10:58:06Сюда мы передаём HTTP запрос, то есть
- 10:58:09вот этот вот наш аргумент R. Что это нам
- 10:58:11даёт? Это нам вернёт мапу ключей и
- 10:58:13значений. В нашем случае ключом будет
- 10:58:15вот этот вот параметр tйтle, а значением
- 10:58:17будет то, что на этом месте написал
- 10:58:19клиент. Значит, ключ у меня tйтle. Ну и
- 10:58:22так как это у нас мапа, то я могу
- 10:58:23получить два значения. Первое - это,
- 10:58:25собственно, сам tйтle. Второе - это ок.
- 10:58:27То есть есть он или нет. И если у меня
- 10:58:28не ок, это получается, что у меня
- 10:58:31пользователь не передал вот этот вот
- 10:58:32тайтл, хотя должен был. Но давайте
- 10:58:34подумаем, нужно ли мне проверять вообще
- 10:58:36этот второй параметр. Казалось бы, мы из
- 10:58:37мапы получаем какой-то ключ. этого
- 10:58:39ключа-то может не быть, но так как мы
- 10:58:41этот handlerр handle getask
- 10:58:43зарегистрировали при помощи этой нашей
- 10:58:44сторонней библиотеки при помощи её
- 10:58:46роутера и сказали вызывать handle get
- 10:58:48task, если во входящем паттерне есть вот
- 10:58:50этот вот ключик title, то получается
- 10:58:51если хендлер был так и вызван, значит,
- 10:58:53этот ключик по-любому есть. Поэтому мы
- 10:58:55тут можем не проверять вот это вот самое
- 10:58:57ок. Если у нас вызвался этот хендлер
- 10:59:00по-любому, значит, у нас есть вот этот
- 10:59:01вот тайтл в паттерне, и по-любому мы его
- 10:59:03сможем вычитать. О'кей, вычитали тайтл.
- 10:59:05Мы знаем тайтл. Сразу можем обратиться к
- 10:59:07нашему тудулисту и попробовать получить
- 10:59:09какую-то задачу. Но что самое
- 10:59:10интересное, мы в нашем ту-дулисте не
- 10:59:12предусмотрели метод для получения одной
- 10:59:14какой-то задачи. У нас есть метод только
- 10:59:16для получения всех задач либо получения
- 10:59:18задач невыполненных. Ну давайте добавим
- 10:59:20этот метод, что у нас как бы процесс
- 10:59:21разработки. Не всегда мы с самого начала
- 10:59:23можем полностью описать какой-то пакет.
- 10:59:25Абсолютно нормально, что по ходу
- 10:59:26разработки у нас понемножку добавляется
- 10:59:27новая функциональность. Значит, я
- 10:59:29добавляю новый метод list, это у меня
- 10:59:30будет get task. И принимать tйтle
- 10:59:33задача, которую я хочу, собственно,
- 10:59:34дать, будет возвращать два значения.
- 10:59:36сама задача и возможная ошибка, если
- 10:59:37задача не найдена. Окей. Task запятая.
- 10:59:40OK - это l.
- 10:59:42По ключу title. Если не OK, тогда return
- 10:59:45year task not found. Ну и первое
- 10:59:47возвращаемое значение - это структура
- 10:59:48задачи, которая будет создана по
- 10:59:50умолчанию, потому что требуется у меня
- 10:59:51вернуть два значения, если я объявил,
- 10:59:53что буду два значения возвращать. Но
- 10:59:55первое - это бессмысленное значение.
- 10:59:56Второе - это вот наша самая ошибка. Если
- 10:59:58всё хорошо, тогда я буду возвращать эту
- 10:59:59самую задачу. И вторым аргументом как бы
- 11:00:02то, что ошибки нету. Хорошо, тут
- 11:00:03toдулист. Теперь я могу зать get task и
- 11:00:05передать туда этот самый tйтle и
- 11:00:06получить задачу и через запятую ошибку и
- 11:00:09смотреть, что если у меня ошибка
- 11:00:11неравно, тогда я буду смотреть, что если
- 11:00:13у меня errors is, это самая полученная
- 11:00:16ошибка, это to do year, task not found.
- 11:00:19То есть, если случилась ошибка и это
- 11:00:20именно ошибка, задача не найдена, в
- 11:00:22таком случае я знаю, какой статус-код
- 11:00:24мне нужно возвращать в ответе. Иначе
- 11:00:26Иначе я не знаю, какой статус код. А
- 11:00:28если я не знаю, значит, какая-то
- 11:00:29внутренняя ошибка сервера неизвестно.
- 11:00:31Это 500 статус код. Хорошо. До этого я
- 11:00:33объявлю.
- 11:00:34Это вот наше сTO. Message тут у меня -
- 11:00:37это е. И - это у меня time. На самом
- 11:00:41деле вот это можно было вынести в
- 11:00:42конструктор, который бы принимал
- 11:00:44сообщение об ошибке и сам уже нам
- 11:00:45создавал эту структуру и выставлял ей
- 11:00:47время. Ну, можно, почему нет, но мы не
- 11:00:49сделали так. Это мы отправим тогда в наш
- 11:00:51техдолг, и я на ваши плечи кладу
- 11:00:53исправить это. То есть вынести вот это
- 11:00:54создание новой DTO в конструктор
- 11:00:56отдельный. Хорошо. И можем начать
- 11:00:58записывать наши ответы. http eror
- 11:01:01response writer. Сама ошибка - это ydto
- 11:01:04string. И третье - это у меня http not
- 11:01:07found.
- 11:01:08Таким вот образом. То есть если я
- 11:01:10попытался получить задачу, у меня
- 11:01:12возникла ошибка. Я узнал, что эта ошибка
- 11:01:13связана с тем, что задачи у меня нету. В
- 11:01:15таком случае я возвращаю статус код not
- 11:01:16found, то есть задача не найдена. Иначе
- 11:01:18я буду делать всё то же самое, только у
- 11:01:20меня статус же будет internal server
- 11:01:22eror, потому что я, в принципе, не знаю,
- 11:01:23что случилось. Ну и буду завершать наш
- 11:01:25хендлер. Всё. Если же ошибки не было,
- 11:01:28это значит, что я успешно смог получить
- 11:01:29свою задачу. Это значит, всё, что мне
- 11:01:31осталось - это вернуть статус код 200 и
- 11:01:33вернуть jon этой самой задачи. О'кей, я
- 11:01:35обращаюсь к пакету GSON marsha, передаю
- 11:01:38туда задачу. Никакого префикса, а отступ
- 11:01:41у меня будет четыре пробела. Ну и,
- 11:01:42соответственно, я буду получать сй баййт
- 11:01:43и ошибку. Если у меня ошибка не равна,
- 11:01:46тогда я буду паниковать, почему я уже
- 11:01:48объяснял. Ну и теперь я могу со
- 11:01:49спокойной душой в HTTP ответ записать
- 11:01:52200 ОК. То есть это HTTP у меня статус
- 11:01:54ОК.
- 11:01:55Ну и в ответ я смогу записать вот этот
- 11:01:58вот наш самый GSON, который представляет
- 11:01:59собой нашу ту самую задачу из туду
- 11:02:01листа. Ну и опять же метод WR у нас
- 11:02:03может возвращать ошибку. Если у меня
- 11:02:05ошибка не равна, тогда я ничего, кроме
- 11:02:08вывода на экран тут не смогу сделать.
- 11:02:09Failed to wr http response. Ну и тут вот
- 11:02:12наша ошибочка. Ну и всё. И завершаю
- 11:02:14hand. О'кей. Мы с вами теперь уже умеем
- 11:02:17добавлять новые задачи. И мы с вами
- 11:02:19умеем получать уже существующие задачи.
- 11:02:22Ну и тут самое главное у нас - это не
- 11:02:23забывать завершать наши хендлеры после
- 11:02:25обработки какой-то ошибки. Потому что
- 11:02:27если мы забудем это сделать, то мы
- 11:02:28ошибку как бы обработаем, но выполнение
- 11:02:30продолжится. Это неправильно. Давайте
- 11:02:32посмотрим, мы не забыли ли тут где-то
- 11:02:33это сделать. Значит, мы, когда с вами
- 11:02:35читаем входящий запрос, мы завершаем
- 11:02:37хендлер, если была ошибка. А вот после
- 11:02:39валидации, видите, забыли. Нужно это
- 11:02:40исправлять. Пишут для того, чтобы
- 11:02:42завершить хендлер. После того, как я в
- 11:02:44HTP ответе дал ошибку, потому что
- 11:02:46дальнейшее выполнение хендлера мне не
- 11:02:48нужно. О'кей, тут я завершаю хендлер,
- 11:02:50тут завершаю. А тут завершаю, тут
- 11:02:52паника. Тут завершаю. Всё, отлично.
- 11:02:54Теперь мы, повторюсь, умеем создавать
- 11:02:57новые задачи и умеем получать информацию
- 11:02:59о уже существующих задачах. О'кей,
- 11:03:01пойдём дальше описывать следующий
- 11:03:02хендлер. Следующий хендлер у нас нужен
- 11:03:03для получения вообще всех задач. Хорошо,
- 11:03:05что это значит? Это значит, что я из
- 11:03:07входящего запроса, видите, никакой инфы
- 11:03:09я не получаю. То есть нету инфы во
- 11:03:11входящем запросе. Это значит, я не
- 11:03:12должен её вычитывать. Это значит, я могу
- 11:03:14сразу пойти в todду list и сделать list
- 11:03:17tasks. То есть вот все задачи я хочу
- 11:03:19получить. Положу это в переменную tasks
- 11:03:21и потом захочу преобразовать в gon, то
- 11:03:23есть gson то marshallent tasks преfix
- 11:03:27никакой. Отступ у меня четыре пробела. 1
- 11:03:292 3 4. Ну, соответственно, буду слай
- 11:03:31получать и какую-то ошибку. Ошибка у нас
- 11:03:33опять же не будет обрабатываться. Это не
- 11:03:36значит, что всегда не нужно обрабатывать
- 11:03:38ошибку при преобразовании какой-то
- 11:03:39структуры в GSON. Но в нашем примере я
- 11:03:41уже объяснял, почему я смысла в этом не
- 11:03:43вижу. Если у нас какое-то большое крутое
- 11:03:45приложение, то, в принципе, такие ошибки
- 11:03:46тоже можно как-то уметь обрабатывать,
- 11:03:48как-то писать в какие-то файлы, чтобы
- 11:03:50потом можно было эти файлы открывать и
- 11:03:51смотреть, какие ошибки происходили,
- 11:03:53какого рода. Но это у нас тема уже
- 11:03:55следующего видео, поэтому сейчас мы вот
- 11:03:57такие вот ошибки просто в панику
- 11:03:58отправляем. О'кей, если всё хорошо, я
- 11:04:00просто беру, записываю статус код
- 11:04:01двухсотый http окей. Ну и буду
- 11:04:04записывать ответ. То есть вот наш слайс,
- 11:04:06который я получил, преобразуя задачи в
- 11:04:08gon. Тут у меня может быть какая-то
- 11:04:10ошибка.
- 11:04:12Если у меня ошибка неравна, тогда напишу
- 11:04:15об этом на экран. Failed to write http
- 11:04:19response.
- 11:04:20Вот так вот. И завершаю наш хендлер. По
- 11:04:22сути, всё. Мы с вами описали хендлер для
- 11:04:24получения всех задач. Далее у нас идёт
- 11:04:25хендлер для получения всех невыполненных
- 11:04:27задач. И что самое интересное, хоть этот
- 11:04:29хендлер у нас и содержит
- 11:04:30query-параметры, которые мы по сути
- 11:04:32должны были бы вычитывать и смотреть на
- 11:04:33значение различных переменных, но
- 11:04:35благодаря вот этому роутеру из
- 11:04:37библиотеки Gorilla Mux, что если нам в
- 11:04:40handler get all uncompleted tasks пришёл
- 11:04:43запрос, это значит, что query параметры
- 11:04:44были completed равно true. Значит, нам
- 11:04:46их даже не нужно вычитывать как-то
- 11:04:48дополнительно проверять. Поэтому тут
- 11:04:49всё, что я буду делать - это получать
- 11:04:52uncompleted tasks. Это у меня h тоt tod
- 11:04:55list тоlist not completed tasks. Ну вот,
- 11:04:58видите, опять я в ней промахнулся. Тут я
- 11:05:00назвал это not completed, а тут я назвал
- 11:05:02uncompleted. Хорошо бы всё-таки
- 11:05:03придерживаться какого-то единого
- 11:05:05нейминга. Ну давайте я это и сделаю. То
- 11:05:07есть тут у меня это uncompleted tasks.
- 11:05:10Unclleted tasks. И это тоже у меня
- 11:05:13uncomed tasks. Окей.
- 11:05:16list uncompleted tasks. Ну и дальше, в
- 11:05:18принципе, я даже могу скопировать этот
- 11:05:20код, потому что ничего другого тут не
- 11:05:22будет. Вставляю. Только тут у меня уже
- 11:05:24не tasks, а uncleed tasks. И дальше, ну,
- 11:05:27всё просто один в один. Хорошо, пойдём
- 11:05:29дальше. Дальше у нас хендлер, который
- 11:05:30нужен для того, чтобы выполнять какую-то
- 11:05:32задачу. И мы смотрим, что нам заголовок
- 11:05:33этой задачи будет передан в паттерне в
- 11:05:35параметре, которые мы сможем благодаря
- 11:05:37Gorill библиотеки прочитать. А также ещё
- 11:05:39в Джисоне, в теле запроса нам будет
- 11:05:41передано поле, которое нам нужно
- 11:05:42поменять у нашей задачи. О'кей,
- 11:05:44получается, нам нужна ещё одна новая
- 11:05:46ДТшка. Назову её Type Complete task dto
- 11:05:51всё, что она будет иметь, это поле
- 11:05:53complete и булевское значение. Ну,
- 11:05:55получается, я по сети просто буду ждать
- 11:05:56Jon следующего формата. Здесь вот у меня
- 11:05:59будет complete и через двоеточа true.
- 11:06:01Всё. А давайте подумаем, что будет, если
- 11:06:03тут будет передано флSE. Ну, мы можем, в
- 11:06:05принципе, ничего не делать, но почему бы
- 11:06:07нам не поддержать функциональность
- 11:06:09нашего тудулиста, что мы помимо того,
- 11:06:11чтобы отмечать задачи как выполнены,
- 11:06:13можем ещё и отменять это отмечание. Ну,
- 11:06:16знаете, бывает такое, что в списке дел
- 11:06:18какую-то задачку отметили как
- 11:06:19выполненную, а потом оказалось, что она
- 11:06:21на самом-то деле не до конца выполнена,
- 11:06:22и хочется её обратно вернуть в исходное
- 11:06:24положение. Так, давайте подержим эту
- 11:06:25функциональность. То есть, если нам
- 11:06:27complete будет false приходить, это
- 11:06:28значит, нам нужно опять вернуть нашу
- 11:06:30задачу из состояния выполнено в
- 11:06:33состояние не выполнено. Ну о'кей,
- 11:06:34описали ДТошку, давайте начнём её в
- 11:06:36наших хендлерах принимать. Json, New
- 11:06:39Decoder, R. Body, DecOD. Ну и тут нужно
- 11:06:42нам объявить нашу complete DTO. Это вот
- 11:06:45наше complete TAS DTO. И передавать её
- 11:06:48по ссылке. Complete DTO. Если какая-то
- 11:06:50ошибка у меня была, значит, клиент,
- 11:06:52скорее всего, что-то там точно не так
- 11:06:53сделал. В своём запросе создаю EDTO. Это
- 11:06:56у меня error dto message у меня это вот
- 11:06:59моя ошибочка и time - это time now.
- 11:07:02О'кей. Теперь я могу начать это
- 11:07:03записывать. http.
- 11:07:06Response writer передаю.
- 11:07:09String и status cд у меня http bedre
- 11:07:11request. Всё, завершаю handler. О'кей.
- 11:07:14Теперь я хочу прочитать сам заголовок
- 11:07:16задачи. У меня title, это у меня mк.
- 11:07:20Передаю туда request с индексом title.
- 11:07:23Вот так вот. Всё. Ну а теперь я буду
- 11:07:24смотреть, что если у меня completто то
- 11:07:26complete, это значит, что кто-то хочет
- 11:07:28выполнить какую-то задачу, я буду
- 11:07:30обращаться к toдулисту h тоt tod list.
- 11:07:34Task и передавать туда title. Тут у меня
- 11:07:36может быть ошибка, я её буду, конечно
- 11:07:38же, получать. Если она у меня не равно
- 11:07:40нил, тогда я буду ещё тоже дополнительно
- 11:07:42смотреть, что за ошибка. Task not found
- 11:07:44и либо ещё какая-то другая непонятная.
- 11:07:46Если у меня errors is запятая tod,
- 11:07:52в таком случае я буду возвращать как
- 11:07:54обычно HTTP status код 404 not found,
- 11:07:57иначе пятисотый статус код. До этого я
- 11:07:58объявлю DTO, это у меня eror dto message
- 11:08:03- это наша ошибочка и time - это текущее
- 11:08:06время. Всё. Если у меня task not found,
- 11:08:08это значит, что http то error response
- 11:08:11writer dto string http not found. Всё,
- 11:08:16значит, кто-то захотел выполнить или
- 11:08:18наоборот отменить выполнение задачи,
- 11:08:20которой не существует. Иначе то же
- 11:08:22самое, но internal server error. Вот так
- 11:08:25вот. и завершаю этот самый хендлер.
- 11:08:27Иначе, то есть, если у меня не complete,
- 11:08:29то есть кто-то хочет обратно вернуть
- 11:08:30задачу из состояния выполнено в
- 11:08:32невыполнено, тогда я должен в туду листе
- 11:08:34добавить этот самый метод, который
- 11:08:36позволит мне отменять выполнение
- 11:08:38каких-то задач. Вот у меня есть complete
- 11:08:39task, а будет ещё метод
- 11:08:42uncomete task.
- 11:08:45Принимаю какой-то заголовок задачи,
- 11:08:47которую я хочу перевести обратно в
- 11:08:48состояние не выполнено. буду эту самую
- 11:08:50задачу получать по переданному заголовку
- 11:08:53и смотреть, что если у меня этой задачи
- 11:08:55нету, значит, кто-то хочет отменить
- 11:08:56выполнение вообще несозданной задачи,
- 11:08:59значит, task not found. Иначе по
- 11:09:01аналогии сkete я должен вызвать что-то
- 11:09:03типа task uncomete, но у меня такого
- 11:09:05метода нету. Я добавлю его для нашей
- 11:09:07задачи. Funk task un compete. Что он
- 11:09:12будет делать? Он будет t completed
- 11:09:14переводить false и t completed ставить.
- 11:09:18Всё. То есть задача теперь опять в
- 11:09:19исходном положении, она не выполнена.
- 11:09:21Хорошо. И тут я буду вызывать точка
- 11:09:23uncomete. После этого перезаписывать
- 11:09:25новую задачу. L tasks title равно task.
- 11:09:29Ну и всё. И return new, то есть ошибки
- 11:09:30не было. Всё, описал новый метод. Теперь
- 11:09:32могу его использовать в хендлере. В
- 11:09:33принципе, тут вообще то же самое всё
- 11:09:35будет ровно один в один, только тут не
- 11:09:36complete task, а uncleete task. Всё,
- 11:09:38весь остальной код у меня остаётся тот
- 11:09:40же самый. Но тот факт, что у нас большие
- 11:09:42куски кода отличаются лишь вызовом одной
- 11:09:44функции, даёт нам повод задуматься о
- 11:09:46том, можно ли вынести в какую-то
- 11:09:47дополнительную отдельную функцию
- 11:09:49какую-нибудь функциональность. Например,
- 11:09:51функциональность по обработке некоторых
- 11:09:52ошибок. Потому что, если посмотреть, мы
- 11:09:54очень угогде руками создаём РДТО, потом
- 11:09:56проверяем, что это за ошибка, в
- 11:09:58соответствии с этой ошибкой даём
- 11:09:59какой-то ответ. И этот код, он абсолютно
- 11:10:01одинаковый, причём не только здесь, ещё
- 11:10:02и в других местах. Например, вот handle
- 11:10:04getask. И тут, ну, в принципе, то же
- 11:10:06самое абсолютно. Возможно, что-то стоит
- 11:10:08вынести в какую-то отдельную функцию, но
- 11:10:10это я вам просто как пищу для
- 11:10:11размышлений закидываю. Там вы, если уже
- 11:10:13захотите что-то вынести в отдельную
- 11:10:14функцию, вынесите. Не захотите, значит,
- 11:10:16не вынесите. Да. И всё, мы с вами
- 11:10:17описали этот хендлер. Осталось перейти к
- 11:10:19последнему хендлеру, который у нас нужен
- 11:10:21для того, чтобы задачу удалить. Тут у
- 11:10:22нас дополнительная информация, она в
- 11:10:23паттерне передаётся, я её буду получать.
- 11:10:26Title - это mux. Wars. Передаю request.
- 11:10:30И тут у меня title. Всё. Теперь я
- 11:10:32обращаюсь к туду листу и пробую удалить
- 11:10:35какую-то задачу по переданному
- 11:10:36заголовку. У меня delete task, если
- 11:10:38задача не найдена, будет возвращать
- 11:10:40notфа. То есть мы пытаемся удалить
- 11:10:41несуществующую задачу. Что если ошибочка
- 11:10:43у меня какая-то случилась,
- 11:10:46ну тут опять же вот этот вот код можно
- 11:10:47спокойно скопировать.
- 11:10:49Создаю и потом смотрю task not found.
- 11:10:52Тогда not found status cod, иначе
- 11:10:53internal server error. Ну и завершаем
- 11:10:55этот наш самый хенлер. О'кей, всё,
- 11:10:57казалось бы, круто. То есть, ну, типа,
- 11:10:59вау, мы с вами описали все хендлеры. Они
- 11:11:01даже как-то работают студолистом. Вроде
- 11:11:03всё правильно, всё круто, но мы с вами
- 11:11:05опять забыли про нашу самую нелюбимую
- 11:11:08гонку данных. Это же у нас HTTP
- 11:11:10хендлеры, а значит, всё взаимодействие с
- 11:11:12нашим ту-дулистом, то есть с нашими
- 11:11:13разделяемыми ресурсами, а у нас тут мы
- 11:11:15видим, что разделяемые ресурсы между
- 11:11:17всеми хендлерами - это тудулист, что
- 11:11:18работа с нашими разделяемыми ресурсами у
- 11:11:20нас идёт конкурентно. Значит, опять мы
- 11:11:22можем с вами напороться в нашем
- 11:11:24тудулисте на гонку данных. Что с этим
- 11:11:26можно сделать? Можно впихнуть MuteК.
- 11:11:28Mutкс можно впихнуть либо в сами
- 11:11:29хендлеры, но я не хочу, знаете, типа в
- 11:11:31начале каждого хендлера лочить Mutкс и в
- 11:11:33конце каждого хендлера этот самый
- 11:11:34мьютекс отпускать. Тогда я потеряю
- 11:11:36вообще преимущество того, что у меня
- 11:11:37каждый хендлер запущен в отдельной
- 11:11:39грутине. Я лучше на уровне туду листа
- 11:11:41добавлю Mutкс
- 11:11:43и не простой Mutx, а RV Muttex, потому
- 11:11:45что у нас тут есть как нагрузка на
- 11:11:47запись, так и нагрузка на чтение. Притом
- 11:11:49нагрузка на чтение может быть довольно
- 11:11:50большая. У нас у нас несколько хендлеров
- 11:11:52для чтения предназначены. Ну и даже
- 11:11:54представляем с точки зрения сценария
- 11:11:56использования. Когда клиент пользуется
- 11:11:58нашим тудулистом, он вот будет открывать
- 11:11:59тудулист очень часто, а добавлять
- 11:12:01какие-то задачи новые не так часто.
- 11:12:03Поэтому нагрузка на чтение у нас
- 11:12:04довольно большая. Значит, rmexст тут нам
- 11:12:06хорошо подойдёт. Ну поехали сверху вниз,
- 11:12:09разберём отдельно каждый метод структуры
- 11:12:11listст и посмотрим, как же нам наилучшим
- 11:12:13образом в том или ином методе
- 11:12:14заиспользовать наш RVM Mutex для того,
- 11:12:16чтобы избежать гонки данных. Смотрим, у
- 11:12:18нас есть метод add task. Тут что
- 11:12:20происходит? происходит чтение из мапы
- 11:12:22Tasks. Мапа Tasks - это поле структуры
- 11:12:25List. Если это поле структуры List,
- 11:12:27значит, логично предположить, что это
- 11:12:29самое поле будет использоваться не
- 11:12:30только лишь в одном методе, но ещё и в
- 11:12:32других. И действительно, если мы
- 11:12:34приглядимся, то мы увидим, что эта мапа
- 11:12:35Tasks, она используется у нас абсолютно
- 11:12:37в каждом методе туду листа. А это
- 11:12:38значит, что мапа Tasks - это разделяемый
- 11:12:41вот этими самыми методами ресурс. Что
- 11:12:43значит разделяемый ресурс? Это значит,
- 11:12:44что этот самый ресурс, эту самую мапу
- 11:12:46используют разные методы. Это значит,
- 11:12:49что если в разных рутинах будут вызваны
- 11:12:51эти самые методы, то у нас случится
- 11:12:53конкурентный доступ к этой самой мапе. А
- 11:12:55конкурентный доступ мы с вами всегда
- 11:12:56должны регулировать. А значит, мы
- 11:12:58понимаем, что у нас тут основная
- 11:12:59проблема в том, чтобы зарегулировать
- 11:13:01конкурентный доступ к мапе Tasks.
- 11:13:03Хорошо, смотрим метод add tas Tasks. У
- 11:13:04нас тут сначала идёт чтение из мапы, то
- 11:13:06есть проверка, есть ли уже задача с
- 11:13:08таким заголовком, который хочет добавить
- 11:13:10наш пользователь. Если такая задача уже
- 11:13:12есть, получается, мы возвращаем ошибку,
- 11:13:14что такая задача уже есть, потому что у
- 11:13:16нас не может быть две задачи с одним
- 11:13:18заголовком. Иначе, если у нас такой
- 11:13:20задачи ещё нет, мы её добавляем
- 11:13:21спокойно. Хорошо? То есть, во-первых,
- 11:13:22сразу же видно, что у нас тут происходит
- 11:13:23чтение из разделяемого ресурса и запись
- 11:13:25в разделяемый ресурс. Пока что на таком
- 11:13:27уровне рассмотрения мы можем понять, что
- 11:13:29нам нужно заизолировать чтение из
- 11:13:30разделяемого ресурса и заизолировать
- 11:13:32запись в разделяемый ресурс. Мы это
- 11:13:34умеем делать. То есть мы можем тут взять
- 11:13:36вот так вот meex. Сначала взять
- 11:13:37блокировку на чтение, потом тут
- 11:13:38отпустить эту самую блокировку и тут
- 11:13:40отпустить эту самую блокировку. Почему в
- 11:13:43двух местах я отпускаю блокировку? Да
- 11:13:44потому что значение переменной ОК у нас
- 11:13:46может быть как true, так и false. И то
- 11:13:48есть у меня программа после
- 11:13:49восемнадцатой строчки может пойти
- 11:13:51выполняться либо в тело условного
- 11:13:52ветвления, либо дальше по методу Adasks.
- 11:13:55И мне в любом случае, независимо от
- 11:13:56того, куда пойдёт выполнение программы,
- 11:13:58нужно отпустить блокировку на Мютексе
- 11:13:59перед завершением метода. Потому что,
- 11:14:01если я возьму один раз блокировку на Mex
- 11:14:02и не отпущу её, тогда наш Mutкс просто
- 11:14:04сломается и уже не позволит нам
- 11:14:06корректно работать с нашими методами.
- 11:14:08Поэтому я обязан отпускать блокировку
- 11:14:09независимо от того, какое значение
- 11:14:10переменной ОК у меня будет в условии.
- 11:14:13Хорошо, мы заизолировали конкурентное
- 11:14:15чтение из нашей мапы. Дальше у нас
- 11:14:17запись в мапу. Получается, мы должны
- 11:14:18заизолировать конкурентную запись в эту
- 11:14:20мапу. Поэтому берём блокировку на
- 11:14:21запись. LК и unlock. Хорошо, брали
- 11:14:25блокировки на чтение, там, где мы
- 11:14:26читали, брали блокировки на запись, там,
- 11:14:28где мы писали. Ну, казалось бы, мы
- 11:14:29молодцы. зарегулировали конкурентный
- 11:14:31доступ к нашей мапе в нашем методе
- 11:14:33Tasks. Но тут всё не так просто. Есть
- 11:14:35одна очень такая большая проблема,
- 11:14:36которая у нас в этом методе возникает.
- 11:14:38На самом деле, кто-то мог уже догадаться
- 11:14:40до этого, но сразу скажу, тут
- 11:14:41действительно сложно это заметить,
- 11:14:43особенно когда нету большого опыта в
- 11:14:45написании подобных вещей. Значит, какая
- 11:14:47тут проблема? Если помните, мы с вами
- 11:14:48писали HTTP сервер, у которого было два
- 11:14:50хендлера: Payхandler и Save Handler. И
- 11:14:53там у нас на каком-то этапе разработки
- 11:14:55была такая проблема, что у нас
- 11:14:57Payхandler и Safehхнler проводили
- 11:14:58проверки, можно ли провести оплату
- 11:15:00независимо друг от друга. И у нас мог
- 11:15:02произойти такой сценарий, что две
- 11:15:04горутины одновременно в Pay и в
- 11:15:06Сейхендлере проводят проверку, можно ли
- 11:15:08провести оплату. Одновременно получают
- 11:15:10тру, что оплату провести можно, и эту
- 11:15:12самую оплату проводят. Тем самым
- 11:15:14получается двойное списание, из-за чего
- 11:15:16мы могли уйти в минус. Тут ситуация
- 11:15:18очень похожая. То есть у нас какое
- 11:15:19правило, что мы не можем добавлять две
- 11:15:21задачи с одинаковым заголовком. И тут
- 11:15:22представим, что дверутины одновременно
- 11:15:24вызывают метод add task. И у одной
- 11:15:26рутины задача с заголовком домашка и
- 11:15:28описанием X. У второй рутины тоже задача
- 11:15:30с заголовком домажка, но описание Y. Эти
- 11:15:33две гарутины одновременно начинают
- 11:15:34проходить проверку, можно ли добавить
- 11:15:36задачу в нашу мапу. И обе узнают, что на
- 11:15:38момент проверки в мапе нету задачи с
- 11:15:40заголовком домажка. Тем самым получают
- 11:15:42разрешение на запись задачи в мапу.
- 11:15:45Перваяна запишет задачу с заголовком
- 11:15:47домажка и описанием X. После неё вторая
- 11:15:49рутина тоже сразу же запишет задачу с
- 11:15:52заголовком Домажка и с описанием Y. В
- 11:15:54итоге, что случится? Правильно, задача с
- 11:15:57заголовком домажка и описанием X у нас
- 11:15:59перезапишется и просто потеряется. Чем
- 11:16:02это вызвано? Вызвано это тем, что у нас
- 11:16:04составная операция, у нас операция по
- 11:16:06добавлению задачи состоит из двух
- 11:16:07пунктов: проверить, можно ли задачу
- 11:16:09добавить, и, собственно, само добавление
- 11:16:11задачи. И эти самые два пункта нашей
- 11:16:13составной операции могут выполняться
- 11:16:15независимо несколькими разными
- 11:16:17грутинами. Это для нас проблема. Решение
- 11:16:19этой проблемы ровно такое же, каким
- 11:16:21решение было проблемы в случае с payй и
- 11:16:23сейхендлером. То есть мы должны
- 11:16:25гарантировать, что если какая-то рутина
- 11:16:27начала в двадцатой строчке проверять,
- 11:16:28можно ли добавить новую задачу, то на
- 11:16:30момент достижения двадцать седьмой
- 11:16:32строчки состояние вот этой мапы, оно не
- 11:16:34изменилось. То есть мы должны
- 11:16:35гарантировать, что если в двадцатой
- 11:16:37строчке можно добавлять задачу, то и в
- 11:16:39двадцать седьмой тоже можно добавлять
- 11:16:40задачу. Это значит, что я, в принципе,
- 11:16:42должен всем рутинам запретить выполнять
- 11:16:43метод task и другие методы, если я в
- 11:16:46какой-то одной рутине в этот момент
- 11:16:48начал добавление задачки. Если одна
- 11:16:50грутина начала добавлять задачку,
- 11:16:52значит, все остальные рутины должны
- 11:16:54полностью дождаться выполнения этой
- 11:16:56операции по добавлению задачи от и до, и
- 11:16:58только после этого уже они смогут
- 11:17:00выполнять свои операции. А это значит,
- 11:17:02что мне тут полноценную блокировку на
- 11:17:03Мютексе, которая, в принципе, исключает
- 11:17:05возможность взятия этой самой блокировки
- 11:17:07больше, чем одной рутиный, я эту
- 11:17:09блокировку должен растянуть на весь
- 11:17:10метод task. Получается, мне тут в
- 11:17:12принципе не подойдёт взятие блокировки
- 11:17:13на чтение. Мне нужно полностью
- 11:17:16заблокировать весь метод add task.
- 11:17:22Теперь, если какая-то рутина вызывает
- 11:17:24task, она возьмёт блокировку на
- 11:17:26бьютексе. Если в это же самое время
- 11:17:27какая-то другая грутина тоже вызовет
- 11:17:29метод tasк, эта вторая грутина попробует
- 11:17:31взять блокировку на Мютексе, но не
- 11:17:32сможет, потому что блокировка уже взята.
- 11:17:34И пока первая грутина до конца не
- 11:17:35доработает всю операцию добавления
- 11:17:37задачи, вторая рутина не начнёт
- 11:17:38выполнять этот кусок кода. А значит, мы
- 11:17:40избежали выше описанной проблемы. Ну и
- 11:17:41тут ещё осталась такая небольшая
- 11:17:43проблемка, что мы в двух местах Mutкс
- 11:17:44разблокируем. А если у нас этот метод
- 11:17:46будет более сложный, если у нас какие-то
- 11:17:48циклы будут ещё более сложные условные
- 11:17:49ветвления и так далее, и так далее, и
- 11:17:50так далее, нам уже глазами уследить за
- 11:17:52каждым местом, где мы будем те
- 11:17:54разблокировать, довольно сложно. И в то
- 11:17:55же время очень просто будет забыть
- 11:17:58написать где-то разблокировку Мьютокса.
- 11:17:59Ну вот, например, в теле условного
- 11:18:00ветвления мы с вами забыли бы
- 11:18:01разблокировать. Получается, мы при
- 11:18:03вызове метода Task взяли бы блокировку.
- 11:18:05Допустим, у нас переменная ОК была бы
- 11:18:07true, мы бы закончили выполнение нашего
- 11:18:09метода и всё, и Muttex уже никогда не
- 11:18:10разблокируется. А если Mutx никогда
- 11:18:12больше не разблокируется, значит, никто
- 11:18:13больше не сможет эту самую блокировку
- 11:18:15взять, а значит у нас, в принципе,
- 11:18:16больше невозможно становится
- 11:18:18взаимодействие с нашим тудолистом. Это
- 11:18:19неправильно. Поэтому мы должны следить
- 11:18:21за тем, чтобы везде отпускать
- 11:18:23блокировку, независимо от того, в каком
- 11:18:24месте наш метод завершается. Но это,
- 11:18:26повторюсь, не очень удобно вот так вот
- 11:18:27следить, где, в каких местах мы должны
- 11:18:28отпускать эту самую блокировку. Гораздо
- 11:18:30удобнее нам было бы каким-то непонятным
- 11:18:33образом попросить Geng отпускать
- 11:18:35блокировку на Мютексе всегда, когда
- 11:18:37завершается метод add task. Неважно, где
- 11:18:39бы он завершился. Ну, у нас, к счастью,
- 11:18:41такая возможность есть. Называется она
- 11:18:42def. И именно по выше описанным причинам
- 11:18:44я отпускать блокировку буду через defe.
- 11:18:47И теперь мне уже не нужно следить
- 11:18:48глазами, где именно наш метод task
- 11:18:50завершается. Просто в любом случае, если
- 11:18:52этот метод завершится, блокировка
- 11:18:53отпустится. Хорошо, мы с вами
- 11:18:55наконец-таки разобрали, почему именно
- 11:18:56так не иначе. Я беру блокировку на
- 11:18:58Мютексе в методе Task. Поехали дальше.
- 11:19:00Метод get task. Что тут происходит? Тут
- 11:19:01у нас разделяемый ресурс читается, то
- 11:19:03есть происходит чтение из мапы и больше
- 11:19:05ничего. Но, в принципе, мы тут уже можем
- 11:19:07спокойно ограничиться блокировкой на
- 11:19:08чтение. Airlock. И точно также в Defоре
- 11:19:10я эту самую блокировку на чтение буду
- 11:19:12отпускать. Отлично. Идём дальше. List
- 11:19:15tasks. Значит, у нас тут идёт
- 11:19:16взаимодействие с двумя мапами. Мапа
- 11:19:18Tasks, которая является полем нашего
- 11:19:20туду листа, она подвергается только
- 11:19:22чтению. Тут мы читаем её размер, а тут
- 11:19:24мы читаем её элементы. Но у нас есть ещё
- 11:19:26одна мапа ТМП. И вот мапа ТМП уже
- 11:19:28подвергается записи. Но для нас эта
- 11:19:30запись не критична. Почему? Потому что
- 11:19:31даже если несколько рутин одновременно
- 11:19:33вызовут метод list tasks, то для каждой
- 11:19:36рутины вот эта мапа ТМП будет создана
- 11:19:38своя, потому что область видимости мапы
- 11:19:40ТМП ограничена методом list tas Tasks.
- 11:19:42Поэтому мы не можем говорить, что мапа
- 11:19:44ТМП является каким-то разделяемым
- 11:19:45ресурсом, конкурентный доступ к которому
- 11:19:47нужно регулировать. Ну а вот
- 11:19:49конкурентный доступ к мапе Tasks мы
- 11:19:50регулировать должны, но у нас в методе
- 11:19:52list tas Tasks этот доступ
- 11:19:53ограничивается только чтением, поэтому
- 11:19:55нам тут полностью хватит взятия
- 11:19:57блокировки на чтение. Rlock и через def
- 11:20:01unlock. List uncompleted tasks.
- 11:20:03Абсолютно то же самое, не буду
- 11:20:05повторяться, абсолютно тоже самое, что и
- 11:20:07в случае с методом list tasks. Нам тут
- 11:20:09абсолютно хватит блокировки на чтение.
- 11:20:13Отлично. Идём дальше. Complete task.
- 11:20:15Значит, у нас тут есть чтение из мапы
- 11:20:17tasks и запись в эту самую мапу. Ну, не
- 11:20:19будем повторять наших предыдущих ошибок.
- 11:20:21В принципе, тут абсолютно такая же была
- 11:20:23бы ошибка, если бы мы пытались во время
- 11:20:24чтения взять блокировку на чтение и
- 11:20:26отдельно во время записи взять
- 11:20:27блокировку на запись, потому что мы бы с
- 11:20:29вами могли вот в этом условии проверить,
- 11:20:31есть ли у нас задача. У нас задача бы
- 11:20:33оказалась действительно, но в этот же
- 11:20:35самый момент в какой-то другой гарутине
- 11:20:36был бы вызван метод delete, и у нас
- 11:20:38оказалось бы, что на момент наступления
- 11:20:40семьдесят седьмой строчки ранее
- 11:20:41существующая задача больше не
- 11:20:43существует. А мы вот этой записью эту
- 11:20:45самую задачу обратно бы вернули. Ну и
- 11:20:46пользователь бы не понял, типа я удалил
- 11:20:48вроде задачу, она у меня опять
- 11:20:49перезаписалась. Довольно странно.
- 11:20:51Поэтому я тут тоже буду брать
- 11:20:52полноценную блокировку полностью на весь
- 11:20:55этот метод. И в diffфёре я буду
- 11:20:57разблокировать этот самый mee uncomete
- 11:21:00task один в один, что и complete task,
- 11:21:02поэтому не буду повторяться, просто беру
- 11:21:04полноценную блокировку на mute и потом в
- 11:21:06дефре эту самую блокировку я отпускаю. И
- 11:21:08остаётся у нас метод delete task. Тут у
- 11:21:10нас происходит чтение из нашей мапы и
- 11:21:12потом идёт удаление элемента из мапы. На
- 11:21:14самом деле удаление элемента из мапы -
- 11:21:15это тоже изменение значения переменной.
- 11:21:18У нас вот переменная tasks - это мапа. И
- 11:21:20мы, удаляя значение из мапы, меняем, по
- 11:21:22сути, состояние этой самой мапы. А
- 11:21:24изменение состояния разделяемого ресурса
- 11:21:26приравнивается к записи в этот самый
- 11:21:27разделяемый ресурс. Поэтому нет разницы,
- 11:21:29пишем мы в этот разделяемый ресурс или
- 11:21:30удаляем какие-то значения из этого
- 11:21:32разделяемого ресурса. Мы этот ресурс
- 11:21:34меняем. Поэтому мы тут должны тоже взять
- 11:21:36полноценную блокировку. Блокировкой на
- 11:21:38чтение мы тут с вами уже не отделаемся.
- 11:21:40Ну и, в принципе, чтобы себя тоже не
- 11:21:41подвергать опасности вот этой вот
- 11:21:42многосоставной операции, что мы сначала
- 11:21:44проверили, можно ли удалить задачу,
- 11:21:46потом её удалили. Тут тоже, если очень
- 11:21:48хорошо подумать, может быть проблема с
- 11:21:50тем, что у нас проверка на существование
- 11:21:52задачи и фактическое удаление задачи -
- 11:21:55это две отдельные операции, между
- 11:21:56которыми может ещё какая-нибудь третья
- 11:21:58операция из другой горутины встрять.
- 11:22:00Поэтому для нас просто сигналом служит
- 11:22:01то, что идёт изменение разделяемого
- 11:22:03ресурса, а значит, мы должны брать
- 11:22:04полноценную блокировку на нашем Ютексе.
- 11:22:08Всё. Таким образом, мы с вами благодаря
- 11:22:10тому, что заиспользовали в методах
- 11:22:12нашего тудолиста Mutex, мы конкурентный
- 11:22:15доступ к разделяемому ресурсу, а именно
- 11:22:17к мапе Tasks сделали безопасным. Тем
- 11:22:19самым мы с вами избежали гонки данных и
- 11:22:21можем спокойно использовать Tod-дулиist
- 11:22:23в конкурентных приложениях. Ну а наше RP
- 11:22:25приложение является конкурентным, потому
- 11:22:27что на каждый HTTP запрос запускается
- 11:22:28дополнительная рутина. Значит, когда мы
- 11:22:30в разных методах с вами работаем с одним
- 11:22:32и тем же тудолистом, то у нас происходит
- 11:22:34этот самый конкурентный доступ. И мы
- 11:22:35этот конкурентный доступ Мьютексом
- 11:22:37зарегулировали. Причём взяли не обычный
- 11:22:39MEx, а R MEEX. Благодаря этому, в случае
- 11:22:41возникновения большой читающей нагрузки
- 11:22:43на наше приложение мы не будем проседать
- 11:22:45по производительности. Описали наши
- 11:22:47хендлеры, и теперь нам осталось с вами
- 11:22:48просто это всё проинициализировать в мей
- 11:22:50и запустить. Давайте это сделаем. То
- 11:22:52есть я тут напишу сначала, что мне нужно
- 11:22:53- это создать todду list. todo. New
- 11:22:56list. Хорошо. Потом мне нужно HTTP
- 11:22:58хендлеры создать. HTTP handlers http.
- 11:23:01Только нам нужно выбрать не NETET HTTP,
- 11:23:03а наш с вами пакет HTTP. На самом деле
- 11:23:05хорошо бы наш с вами пакет по-другому
- 11:23:06назвать, потому что он конфликтует с
- 11:23:08названием оригинального пакета, но не
- 11:23:11критично. Ладно, http смотрю, чтобы
- 11:23:13именно Restp HTTP выбралось. Ну вот
- 11:23:15опять же смотрю, чтобы у меня тут не
- 11:23:16HTTP было, а моё restpt и тут new HTTP
- 11:23:20handlers. Эти хендлеры у меня принимают
- 11:23:22todдули list. Хорошо. После этого HTTP
- 11:23:23сервер. Это HTTP new HTTP сервер. И он
- 11:23:27уже принимает наши хенделеры. О'кей.
- 11:23:29Теперь нам осталось HTTP сервер
- 11:23:30запустить. Старт сервер. Видим, старт
- 11:23:33сервер у нас возвращает ошибочку. Ну и
- 11:23:34давайте мы будем её обрабатывать. Что,
- 11:23:36если у нас какая-то ошибка случилась, в
- 11:23:38таком случае я выведу на экран failed to
- 11:23:41start http сервер. И тут самая ошибка.
- 11:23:44Тут есть ещё один момент, который я
- 11:23:45всегда пропускал, чтобы нам с вами не
- 11:23:47забивать голову. А именно этот момент
- 11:23:49заключается в следующем. У нас функция
- 11:23:50listen and surf абсолютно всегда будет
- 11:23:52возвращать ошибку. И это написано в
- 11:23:54описании метода. Listen and surf всегда
- 11:23:56возвращает not error, что значит, что
- 11:23:58всегда есть ошибка. Но как так? А всё
- 11:24:00дело в том, что даже если мы с вами
- 11:24:02захотим корректно завершить наш
- 11:24:03HTTP-сервер, у нас есть для этого
- 11:24:05инструменты, но мы их пока не проходили,
- 11:24:07всё равно нам будет возвращена ошибка.
- 11:24:08Но ошибка нам будет возвращена вот эта
- 11:24:10вот y server closed. Тут написано, что
- 11:24:13эта ошибка возвращается после того, как
- 11:24:14мы вызвали метод shutdown или метод
- 11:24:16close, и она не является чем-то
- 11:24:18критичным. Поэтому обычно, когда
- 11:24:20запускают HTTP сервер через listen surf,
- 11:24:22делают следующий хак. получают ошибку и
- 11:24:25смотрят, что если erers из вот эта вот
- 11:24:28самая ошибка - это HTTP server closed, в
- 11:24:32таком случае возвращается, то есть это
- 11:24:33как бы не считается за какую-то прямо
- 11:24:35страшную ошибку, иначе уже возвращается
- 11:24:37та самая ошибка. Ну если ошибок не было,
- 11:24:39тогда возвращается Нил. Таким вот
- 11:24:40образом. Да, это странно. Это ещё одна
- 11:24:42странность гонган, но вот так вот оно
- 11:24:44сделано. Что-то менять в этом месте
- 11:24:46вроде как никто не собирается. Поэтому
- 11:24:47нам просто помнить нужно, что listen and
- 11:24:49surf всегда возвращает ошибку. Но если
- 11:24:51это ошибка север, это означает, что
- 11:24:53просто корректно у нас завершился наш
- 11:24:55сервер. Да и всё. Хорошо. Я могу нас с
- 11:24:58вами поздравить. Мы написали: "Ничего
- 11:25:00себе rest приложение. Нам теперь нужно
- 11:25:02его протестировать". Давайте это
- 11:25:03сделаем. Ну, поехали по старинке. У меня
- 11:25:06на пол экрана приложение, на пол экрана
- 11:25:07Postman. Запускаю наше приложение.
- 11:25:10Запустил. И поехали отправлять первые
- 11:25:12запросики. Я буду просто идти по
- 11:25:14хендлерам, смотреть, какой у нас
- 11:25:15контракт, потому что тут мы с вами,
- 11:25:16напоминаю, описали контракт для наших
- 11:25:18хендлеров и отправлять запросы. Потом
- 11:25:20буду смотреть, что у меня в ответе.
- 11:25:21О'кей, поехали. Local host 901
- 11:25:25порт/tasks.
- 11:25:26Смотрим с нод get, поэтому у нас
- 11:25:30вызовется не этот хендлер, а вызовет у
- 11:25:33нас вот этот вотхендлер. Хендлер,
- 11:25:35который нам возвращает вообще все
- 11:25:36задачи, которые есть. Ну давайте
- 11:25:38попробуем его вызвать. Что вообще он нам
- 11:25:40покажет? Отправляю запрос и видим, нам
- 11:25:42вернулся пустой джесончик. Статус код
- 11:25:44200 у нас в ответе. И пустой жесончик.
- 11:25:46Что это значит? Это значит, что у нас
- 11:25:47никаких задач ещё не было добавлено, но
- 11:25:50при этом всё отработало корректно.
- 11:25:51Хорошо, теперь давайте попробуем создать
- 11:25:53какую-то задачу. Чтобы создать задачу,
- 11:25:55нам нужно также отправить запрос на
- 11:25:56patterns/tasks. Только метод уже нужно
- 11:25:58выбрать пост. Ну давайте я это сделаю.
- 11:26:00Выбираю метод пост и отправляю запрос.
- 11:26:02Опа, я вижу 400 B request. Ага,
- 11:26:05сообщение об ошибке. Я off. Что это
- 11:26:07значит? Это значит, что не получилось
- 11:26:09прочитать тело запроса. Значит, тело
- 11:26:11запроса не было в принципе. Ну и время
- 11:26:13совершения этой самой ошибки. Вот у меня
- 11:26:1528 июля 18: 50 минут 37 секунд. Ну и тут
- 11:26:18дальше ещё более точная информация, даже
- 11:26:20часовой пояс есть плю. О'кей. Давайте
- 11:26:21теперь я создам какое-никакое тело
- 11:26:23запроса. RAF jon тут выберу. Ну и тут
- 11:26:26просто пустой Jon буду передавать.
- 11:26:28Теперь отправлю свой запрос. Отправляю
- 11:26:30опять B request. Только теперь у меня
- 11:26:32уже в месдж видно, что tйтle из empty.
- 11:26:34То есть я не передал полейт. Давайте я
- 11:26:36это сделаю. Title. И я назову задачу
- 11:26:39домашка. Всё, теперь отправляю запрос.
- 11:26:42Опять request. Только теперь desрипtion
- 11:26:45из MT, то есть мне уже моя же программа
- 11:26:46подсказывает, где я не так, что сделал.
- 11:26:48Отлично. Давайте я теперь передам
- 11:26:50desкрипtionн
- 11:26:52desрипtion. Сделать домажку по матише к
- 11:26:56четвергу. Всё. Давайте теперь отправим
- 11:26:59наш запрос и посмотрим, что будет. 2011
- 11:27:02created. У нас статус код 2011. И
- 11:27:03смотрим, нам в дсоне в ответе вернулось
- 11:27:07всё верно. Нам вернулся JON, который
- 11:27:09представляет вновь созданную задачу.
- 11:27:11Можно опять же подсветочку сделать. Тайт
- 11:27:13домажка, то есть заголовок, домажка. Всё
- 11:27:14верно. Description, то есть описание.
- 11:27:16Сделать домашку к четегу. Верно.
- 11:27:17Completed, false. Тоже всё верно. То
- 11:27:19есть задача не выполнена. Created, время
- 11:27:22создания. Completed nul. Что же такое н
- 11:27:25в дсонеone? Давайте быстренько обсудим.
- 11:27:27Ну вот такой вот тут микропримерчик есть
- 11:27:29у меня. Маленький, который состоит всего
- 11:27:31из двух полей: integer 1 и integer 2.
- 11:27:34Значение поля integer 1 - 0. Значение
- 11:27:36поля integer 2 - 0. Ну в данном случае
- 11:27:39означает, что для поля integer 2
- 11:27:41никакого значения не было представлено.
- 11:27:43Получается, когда мы в дсоне с вами
- 11:27:45видим нул, это означает, что никакого
- 11:27:47значения на этом месте у нас нету. Нул
- 11:27:49обозначает отсутствие значения. Давайте
- 11:27:51я быстренько накидаю такой показательный
- 11:27:53пример. У меня будет http.handlefunk
- 11:27:56на pattern/default.
- 11:27:59И тут я прямо на месте опишу HTTP
- 11:28:00handler. Что будет этот хенлер делать?
- 11:28:02Он будет существующую структуру, назову
- 11:28:05её Mystract,
- 11:28:07переводить в Jon и возвращать в ответе.
- 11:28:09Эта структура будет состоять из двух
- 11:28:10полей. Первое поле integer 1, типа int,
- 11:28:13и второе поле у меня integer 2, типа
- 11:28:15указательно int. Чуть-чуть поменяю
- 11:28:17форматирование, чтобы было больше видно.
- 11:28:20Вот так вот. О'кей. Теперь я внутри
- 11:28:21этого самого HTTPхлера создам экземпляр
- 11:28:24структуры Mystract. Myct - это у меня
- 11:28:26myстract. Не буду задавать никакие
- 11:28:28значения. Получается, когда вот здесь
- 11:28:29вот эта структура у меня создаётся, то
- 11:28:31каждое поле принимает значение по
- 11:28:33умолчанию. После этого я обращаюсь к
- 11:28:34пакету GSON, в нём к функции
- 11:28:36Marshallent. Передаю туда экземпляр
- 11:28:39структуры Mystract. Префикс никакой.
- 11:28:40Отступ у меня 1 2 3 4 будет. Marshallal
- 11:28:43intent у меня возвращает байты и ошибку.
- 11:28:44Ошибку я не буду получать, пока меня это
- 11:28:46не интересует. Ну и в следующей строчке
- 11:28:48я в responsс вратере запишу этот самый
- 11:28:50полученный GSON. После этого я запущу
- 11:28:52HTP сервер Lance Surf на 90 порту. В
- 11:28:56этот раз давайте. Второй аргумент мне не
- 11:28:57нужен. Ошибку даже получать никакую не
- 11:28:59буду, потому что это минимальный
- 11:29:00тестовый пример, просто чтобы
- 11:29:01продемонстрировать, что такое нл в нашем
- 11:29:03же. Давайте запущу эту нашу самую
- 11:29:05программу и буду делать запросы с
- 11:29:07постмана на local host 9090 порт в этот
- 11:29:10раз и pattern/ default. Делаю запрос. И
- 11:29:14мы видим, что в ответном Gsone я получаю
- 11:29:16два поля. Первое поле integer 1, в нём
- 11:29:18значение ноль. Второе поле integer 2, в
- 11:29:20нём значение 0. Что произошло? Я, когда
- 11:29:23в семнадцатой строчке создал экземпляр
- 11:29:24структуры Myrct и не передал в неё
- 11:29:26никакие значения при создании, то все её
- 11:29:28поля приняли значение по умолчанию.
- 11:29:30Значение по умолчанию для интера - это
- 11:29:32ноль. Значение по умолчанию для
- 11:29:33указателя на integer - это.
- 11:29:35Соответственно, из этих двух полей
- 11:29:37какое-то осмысленное значение принимало
- 11:29:39только поле integer 1. В нём лежал ноль.
- 11:29:42В поле integer 2 же лежал, что означает
- 11:29:45указатель в никуда. То есть это полное
- 11:29:47отсутствие какого-либо значения.
- 11:29:49Поэтому, когда я переводил этот самый
- 11:29:50экземпляр структуры MystractCT в GSON,
- 11:29:53то я получил на месте поле integer 2 n.
- 11:29:55То есть н - это значит отсутствие
- 11:29:56какого-то значения. Хорошо. Теперь
- 11:29:58давайте создам переменную A, положу в
- 11:29:59неё 10. Эта переменная А будет
- 11:30:01интежером. И при создании экземпляра
- 11:30:03структуры Myract я вже 2 положу
- 11:30:06указатель на А. Соответственно, тут
- 11:30:08теперь у нас уже будет какое-то
- 11:30:09значение. И давайте узнаем, как оно
- 11:30:10будет у нас в GSON реализовано.
- 11:30:11Перезапускаю программу. Ещё раз
- 11:30:13отправляю запрос. И видим, что теперь у
- 11:30:16меня на месте integer 2 появилось
- 11:30:17значение 10. У нас вer 2 при
- 11:30:19преобразовании в JSON был не указательно
- 11:30:22integer. У нас в этом поле integer 2
- 11:30:23после преобразования гошной структуры в
- 11:30:25GSON уже не указательный integer, а
- 11:30:27конкретное значение. Что это значит? О
- 11:30:30чём это она может говорить? Это она
- 11:30:31может говорить о том, что когда мы с
- 11:30:33вами преобразовываем какую-то структуру
- 11:30:34в GSON и в этой структуре у нас есть
- 11:30:37указатели в никуда, тогда эти самые поля
- 11:30:40у нас в джисоне принимают значение нул,
- 11:30:41то есть отсутствие значения. Но как
- 11:30:43только у нас этот самый указатель в
- 11:30:45структуре, который мы переводим в JSON,
- 11:30:46начинает указывать на какое-то
- 11:30:48конкретное осмысленное значение, тогда
- 11:30:50уже при переводе этой самой структуры в
- 11:30:52GSON поле этой структуры уже в джисоне
- 11:30:55будет принимать осмысленное значение той
- 11:30:57переменной, на которую этот самый
- 11:30:58указатель указывает. Это нам говорит о
- 11:31:00том, что нул - это не значит, что
- 11:31:02обязательно. Нул - это просто значит
- 11:31:05отсутствие осмысленного значения. Каким
- 11:31:07ещё образом мы тут можем добиться
- 11:31:09отсутствия какого-то осмысленного
- 11:31:10значения в полях нашей структуры?
- 11:31:12Например, если у нас будет какой-нибудь
- 11:31:13интерфейс, вот я себе создам my
- 11:31:16interfaceй, типа интерфейс. Пускай тут
- 11:31:18будет два метода, там get и, я не знаю,
- 11:31:20set. Ну, то есть неважно, это вообще
- 11:31:22какие-то случайные методы. И у нас в
- 11:31:23MySC также будет ещё одно поле
- 11:31:26интерфейс, типа My Interface. Хорошо,
- 11:31:28давайте попробуем перезапустить нашу
- 11:31:29программу, снова отправить сетевой
- 11:31:30запрос в нашу программу и посмотреть,
- 11:31:32что происходит, когда у нас в нашей
- 11:31:33структуре есть поле интерфейса, в
- 11:31:35котором ничего не лежит, и мы
- 11:31:37преобразуем это всё в gon. Отправлять
- 11:31:39запрос и вижу, что у меня поле интерфейс
- 11:31:41нул. Что это значит? Это значит, что у
- 11:31:42меня в этой структуре на месте
- 11:31:44интерфейса не лежало вообще никакого
- 11:31:46значения. Получается, опять же, мы
- 11:31:47приходим к выводу, что нул в дсоне
- 11:31:50означает отсутствие какого-либо
- 11:31:52значения. И это особенно удобно, когда у
- 11:31:54нас в структуре есть поля, которые не в
- 11:31:56каждый момент времени могут иметь
- 11:31:58какое-то осмысленное значение. Например,
- 11:32:00у нас есть структура Task, и у неё есть
- 11:32:01поле completed AD. Почему я, собственно,
- 11:32:04и сделал это поле указателем на время, а
- 11:32:06не просто временем? Потому что когда
- 11:32:07задача только создаётся, она ещё не
- 11:32:09выполнена. Потому что задача
- 11:32:11только-только создалась, она ещё не
- 11:32:12может быть выполнена. Соответственно, у
- 11:32:14задачи не может быть времени выполнения.
- 11:32:16Соответственно, когда я отдаю эту задачу
- 11:32:18в ДСoney, я хочу, чтобы на месте поля,
- 11:32:20которое отвечает за время выполнения,
- 11:32:22явно было указано, что времени
- 11:32:24выполнения никакого нету. Явно было
- 11:32:26указано отсутствие какого-либо значения.
- 11:32:29Поэтому я полете делаю указателем. Ну и
- 11:32:32потом при преобразовании этой самой
- 11:32:33структуры в GSON я в ответе на месте
- 11:32:35complet буду получать nл, что HTTP
- 11:32:37клиенту будет явно говорить, что в
- 11:32:39данный момент на месте поля complitted
- 11:32:41нет никакого осмысленного значения, то
- 11:32:43есть нету время выполнения, потому что
- 11:32:45задача ещё не выполнена. О'кей, круто.
- 11:32:47Давайте создадим ещё пару задач. Тайтл я
- 11:32:49передам завтрак. Ну и desрипtion
- 11:32:52покушать перед походом в школу.
- 11:32:56Отправляю опять 2011 created. И вот уже
- 11:32:59новая задача у меня создалась. Хорошо. И
- 11:33:00третью задачу, тайтл. Пускай у меня
- 11:33:02будет прогулка с собакой. И описание -
- 11:33:05это проснуться в 8:00 утра и погулять с
- 11:33:09Бобиком. Отправляю. Опять 101 creatтed.
- 11:33:13Всё хорошо. Вот я создал третью задачу.
- 11:33:15Теперь я могу, наверное, уже начать
- 11:33:17получать какие-то задачи. Первое, что я
- 11:33:19захочу получить - это, наверное, сразу
- 11:33:21все задачи, да? То есть я захочу
- 11:33:22получить сразу все задачи. То есть get
- 11:33:24all tasks. Для этого я должен отправить
- 11:33:25запрос на pattern tasks, только метод
- 11:33:27уже выбрать get.
- 11:33:29Выбираю метод get, убираю тело запроса и
- 11:33:31отправляю. Хопа, я получил jon,
- 11:33:35состоящий из трёх моих задач. Давайте
- 11:33:37включу подсветочку. Вот у меня раз
- 11:33:39задача, два задача, три задача. И вся
- 11:33:42информация об этих задачах у меня
- 11:33:43сохранена полностью. О'кей. Теперь
- 11:33:45давайте какую-то задачу я отмечу как
- 11:33:47выполненную. Ну, например, завтрак. Для
- 11:33:49этого что мне надо сделать? Вот handle
- 11:33:50complete task. Я должен отправить запрос
- 11:33:53на паттер/task с/ тут заголовок задачи.
- 11:33:56Ну, допустим, я вот завтрак, да, хочу
- 11:33:57отметить. Завтрак. Я копирую заголовок,
- 11:33:59вставляю сюда. Дальше метод я должен
- 11:34:01выбрать патч. Вот тут у меня написано. И
- 11:34:03ещё я должен дополнительно в теле
- 11:34:05запроса передать информацию о конкретно
- 11:34:07вот эту вот нашу complete task dto, но
- 11:34:09давайте я забуду это сделать. Отправляю
- 11:34:11запрос. Bedre request eof. То есть опять
- 11:34:14нам сигнализирует о том, что тело
- 11:34:15запроса не было прочитано. Ну хорошо,
- 11:34:17давайте я верну тело запроса, только тут
- 11:34:19у меня уже будет одноединственное поле в
- 11:34:21этом жене complete, и я буду передавать
- 11:34:24сюда значение true. Ну давайте попробуем
- 11:34:26отправить наш запрос. Отправляю и вижу,
- 11:34:29что я получил 200 ок. Но при этом
- 11:34:31никакого тела ответа я не получил. Хотя
- 11:34:33на самом-то деле мы с вами обсуждали,
- 11:34:36что когда мы какую-то задачу будем
- 11:34:37выполнять, то мы помимо статус кода в
- 11:34:39ответе при успешной обработке запроса
- 11:34:41должны возвращать ещё и Gson в теле
- 11:34:43ответа. И этот GSON должен представлять
- 11:34:45новую изменённую задачу. Ну просто я во
- 11:34:47время разработки этого хендлера взял, да
- 11:34:48и забыл это сделать. Абсолютно
- 11:34:50стандартная ситуация, что мы во время
- 11:34:51разработки забыли дописать, добавить
- 11:34:53какую-то функциональность. Именно
- 11:34:54поэтому очень важно приложение
- 11:34:56тестировать. Вот сейчас мы благодаря
- 11:34:57тестированию нашего приложения,
- 11:34:59благодаря тому, что мы пробегаемся по
- 11:35:00каждому хендлеру, по каждой написанной
- 11:35:02функциональности, мы увидели, что когда
- 11:35:03мы отмечаем задачу как выполненную, то
- 11:35:05нам возвращается статус-код. Но эта
- 11:35:07самая новая изменённая задача в ответм
- 11:35:09джине отсутствует, хотя там быть должна.
- 11:35:11Ну давайте её туда положим. У нас тут
- 11:35:13есть два варианта, как это сделать.
- 11:35:14Первый вариант - это мы после всех
- 11:35:16манипуляций над задачей можем заново
- 11:35:18получить её из туду листа следующим
- 11:35:20образом. Get task. И вот по тайтлу,
- 11:35:22который нам передавали. Вот так вот. Но
- 11:35:24в чём тут может быть проблема? А на
- 11:35:26самом деле проблема может быть в том,
- 11:35:28что за то время, которое прошло между
- 11:35:30фактическим выполнением задачи и нашим
- 11:35:32получением задачи из туду листа, вот
- 11:35:34между этими двумя строчками с задачей
- 11:35:37могло произойти что угодно. Мы вот тут
- 11:35:38вот могли выполнить задачу. Потом через
- 11:35:40одну наносекунду в каком-нибудь другом
- 11:35:42хендлере эта задача тут у нас могла
- 11:35:44удалиться уже. И мы, когда уже здесь
- 11:35:46будем получать нашу задачу, мы получим
- 11:35:48ничего not found, то есть задач у нас не
- 11:35:50будет, хотя мы, казалось бы, только что
- 11:35:51её поменяли. И что тогда давать в HTTP
- 11:35:53ответе? Задача не найдена. Ну а как
- 11:35:55тогда может быть 200 статусок, если мы
- 11:35:57её поменяли, но при этом она не найдена?
- 11:35:59В общем, странно получается. Поэтому я
- 11:36:00поступлю несколько иначе. Я буду эту
- 11:36:03самую изменённую задачу сразу же
- 11:36:05возвращать из методов complete task и
- 11:36:07uncomplete task. Для чего? Для того,
- 11:36:09чтобы если тут я задачу выполнил,
- 11:36:12сохранил это значение, то я именно это
- 11:36:14значение, изменённой и фактически
- 11:36:16перезаписанной задачи возвращал из
- 11:36:18нашего хендлера, чтобы даже если кто-то
- 11:36:20там через наносекунду эту задачу удалит
- 11:36:22или изменит или ещё что-нибудь с ней
- 11:36:23сделает, то всё равно мы к клиенту в
- 11:36:25HTTP ответе вернули именно тот вид
- 11:36:27задачи, который клиент именно вот этим
- 11:36:29своим запросом получил. клиент именно
- 11:36:31этим своим запросом выполнил задачу и
- 11:36:33больше ничего. И в таком случае я после
- 11:36:35изменения фактического после
- 11:36:36фактического выполнения этой задачи
- 11:36:38должен клиенту вернуть именно тот вид
- 11:36:39задачи, который был достигнут этим самым
- 11:36:42клиентским запросом. А если клиент после
- 11:36:44этого своего запроса увидит там
- 11:36:45что-нибудь not found, типа задача не
- 11:36:47найдена, он будет думать: "Да почему я
- 11:36:48типа своим запросом как-то удалил задачу
- 11:36:50или что?" Это будет воводить в
- 11:36:51заблуждение, поэтому мы будем поступать
- 11:36:53иначе. Я буду из метода Complete Task
- 11:36:56возвращать помимо ошибки ещё и задачу.
- 11:36:58Соответственно, если у меня какая-то
- 11:36:59ошибка, я должен вернуть пустую задачу и
- 11:37:01через запятую ошибку. А если у меня
- 11:37:03никаких ошибок нету, в таком случае я
- 11:37:05буду напрямую из нашего туду листа
- 11:37:07доставать эту новую перезаписанную
- 11:37:09задачу и возвращать её из нашего метода.
- 11:37:11Благодаря тому, что у нас тут мьютексами
- 11:37:12обложен весь наш дулист, то у нас не
- 11:37:14будет такого, что после сохранения
- 11:37:16задачи она где-то изменилась или
- 11:37:18удалилась, и мы тут уже возвращаем,
- 11:37:20например, тоже несуществующую задачу.
- 11:37:22Нет, такого у нас быть не может, потому
- 11:37:23что у нас мьютоксы. У нас вся эта
- 11:37:25секция, она находится под блокировкой
- 11:37:26мьютекса. Поэтому, если мы тут задачу
- 11:37:28записали, то ни в каком другом методе
- 11:37:30это самое значение не будет как-то
- 11:37:31перезаписано до того, как complete task
- 11:37:33не завершится полностью до конца. Ну,
- 11:37:35точно так же я поменяю uncomplete task.
- 11:37:37Значит, будем возвращать задачу и через
- 11:37:39запятую возвращаем ошибку. Тут тоже у
- 11:37:41нас пустая задача. А тут мы как раз-таки
- 11:37:43возвращаем нашу новую перезаписанную
- 11:37:45задачку. Вот так вот. Теперь внутри
- 11:37:49нашего хендлера handle complete task, мы
- 11:37:51должны чуть-чуть код переписать. Теперь
- 11:37:52тут мы уже будем немножко по-другому
- 11:37:54вызывать complete task. Я буду получать
- 11:37:55переменную changed task запятая error.
- 11:37:58Ну и уже в отдельной строчке мы будем
- 11:38:00проверять, что если у нас ошибка не
- 11:38:01равна, тогда мы её там обрабатываем вот
- 11:38:03так вот. Ну и тут значит то же самое.
- 11:38:05Change task запятая. И в отдельной
- 11:38:07строчке уже будем обрабатывать ошибку
- 11:38:09нашу. Ну тут в конце точка запятая нам
- 11:38:10больше не нужна. Теперь нам нужно эту
- 11:38:12самую change task при отсутствии ошибки
- 11:38:14возвращать в http ответе. Но мы видим,
- 11:38:16что у нас тут очень сильно дублируется
- 11:38:18код. Я сейчас вам покажу один из
- 11:38:19вариантов, как конкретно в данной
- 11:38:21сложившейся ситуации можно избавиться от
- 11:38:23этого самого неприятного дублирования
- 11:38:24кода, чтобы этот самый код попонятнее
- 11:38:26смотрелся и покрасивее выглядел, потому
- 11:38:28что уже никуда не годится такое сильное
- 11:38:30дублирование кода, слишком плохо, надо
- 11:38:32переделывать. Мы заранее объявим две
- 11:38:34переменные следующим синтаксисом. Даже
- 11:38:36если вы этот синтаксис впервые видите,
- 11:38:37как говорится, всё бывает в первый раз,
- 11:38:39значит, у нас есть переменная change.
- 11:38:41Change task типа todo. И у нас есть
- 11:38:44ошибка типа интерфейс ошибки. Потом я
- 11:38:46делаю следующим образом. Смотрю, если
- 11:38:48мне в http запросе передаётся,
- 11:38:49собственно, запрос на выполнение задачи,
- 11:38:51тогда я change it task запита присваиваю
- 11:38:54h. Complete task title else change it
- 11:38:58task равно h tod list uncomplete task
- 11:39:02title. Всё, после этого у меня обработка
- 11:39:05ошибки будет абсолютно одинаковая. Я
- 11:39:07могу это вынести. Вот это всё я теперь
- 11:39:09могу удалить.
- 11:39:11Всё, теперь у меня нету дублирования
- 11:39:13кода хотя бы в рамках этого самого
- 11:39:15хендлера. Почему этого дублирования
- 11:39:16нету? как я его избежал. Я вынес
- 11:39:18переменные change task и year из области
- 11:39:20видимости какого-то отдельного блока
- 11:39:22условного витвления в область видимости
- 11:39:24на уровне всего хендлера, то есть на
- 11:39:25уровне всей функции, благодаря чему я
- 11:39:27внутри блока условного ветвления смог
- 11:39:29получить эти самые значения и
- 11:39:31использовать их уже дальше вне блока
- 11:39:33условного ветвления. Раньше же, когда у
- 11:39:35меня не были переменные эти вынесены в
- 11:39:37область видимости всей функции, мне
- 11:39:39приходилось эти самые переменные,
- 11:39:40получается, создавать внутри этих самых
- 11:39:42блоков условного ветвления. И я уже не
- 11:39:44мог за рамками этого самого блока
- 11:39:46условного ветвления с этими переменными
- 11:39:48работать, потому что их область
- 11:39:49видимости ограничивалась вот этими
- 11:39:50самыми блоками. Получается, мне в каждом
- 11:39:52этом блоке приходилось писать одинаковый
- 11:39:54код, но теперь я эти переменные вынес за
- 11:39:56условия, и теперь я спокойно могу уже
- 11:39:59существующие переменные перезаписывать
- 11:40:01новыми значениями и потом после этого
- 11:40:02самого условного ветвления обрабатывать.
- 11:40:04Значит, тут я обработал ошибку. Ну и
- 11:40:06теперь мне остаётся отправить в HTTP
- 11:40:07ответе вот этот вот changed task. Для
- 11:40:09этого преобразуем его в jon
- 11:40:13change it task prefix никакой. Вот стоп.
- 11:40:161 2 3 4. Ну, значит, если у меня
- 11:40:18какая-то ошибка, я просто кидаю панику.
- 11:40:20Мы с вами уже обсуждали этот момент. То
- 11:40:22есть у нас сама структура задачи - это
- 11:40:24абсолютно понятная, простая структура,
- 11:40:27которая точно должна преобразовываться в
- 11:40:29GSON. Если у нас не получилось её
- 11:40:31преобразовать в GSON, то это очень
- 11:40:32странно получается. Хотя я тут, конечно,
- 11:40:34мог бы и в HTP ответ это записывать.
- 11:40:36Тут, на самом деле, довольно спорный
- 11:40:37момент, ребята, я вам хочу сказать. То
- 11:40:39есть, по сути, то, что я сейчас вот так
- 11:40:41кидаю панику, я сам себе упрощаю задачу
- 11:40:43для того, чтобы не делать тут сложных
- 11:40:44обработок этой самой ошибки. Но
- 11:40:46по-хорошему так делать не очень
- 11:40:48правильно. Почему? Потому что даже если
- 11:40:50сейчас вот эта структура задачи, она
- 11:40:51очень простая, то в будущем кто-нибудь,
- 11:40:54какой-нибудь наш коллега может сюда
- 11:40:55добавить какой-нибудь канал в поле этой
- 11:40:57структуры или ещё как-нибудь сделать
- 11:40:58так, чтобы эта самая ранняя простая
- 11:41:01структура стала слишком сложной для
- 11:41:02того, чтобы быть преобразованной в GSON.
- 11:41:05И у нас наше приложение будет кидать
- 11:41:06панику, и мы будем за голову браться.
- 11:41:08Почему же так? А потому что в прошлом
- 11:41:10когда-то ленивые мы не стали
- 11:41:12обрабатывать ошибку и просто подумали,
- 11:41:14что, ну, тут ошибки никогда не будет,
- 11:41:15поэтому я буду кидать панику. Нет,
- 11:41:16всё-таки вот так вот кидать панику,
- 11:41:18когда у нас есть ошибка - это не всегда
- 11:41:19хорошо. И если прямо правильно подходить
- 11:41:21к решению этого вопроса, тут мы, конечно
- 11:41:23же, должны были написать в специальные
- 11:41:25лог-файлы, мы с вами ещё даже пока не
- 11:41:27знаем, что такое локфайлы. В эти самые
- 11:41:28лог-файлы, то есть в логи, мы должны
- 11:41:30были написать о том, что у нас ошибка.
- 11:41:32Мы эту самую ошибку должны были точно
- 11:41:33так же правильно запаковать, дать в
- 11:41:35ответе, что у нас не получилось
- 11:41:36преобразовать Gon и так далее. и так
- 11:41:38далее, и так далее. Но пока я повторюсь,
- 11:41:39у нас нету времени на то, чтобы сейчас
- 11:41:41тут описывать логер наш, для того, чтобы
- 11:41:42писать логи и вот это вот всё. Поэтому я
- 11:41:44тут просто панику кидаю, потому что в
- 11:41:45данный момент я точно уверен, что тут
- 11:41:47абсолютно точно эта структура должна
- 11:41:50уметь переводиться в jon. О'кей, мы
- 11:41:52получили с вами этот самый JON. После
- 11:41:53этого пишем его в HTTP ответе. Записал.
- 11:41:56Тут также у нас может быть ошибка.
- 11:41:59Ну и, соответственно, у нас, если эта
- 11:42:00самая ошибка случилась, ну тогда я
- 11:42:02просто в консоль напишу failed to write
- 11:42:05http response. И вот ошибочка. и
- 11:42:07завершаю наш хендлер. Хорошо, давайте
- 11:42:09подумаем, чего же мы теперь добились
- 11:42:10тем, что мы так сильно поменяли наш
- 11:42:12хендлер. Ну, во-первых, теперь у нас
- 11:42:13будет в jon ответе возвращаться эта
- 11:42:15самая изменённая задача. То есть мы
- 11:42:17меняем задачу через наш toдулиistст. Нам
- 11:42:19возвращает фактически изменённую эту
- 11:42:21самую задачу, и мы потом её преобразуем
- 11:42:23в тело ответа и, собственно, пишем в это
- 11:42:25тело ответа. Это во-первых. Во-вторых,
- 11:42:26мы очень хорошо избавились от
- 11:42:27дублирования кода. Напоминаю, раньше у
- 11:42:29нас вот эта обработка ошибок, она просто
- 11:42:30дублировалась в блоке if и в блокесы.
- 11:42:33Ну, а если бы мы не избавились от этого
- 11:42:34самого дублирования кода, то нам
- 11:42:36пришлось бы ещё и дублировать вот это
- 11:42:37вот перевод изменённой задачи в GSON и
- 11:42:39запись в ответ. Ну а так мы с вами
- 11:42:41чуть-чуть этот хендлер поменяли. Он
- 11:42:42теперь мало того, что выполняет свою
- 11:42:43ключевую функциональность полностью так
- 11:42:46ещё и выглядит приятнее. Ну теперь нам
- 11:42:47нужно перезапустить нашу программу,
- 11:42:49заново наполнить её данными и уже второй
- 11:42:51раз протестировать нашу функциональность
- 11:42:53по выполнению какой-то задачи. Так, ну
- 11:42:55перезапустили наш сервер. Теперь давайте
- 11:42:57заново в него кидать наши постзапросы,
- 11:43:00то есть на создание каких-то наших
- 11:43:01задач. Отправляю прогулку с собакой.
- 11:43:032011 создалось. Значит, всё. Дальше у
- 11:43:05нас завтрак отправилось. И дальше у нас
- 11:43:09домашка тоже отправилось. Теперь
- 11:43:10смотрим, что у нас действительно всё
- 11:43:12добавилось. И да, наши три задачи мы
- 11:43:14заново занесли в наш дулист. Ну и можем
- 11:43:16заново начать тестировать этот handlerр
- 11:43:18handle complete task. Хорошо. Теперь
- 11:43:19давайте ещё раз попробуем поменять нашу
- 11:43:21задачу. Опять возьму завтрак. Это
- 11:43:22заголовок задачи. Заголовок задачи у
- 11:43:24меня должен присутствовать в паттерне,
- 11:43:25поэтому слешзавтрак. Метод patch. Ну и
- 11:43:28тут заново я должен описать jon. У меня
- 11:43:30complete требует моя complete task dto
- 11:43:34и соответственно через двоеточи это у
- 11:43:35меня будет true. О'кей, поехали.
- 11:43:37Отправим. Оп. И теперь мы видим, что
- 11:43:40помимо статус кода 200 в ответе, я вижу
- 11:43:42ещё и новую свою изменённую задачу. Что
- 11:43:44теперь у меня завтрак completed true, то
- 11:43:46есть выполнено, и я вижу время
- 11:43:48выполнения. Я создал задачу в 18:58. Ну,
- 11:43:52выполнил задачу в 19: то есть через 2
- 11:43:55минуты после создания я выполнил свою
- 11:43:56задачу. О'кей. Теперь давайте я получу
- 11:43:58список всех задач. Метод gets нам не
- 11:44:01нужен. Паттер просто скляю.
- 11:44:05И я вижу, что у меня домашка completed
- 11:44:07false, а вот завтрак completed true. Ну
- 11:44:10то есть это абсолютно точно нам говорит
- 11:44:11о том, что я поменял вот эту вот самую
- 11:44:13задачу, поменял в ней поле. Давайте я
- 11:44:15выполню ещё задачу. Домажка. Сделаю
- 11:44:17домажку
- 11:44:18метод pat паch и в боде true. Передаю.
- 11:44:21Запускаю. Отправляю запрос. Всё, теперь
- 11:44:24я выполнил домажку. Снова хочу получить
- 11:44:26список всех задач.
- 11:44:28Отправляю
- 11:44:30и вижу, что теперь у меня домашка помимо
- 11:44:32прочего ещё сделана. Осталось только
- 11:44:33прогулка собакой не выполнена. О'кей.
- 11:44:35Теперь я могу протестировать наш
- 11:44:37хендлер, который нужен для того, чтобы
- 11:44:38получать список всех невыполненных
- 11:44:40задач. И мы ожидаем, что нам вернётся
- 11:44:42только прогулка с собакой. Но я
- 11:44:44специально оставил эту ошибку. Сейчас вы
- 11:44:46увидите, в чём ошибка, и мы с вами её
- 11:44:47обсудим. Давайте мы сделаем, собственно,
- 11:44:49наш запрос. Нам нужно сделать getзапрос
- 11:44:51на Pattern/ tasks. Ставим вопрос. То
- 11:44:54есть query параметam ставим completed
- 11:44:56true. Отлично. Отправляем наш запрос.
- 11:44:59Опа. И мы почему-то получили вообще все
- 11:45:01наши задачи. Ещё раз запрос. Ещё раз.
- 11:45:03Сколько бы мы не отправляли, мы будем
- 11:45:05получать все наши задачи. Почему так?
- 11:45:07Потому что мы с вами, когда
- 11:45:08регистрировали наши самые хендлеры, мы
- 11:45:11сначала на pattern/tasks на мед get
- 11:45:14зарегистрировали метод handle get all
- 11:45:16tasks, который нам возвращает все
- 11:45:17задачи. А потом на тот же самый паттерн,
- 11:45:20на тот же самый метод мы
- 11:45:21зарегистрировали уже другой хендлер, но
- 11:45:24при этом добавили квари параметры. И
- 11:45:26потом вот этот вот самый роутер, когда
- 11:45:27входящие запросы обрабатывал, он первым
- 11:45:29делом находил handlerр get all tasks,
- 11:45:32потому что он посмотрел по пути, он
- 11:45:34посмотрел по методу и понял, всё круто,
- 11:45:36а query параметры он не проверил, потому
- 11:45:38что хендлер, который учитывает quy
- 11:45:40параметры, мы зарегистрировали позже.
- 11:45:42Как удостовериться в том, что у нас
- 11:45:43вызывается handler get all tasks, а не
- 11:45:45handler get all uncompleted tasks. Ну,
- 11:45:48просто вы можете здесь выводы на экран
- 11:45:50поставить fmt println1, а тут fmt
- 11:45:52println2 и посмотреть, что будет. И вы
- 11:45:54увидите, что всегда вызывается у нас вот
- 11:45:56этот вот хендлер. Ну, давайте это
- 11:45:57пофиксим. То есть мы просто возьмём и
- 11:45:59регистрацию этих самых хендлеров
- 11:46:00поменяем местами. Теперь нам нужно
- 11:46:02перезапустить наше приложение. Опять нам
- 11:46:04нужно наполнить данными наше приложение
- 11:46:07постзапрос HTTP GSON. И тут надеюсь, что
- 11:46:11у нас останется вот домажка. Поехали,
- 11:46:13создаём. Создали домашку. Завтрак.
- 11:46:16Покушать перед походом в школу. Ну и
- 11:46:20прогулка с собакой.
- 11:46:23Проснуться в 8:00 утра и погулять с
- 11:46:27Бобиком. Отправляю. Теперь посмотрю
- 11:46:30вообще, что мы с вами создали. Да,
- 11:46:32действительно, создали мы с вами три
- 11:46:33задачи. И теперь давайте я выполню вот
- 11:46:36задачу завтрак. Завтрак. Я метод pat
- 11:46:39патч использую. И тут в баде я передаю
- 11:46:41complete
- 11:46:44true. Отправляю.
- 11:46:46Выполнил завтрак. Теперь посмотрю на все
- 11:46:48имеющиеся задачи.
- 11:46:50Увижу, что у меня действительно завтрак
- 11:46:52выполнен. Всё остальное не выполнено. И
- 11:46:54теперь давайте я передам query
- 11:46:56параметры, то есть наши фильтрационные
- 11:46:57параметры в этот get запрос. Completed =
- 11:47:00true. Отправляю и вижу, что теперь у
- 11:47:03меня наконец-таки мои задачи
- 11:47:05фильтруются. То есть мне были выданы
- 11:47:06только лишь те задачи, которые не были
- 11:47:09ещё выполнены. HTTP тело тут, если что,
- 11:47:11не влияет ни на что, потому что у нас
- 11:47:13вся фильтрация происходит благодаря
- 11:47:15параметрам. Почему именно благодаря
- 11:47:17параметрам? В теме про QY параметры мы
- 11:47:19обсуждали. То есть, допустим, мы бы
- 11:47:20предоставляли какой-то сервис большому
- 11:47:22количеству пользователей и благодаря
- 11:47:24тому, что у нас фильтрация в QRY
- 11:47:25параметрах, один человек мог бы
- 11:47:26скопировать эту ссылочку и дать его
- 11:47:28своему другу, например, и друг бы сразу
- 11:47:30открыл нужную страничку с нужной
- 11:47:32фильтрацией. О'кей. Давайте я попробую
- 11:47:33выполнить ещё какую-нибудь задачу. А,
- 11:47:36домажку, например. Домажка, метод patch
- 11:47:39и наше HTTP тело complete true.
- 11:47:42Отправляю.
- 11:47:44Домажка выполнена. Теперь посмотрю
- 11:47:45вообще на все имеющиеся задачи. Вот они.
- 11:47:48Jon подсвечу и вижу, что у меня теперь
- 11:47:50выполнена домашка и завтрак. Прогулка с
- 11:47:52собакой. Нет, теперь я воспользуюсь
- 11:47:54фильтрацией. Completed true. Отправляю.
- 11:47:57Ну и вижу, что мне вернулась только
- 11:47:58прогулка с собакой, потому что она ещё
- 11:48:01не была выполнена. Таким образом, мы
- 11:48:02увидели, что мало того, чтобы правильно
- 11:48:04зарегистрировать все наши хендлеры, так
- 11:48:06ещё и нужно следить за порядком их
- 11:48:08регистрации. Тут уже, если какие-то
- 11:48:09прямо большие проблемы будут возникать,
- 11:48:11то можете либо погуглить, либо
- 11:48:12посмотреть в документацию этой самой
- 11:48:14библиотеки, либо спросить у нейронки.
- 11:48:16Обычно всё-таки нейронка может какой-то
- 11:48:17дельный совет дать и избавить вас там от
- 11:48:19большого количества проведённых часов в
- 11:48:21Гугле. Хорошо, продолжаем тестировать
- 11:48:22наше приложение. Теперь представим, я
- 11:48:24хочу ещё раз создать задачу с уже
- 11:48:26существующим заголовком.
- 11:48:28Я хочу ещё раз создать задачу. Прогулка
- 11:48:30с собакой. Давайте посмотрим, что будет.
- 11:48:32Отправляю запрос и вижу 409 conflict. И
- 11:48:35мне в сообщении пишется, что task
- 11:48:37already exist. То есть такая задача уже
- 11:48:38у меня есть. Отлично, это у меня
- 11:48:40отрабатывает. Давайте попробуем удалить
- 11:48:41какую-то задачу. Я захочу сначала
- 11:48:43давайте получить список всех задач. И я
- 11:48:45захочу удалить задачу завтрак. То есть
- 11:48:47вот завтрак я, в принципе, хочу её
- 11:48:48удалить. Метод delete. И саму задачу я
- 11:48:51буду передавать в паттерне. Ну, потому
- 11:48:53что, видимо, у нас метод, который задачу
- 11:48:55удаляет, он вот её заголовок принимает в
- 11:48:57паттерне. Отправляю, вижу 200 ок никого
- 11:49:00телоответа. Давайте посмотрим, удалилась
- 11:49:01ли задача.
- 11:49:03Действительно, задача удалилась. У нас
- 11:49:05больше нет задачи. Завтрак. Но,
- 11:49:06вообще-то, у нас должно было вернуться
- 11:49:08204но контент, вернулось 200. ОК.
- 11:49:10Почему? Да потому что я опять допустил
- 11:49:12ошибку и в случае успешного удаления
- 11:49:15задачи не проконтролировал статус-код,
- 11:49:17который будет записываться в ответе. Для
- 11:49:18этого мне нужно написать WR header http
- 11:49:21статус content. Всё, теперь у меня в
- 11:49:23случае успешного удаления задачи будет
- 11:49:25именно 204 статус-код. возвращаться. Но
- 11:49:27для этого мне нужно перезапустить
- 11:49:29приложение, опять наполнить его данными.
- 11:49:31Поэтому я предлагаю вам самим это всё
- 11:49:32протестировать. Ну а на этом всё. Мы с
- 11:49:34вами взяли и описали полноценное ээend
- 11:49:36приложение. Почему это приложение можно
- 11:49:38назвать эээнд приложением? Да потому что
- 11:49:40оно слушает входящие сетевые запросы,
- 11:49:42как-то их обрабатывает и отдаёт сетевые
- 11:49:44ответы. Почему это приложение можно
- 11:49:46называть ROP приложением? Потому что мы
- 11:49:48учли все основные требования, которые
- 11:49:51накладываются на наш контракт, если мы
- 11:49:53хотим быть RESTP приложением.
- 11:49:55Соответственно, всё, о чём мы с вами
- 11:49:56говорили, что у нас должен быть
- 11:49:57определённый стиль наименования
- 11:49:59endпоинтов или же паттернов, это мы
- 11:50:00соблюли. У нас везде наши энд endпоинты,
- 11:50:02наши паттерны называются согласно нашему
- 11:50:04ресурсу основному. Это задача. Далее, мы
- 11:50:07с вами строго определили HTTP методы под
- 11:50:09каждый вид манипуляции над ресурсом. Но
- 11:50:11вы помните, что когда мы получали
- 11:50:12какие-то задачи, мы просто метод get
- 11:50:14использовали. Когда мы создавали новую
- 11:50:15задачу, мы метод пост использовали.
- 11:50:17Когда мы удаляли, мы метод delete
- 11:50:19использовали. Когда мы изменяли метод
- 11:50:20pat патч, всё соблюдено. Мы с вами
- 11:50:22соблюли третье правило, что у нас должны
- 11:50:23быть строго определённые места для
- 11:50:25передачи дополнительной информации в
- 11:50:26запросе. То есть помним, что-то мы с
- 11:50:28вами вычитывали из джисона в
- 11:50:29телезапроса, что-то мы с вами передавали
- 11:50:31прямо в паттерне идентификаторы наших
- 11:50:33ресурсов, где-то мы с вами, в принципе,
- 11:50:35никакую дополнительную информацию не
- 11:50:36передавали, где-то мы передавали
- 11:50:38quyпараметры, где-то мы комбинировали.
- 11:50:40Опять же мы выполнили основные правила
- 11:50:42написания Restp backend приложения.
- 11:50:45Соблюли четвёртое правило, что у нас
- 11:50:47должна быть строго определённая
- 11:50:48информация в HTP теле ответа. соблюли
- 11:50:50особенно вот это вот правило с ошибкой.
- 11:50:51У нас очень красивые ошибки выводились.
- 11:50:53Сразу видно, что пошло не так, и сразу
- 11:50:55понятно, когда у нас эта самая ошибка
- 11:50:56случилась. Могут быть ещё другие
- 11:50:58опциональные поля, но вы уже умеете их,
- 11:51:00если что, добавлять. Также мы с вами
- 11:51:02проследили за статус-кодами,
- 11:51:04протестировали, что у нас действительно
- 11:51:05во время создания ресурса 2011
- 11:51:07статус-код, во время получения и
- 11:51:09изменения ресурсов двухсотые статус-коды
- 11:51:11и во время удаления ресурса 204
- 11:51:13статус-код. То же самое с ошибками мы с
- 11:51:14вами посмотрели, когда у нас конфликт,
- 11:51:16когда у нас not found, когда у нас B
- 11:51:18request и так далее. Ну и в заключении я
- 11:51:20бы хотел повторить, что Restaпи - это не
- 11:51:22панацея, это не единственная верная
- 11:51:24парадигма написаниякэнд приложений.
- 11:51:26Далеко не так. И повторюсь, далеко не
- 11:51:29везде придерживаются полностью всех
- 11:51:30правил RP. Но если задаваться вопросом:
- 11:51:33"Зачем же вообще RP придумали? Зачем же
- 11:51:35он нужен, то ответ таков: RСпи - это
- 11:51:37свод правил, либо же ещё можно назвать
- 11:51:39это парадигмой. И исходя из этих правил,
- 11:51:41исходя из этой парадигмы, мы с вами
- 11:51:43получаем ответы на такие фундаментальные
- 11:51:46вопросы. Как как нам назвать наш
- 11:51:47паттерн? Какой HTTP метод мне нужно
- 11:51:49использовать? Что мне нужно
- 11:51:50использовать, чтобы получать
- 11:51:51дополнительную информацию? Что мне нужно
- 11:51:53возвращать в теле ответа? Как мне
- 11:51:54возвращать мои ошибки? Как мне
- 11:51:55обрабатывать мои ошибки? На все эти и
- 11:51:58большое количество других вопросов даёт
- 11:52:00ответ парадигма RP. И несмотря на то,
- 11:52:02что много где не придерживаются
- 11:52:04полноценно всех правил RСP, мы это даже
- 11:52:06уже видели на примере с Ютубом, несмотря
- 11:52:08на это, какие-то отдельные правила очень
- 11:52:11часто используются. Поэтому, когда вы
- 11:52:12где-то увидите разделение
- 11:52:13функциональности, например, по методам,
- 11:52:15что у нас есть get метод HTTP, есть
- 11:52:17постмеthod, есть put, то знайте, что это
- 11:52:19одна из частей P, и возможно где-то ещё
- 11:52:22в вашей системе использованы какие-то
- 11:52:23другие правила RСP. Таким образом, вы
- 11:52:25просто расширяете свой кругозор вообще
- 11:52:27на построение- систем, на построение
- 11:52:29контрактов этих бэкэнд-систем как
- 11:52:31разработчик. Тем самым вы становитесь
- 11:52:32более опытны, и тем самым вы получаете
- 11:52:34умение писать уже свои собственные ээнд-
- 11:52:37приложения, продумывать им контракт,
- 11:52:39продумывать, как клиенты с вашими
- 11:52:41приложениями будут взаимодействовать и
- 11:52:43так далее. Ну а на этом вторая часть
- 11:52:45полного курса по GLН подходит к концу. В
- 11:52:48завершение я лишь хочу напомнить о том,
- 11:52:51насколько важно закреплять пройденный
- 11:52:53материал при помощи решения домашних
- 11:52:55заданий и написания учебных проектов.
- 11:52:58Благодарю вас за просмотр. До встречи.
- 11:53:01M.
About this transcript
This page contains the full transcript of GOLANG ПОЛНЫЙ КУРС ДЛЯ НАЧИНАЮЩИХ | ЧАСТЬ 2 | ПРОДВИНУТЫЙ GO и REST API by nilchanpub, generated from the public captions YouTube serves with the video. The transcript has 127,834 words across 21,304 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to choose a caption track available on the video.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, caption language selection, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.