HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В большинстве случаев веб-приложения взаимодействуют с хранилищами данных. Хранилищем может быть любая база данных, объектное хранилище, файловая система. У каждого есть свой интерфейс взаимодействия. Это большая тема для изучения, поэтому мы опишем базовый принцип работы с хранилищем в веб-приложениях. Для этого воспользуемся хранением данных в памяти Go-приложения.</p>
1 <p>В большинстве случаев веб-приложения взаимодействуют с хранилищами данных. Хранилищем может быть любая база данных, объектное хранилище, файловая система. У каждого есть свой интерфейс взаимодействия. Это большая тема для изучения, поэтому мы опишем базовый принцип работы с хранилищем в веб-приложениях. Для этого воспользуемся хранением данных в памяти Go-приложения.</p>
2 <h2>Хранение данных в памяти Go-приложения</h2>
2 <h2>Хранение данных в памяти Go-приложения</h2>
3 <p>Один из подходов построения архитектуры веб-приложения на Go - это<strong>слоеная архитектура</strong>. Когда в приложение приходит запрос, он попадает в слой обработчика, далее идет в слой бизнес-логики, а затем в слой хранилища. Если обработчику нужно только прочитать/записать данные из хранилища, то можно перейти в этот слой напрямую из обработчика:</p>
3 <p>Один из подходов построения архитектуры веб-приложения на Go - это<strong>слоеная архитектура</strong>. Когда в приложение приходит запрос, он попадает в слой обработчика, далее идет в слой бизнес-логики, а затем в слой хранилища. Если обработчику нужно только прочитать/записать данные из хранилища, то можно перейти в этот слой напрямую из обработчика:</p>
4 <p>Разберем слой хранения данных на примере заказов в интернет-магазине. Мы реализуем веб-приложение, которое позволит создавать новый заказ и получать о нем информацию.</p>
4 <p>Разберем слой хранения данных на примере заказов в интернет-магазине. Мы реализуем веб-приложение, которое позволит создавать новый заказ и получать о нем информацию.</p>
5 <p>Веб-приложение будет представлено в виде слоеной архитектуры. Каждый слой будет представлен в виде отдельной структуры. Структура<em>OrderHandler</em>выступит в роли обработчика HTTP-запросов, а структура<em>OrderStorage</em>- в роли хранилища заказов.</p>
5 <p>Веб-приложение будет представлено в виде слоеной архитектуры. Каждый слой будет представлен в виде отдельной структуры. Структура<em>OrderHandler</em>выступит в роли обработчика HTTP-запросов, а структура<em>OrderStorage</em>- в роли хранилища заказов.</p>
6 <p>Опишем хранилище<em>OrderStorage</em>. Для простоты мы будем хранить всю информацию о заказах в памяти приложения. Для этого используем структуру данных map. Ключом будет идентификатор заказа, а значениями - структуры заказов. Такая архитектура позволит получать и записывать значения за алгоритмическую сложность O(1):</p>
6 <p>Опишем хранилище<em>OrderStorage</em>. Для простоты мы будем хранить всю информацию о заказах в памяти приложения. Для этого используем структуру данных map. Ключом будет идентификатор заказа, а значениями - структуры заказов. Такая архитектура позволит получать и записывать значения за алгоритмическую сложность O(1):</p>
7 <p>Структура заказа содержит идентификатор, идентификатор пользователя и список товаров. Для простоты примера мы опустим необходимость указывать количество товаров в заказе:</p>
7 <p>Структура заказа содержит идентификатор, идентификатор пользователя и список товаров. Для простоты примера мы опустим необходимость указывать количество товаров в заказе:</p>
8 <p>Теперь опишем структуру<em>OrderHandler</em>, которая будет представлять слой обработчика:</p>
8 <p>Теперь опишем структуру<em>OrderHandler</em>, которая будет представлять слой обработчика:</p>
9 <p>Мы не стали использовать структуру<em>OrderStorage</em>напрямую. Мы описали интерфейс<em>OrderCreatorGetter</em>, который будет представлять общий интерфейс для хранилища. Это позволит в будущем заменить хранилище на другое без кода обработчика. Для этого нам достаточно будет реализовать интерфейс<em>OrderCreatorGetter</em>в новом хранилище.</p>
9 <p>Мы не стали использовать структуру<em>OrderStorage</em>напрямую. Мы описали интерфейс<em>OrderCreatorGetter</em>, который будет представлять общий интерфейс для хранилища. Это позволит в будущем заменить хранилище на другое без кода обработчика. Для этого нам достаточно будет реализовать интерфейс<em>OrderCreatorGetter</em>в новом хранилище.</p>
10 <p>В итоге мы получили следующее веб-приложение:</p>
10 <p>В итоге мы получили следующее веб-приложение:</p>
11 <p>Запускаем веб-приложение и отправляем запрос на сохранение тестовой записи:</p>
11 <p>Запускаем веб-приложение и отправляем запрос на сохранение тестовой записи:</p>
12 <p>В ответе получаем идентификатор созданного заказа. Подставляем его в запрос на получение и проверяем, что структура заказа возвращается:</p>
12 <p>В ответе получаем идентификатор созданного заказа. Подставляем его в запрос на получение и проверяем, что структура заказа возвращается:</p>
13 <p>Так мы создали простое веб-приложение на Go, которое хранит данные в памяти приложения. Также мы добавили интерфейс хранилища, чтобы в будущем при необходимости легко заменить хранение в памяти на хранение в любом другом хранилище.</p>
13 <p>Так мы создали простое веб-приложение на Go, которое хранит данные в памяти приложения. Также мы добавили интерфейс хранилища, чтобы в будущем при необходимости легко заменить хранение в памяти на хранение в любом другом хранилище.</p>
14 <h2>Защита мап с мьютексом</h2>
14 <h2>Защита мап с мьютексом</h2>
15 <p>В нашем веб-приложении на Go есть одно упущение, которое может привести к возникновению фатальной ошибки программы. Каждый HTTP-запрос обрабатывается в отдельной горутине в Go-приложениях, что может привести к ситуации, когда несколько горутин одновременно пытаются записать или читать из мапы<em>orders</em>. В этом случае происходит<strong>гонка данных</strong>- случай, когда две и более параллельные горутины пытаются изменить состояние одной структуры. Когда в Go-приложении возникает гонка данных с мапой, то оно завершается с фатальной ошибкой.</p>
15 <p>В нашем веб-приложении на Go есть одно упущение, которое может привести к возникновению фатальной ошибки программы. Каждый HTTP-запрос обрабатывается в отдельной горутине в Go-приложениях, что может привести к ситуации, когда несколько горутин одновременно пытаются записать или читать из мапы<em>orders</em>. В этом случае происходит<strong>гонка данных</strong>- случай, когда две и более параллельные горутины пытаются изменить состояние одной структуры. Когда в Go-приложении возникает гонка данных с мапой, то оно завершается с фатальной ошибкой.</p>
16 <p>Чтобы избежать этой проблемы, можно использовать мьютексы. Мьютекс - это механизм синхронизации, который позволяет блокировать доступ к критической секции кода, чтобы только одна горутина могла читать или изменять данные в мапе в один момент времени.</p>
16 <p>Чтобы избежать этой проблемы, можно использовать мьютексы. Мьютекс - это механизм синхронизации, который позволяет блокировать доступ к критической секции кода, чтобы только одна горутина могла читать или изменять данные в мапе в один момент времени.</p>
17 <p>Давайте рассмотрим простой пример использования мьютекса:</p>
17 <p>Давайте рассмотрим простой пример использования мьютекса:</p>
18 <p>Структура мьютекс предоставляет две функции: заблокировать и разблокировать. В примере выше мы заблокировали секцию инкремента переменной<em>i</em>, что гарантирует изменение переменной только одной горутиной в один момент времени. Такой код безопасен для использования в производственной среде.</p>
18 <p>Структура мьютекс предоставляет две функции: заблокировать и разблокировать. В примере выше мы заблокировали секцию инкремента переменной<em>i</em>, что гарантирует изменение переменной только одной горутиной в один момент времени. Такой код безопасен для использования в производственной среде.</p>
19 <p>Мы узнали, как использовать мьютекс. Давайте перепишем хранилище<em>OrderStorage</em>, чтобы его можно было безопасно использовать при параллельных запросах:</p>
19 <p>Мы узнали, как использовать мьютекс. Давайте перепишем хранилище<em>OrderStorage</em>, чтобы его можно было безопасно использовать при параллельных запросах:</p>
20 <p>Мы добавили блокировки в каждый метод, который читает или пишет в мапу<em>orders</em>. Теперь только одна горутина может изменять или читать данные в мапе в один момент времени, и гонки данных больше не возникает.</p>
20 <p>Мы добавили блокировки в каждый метод, который читает или пишет в мапу<em>orders</em>. Теперь только одна горутина может изменять или читать данные в мапе в один момент времени, и гонки данных больше не возникает.</p>
21 <p>Мы более подробно изучим различные механизмы синхронизации в отдельном курсе. Сейчас следует запомнить, что если мапа используется в разных горутинах, то всегда необходимо защищать ее мьютексом.</p>
21 <p>Мы более подробно изучим различные механизмы синхронизации в отдельном курсе. Сейчас следует запомнить, что если мапа используется в разных горутинах, то всегда необходимо защищать ее мьютексом.</p>
22 <h2>Выводы</h2>
22 <h2>Выводы</h2>
23 <ul><li>Веб-приложения используют различные хранилища для хранения данных. В самом простом случае можно хранить данные в памяти Go-приложения</li>
23 <ul><li>Веб-приложения используют различные хранилища для хранения данных. В самом простом случае можно хранить данные в памяти Go-приложения</li>
24 <li>В Go-приложениях часто используют слоеную архитектуру, когда логика обработчика отделена от логики хранилища</li>
24 <li>В Go-приложениях часто используют слоеную архитектуру, когда логика обработчика отделена от логики хранилища</li>
25 <li>Зависимости между слоями лучше передавать через интерфейсы. Это позволит легко заменять реализацию зависимости в будущем и при этом не изменять код слоя выше</li>
25 <li>Зависимости между слоями лучше передавать через интерфейсы. Это позволит легко заменять реализацию зависимости в будущем и при этом не изменять код слоя выше</li>
26 <li>Если в веб-приложении в памяти используются переменные, которые могут быть изменены в разных горутинах, то всегда необходимо защищать их мьютексами</li>
26 <li>Если в веб-приложении в памяти используются переменные, которые могут быть изменены в разных горутинах, то всегда необходимо защищать их мьютексами</li>
27 </ul>
27 </ul>