Go Developer: Путь до Strong Junior

Комплексный курс для освоения языка Go, включающий изучение синтаксиса, модели конкурентности и создание производительных веб-сервисов. Программа ориентирована на получение практических навыков, необходимых для уверенного старта карьеры и написания идиоматичного кода.

1. Основы языка: синтаксис, базовые типы данных и управление потоком

Основы языка: синтаксис, базовые типы данных и управление потоком

Добро пожаловать в курс Go Developer: Путь до Strong Junior. Мы начинаем наше путешествие с фундамента, на котором строится любое приложение на Go. Этот язык, разработанный в Google, славится своей простотой, производительностью и строгим отношением к коду.

В этой статье мы разберем анатомию Go-программы, научимся работать с переменными и типами данных, а также освоим управление потоком выполнения. Если вы пришли из других языков (Python, Java, C++), многое покажется вам знакомым, но у Go есть свои уникальные идиомы, которые важно усвоить с самого начала.

Анатомия программы на Go

Любая исполняемая программа на Go начинается с пакета main. Это точка входа. Давайте посмотрим на классический пример Hello World и разберем его по косточкам.

Разбор структуры

  • package main: Первая строка любого файла .go объявляет, к какому пакету относится этот файл. Пакет main — особенный. Он сообщает компилятору, что этот код должен быть скомпилирован в исполняемый файл, а не в библиотеку.
  • import "fmt": Команда импорта подключает внешние пакеты. fmt (форматирование) — это стандартная библиотека для ввода и вывода данных (аналог printf в C или print в Python).
  • func main() { ... }: Функция main — это сердце программы. Именно с неё начинается выполнение кода. Обратите внимание: она не принимает аргументов и ничего не возвращает.
  • !Структура стандартного файла исходного кода на Go

    Переменные и константы

    Go — язык со статической типизацией. Это значит, что тип переменной определяется на этапе компиляции и не может меняться в процессе работы программы. Это спасает от множества глупых ошибок.

    Объявление переменных

    Существует два основных способа создания переменных.

    1. Полное объявление через var

    Используется, когда мы хотим явно указать тип или когда мы объявляем переменную на уровне пакета (вне функций).

    2. Короткое объявление :=

    Это самый популярный способ внутри функций. Go сам понимает (выводит) тип переменной на основе значения справа.

    > Важно: Оператор := можно использовать только внутри функций. На уровне пакета он вызовет ошибку компиляции.

    Константы

    Константы объявляются с помощью ключевого слова const. Их значение должно быть известно на этапе компиляции.

    Базовые типы данных

    В Go нет бесконечного зоопарка типов, но есть всё необходимое для эффективной работы.

    Целые числа (Integers)

    Самые используемые типы:

    * int: Зависит от архитектуры процессора (32 или 64 бита). Используйте его по умолчанию для счетчиков и математики. * int8, int16, int32, int64: Числа фиксированного размера. * uint: Беззнаковое целое (только положительные числа).

    Числа с плавающей точкой (Floats)

    * float32: Одинарная точность. * float64: Двойная точность (стандарт де-факто в Go).

    Булев тип (Boolean)

    Тип bool может принимать только два значения: true или false. В Go нет неявного приведения типов, как в C или JavaScript. Вы не можете написать if 1 { ... }, вы обязаны написать if 1 == 1 { ... }.

    Строки (Strings)

    Строки в Go — это неизменяемая последовательность байтов. Они заключаются в двойные кавычки "...".

    Для многострочного текста используются обратные кавычки (backticks) ` ... . В них сохраняются все переносы строк и пробелы.

    Нулевые значения (Zero Values)

    Это одна из киллер-фич Go. Если вы объявили переменную, но не присвоили ей значение, она не будет содержать "мусор" из памяти. Она получит нулевое значение по умолчанию:

    | Тип | Zero Value | | :--- | :--- | | int, float | 0 | | bool | false | | string | "" (пустая строка) | | Ссылочные типы (пока не изучали) | nil |

    Это гарантирует предсказуемое поведение программы.

    Управление потоком

    Go стремится к минимализму. Здесь нет while или do-while. Есть только один цикл, но он умеет всё.

    Условный оператор if

    Синтаксис похож на C, но круглые скобки вокруг условия не нужны, а фигурные скобки обязательны.

    Идиома Go: if с инициализацией

    Вы можете объявить переменную прямо в условии if. Эта переменная будет видна только внутри блока if-else.

    Это крайне полезно для обработки ошибок, о чем мы поговорим в следующих статьях.

    Цикл for

    Как уже было сказано, for — единственный цикл в Go. Но у него есть три обличия.

    1. Классический цикл (как в C/Java)

    2. Цикл-while (только условие)

    3. Бесконечный цикл

    !Три формы использования цикла for в Go

    Оператор switch

    switch в Go удобнее, чем во многих других языках.

  • Не нужен break: В Go кейсы не "проваливаются" (fallthrough) по умолчанию. Выполнился один case — вышли из switch.
  • Любые типы: Можно переключаться не только по числам, но и по строкам.
  • Switch без условия

    Это чистая альтернатива длинным цепочкам if-else.

    Форматирование кода

    В мире Go не спорят о том, где ставить скобки. Есть утилита go fmt, которая автоматически форматирует код по единому стандарту.

    > Хороший тон Go-разработчика: всегда прогонять код через go fmt` перед коммитом. Большинство IDE делают это автоматически при сохранении файла.

    Заключение

    Мы рассмотрели базу: структуру программы, переменные, типы и управление потоком. Go может показаться слишком простым, но именно в этой простоте кроется его сила. Ограниченный набор инструментов заставляет писать понятный и поддерживаемый код.

    В следующей статье мы углубимся в более сложные структуры данных: массивы, слайсы и мапы, которые являются "рабочими лошадками" любого Go-приложения.

    2. Составные типы, методы и реализация полиморфизма через интерфейсы

    Составные типы, методы и реализация полиморфизма через интерфейсы

    В предыдущей статье мы заложили фундамент, разобравшись с базовыми типами данных и управлением потоком. Но реальные приложения редко оперируют просто числами или строками. Мы работаем с пользователями, заказами, списками товаров и сложными конфигурациями.

    Сегодня мы переходим на следующий уровень: научимся создавать сложные структуры данных, наделять их поведением с помощью методов и освоим одну из самых мощных концепций Go — интерфейсы. Именно здесь начинается настоящий объектно-ориентированный подход в стиле Go (хотя сам язык избегает термина ООП в классическом понимании).

    Массивы и Слайсы: Работа с коллекциями

    В большинстве языков есть массивы или списки. В Go эта концепция разделена на два типа: массивы (Arrays) и слайсы (Slices).

    Массивы (Arrays)

    Массив в Go — это последовательность элементов одного типа фиксированной длины. Длина массива является частью его типа. Это значит, что [5]int и [10]int — это совершенно разные типы данных.

    Массивы используются редко из-за своей жесткости. Вы не можете изменить размер массива после создания. Однако они служат основой для слайсов.

    Слайсы (Slices)

    Слайс (или срез) — это гибкая, динамическая обертка над массивом. В 99% случаев, когда вам нужен список элементов, вы будете использовать именно слайс.

    Слайс состоит из трех компонентов:

  • Указатель на базовый массив.
  • Длина (len) — количество элементов в слайсе.
  • Емкость (cap) — сколько элементов помещается в базовом массиве, начиная с индекса слайса.
  • !Внутреннее устройство слайса: указатель на массив, длина и емкость

    Создание слайсов:

    Динамическое расширение:

    Главная фишка слайса — функция append. Она добавляет элементы в конец. Если в базовом массиве есть место (емкость позволяет), элемент просто дописывается. Если места нет, Go создает новый массив большего размера (обычно в 2 раза), копирует туда старые данные и добавляет новый элемент.

    > Важно: Слайсы являются ссылочными типами. Если вы передадите слайс в функцию и измените его элемент по индексу, это изменение отразится и в вызывающей функции, так как они смотрят на один и тот же массив.

    Карты (Maps)

    Map (карта, словарь, хеш-таблица) — это неупорядоченная коллекция пар «ключ-значение». Это основной инструмент для быстрого поиска данных.

    Операции с картами:

    Проверка наличия ключа:

    Если запросить ключ, которого нет, Go вернет нулевое значение для типа (пустую строку, 0 и т.д.). Чтобы отличить «значение 0» от «ключа нет», используется идиома «comma ok»:

    > Порядок обхода карты в цикле for range в Go случайный. Не полагайтесь на то, что элементы будут выводиться в том же порядке, в котором вы их добавили.

    Структуры (Structs)

    Структура — это типизированная коллекция полей. Это аналог классов в других языках, но без наследования в привычном виде. Структуры позволяют объединять логически связанные данные.

    Встраивание (Embedding)

    Вместо наследования Go использует композицию. Вы можете встроить одну структуру в другую.

    Это называется promoted fields (продвинутые поля). Поля Person становятся доступны на уровне Employee, если нет конфликта имен.

    Методы

    Метод в Go — это функция, у которой есть специальный аргумент — получатель (receiver). Получатель указывается в скобках перед именем функции.

    Value Receiver vs Pointer Receiver

    Это критически важный момент для понимания Go. У метода может быть два типа получателей:

  • Value Receiver (r Rectangle): Метод получает копию структуры. Изменения полей внутри метода не повлияют на оригинальный объект.
  • Pointer Receiver (r *Rectangle): Метод получает указатель на структуру. Изменения полей изменят оригинал.
  • Правило большого пальца: Если метод должен изменять состояние структуры или если структура большая (чтобы не копировать много данных), используйте Pointer Receiver. В остальных случаях можно использовать Value Receiver.

    Интерфейсы и Полиморфизм

    Интерфейсы в Go — это способ определения поведения. Интерфейс говорит не о том, чем является объект, а о том, что он умеет делать.

    Объявление интерфейса

    Любой тип, у которого есть метод Speak() string, автоматически удовлетворяет этому интерфейсу. В Go нет ключевого слова implements. Реализация интерфейсов неявная (Duck Typing: «Если это выглядит как утка и крякает как утка, то это утка»).

    Реализация полиморфизма

    Давайте создадим два разных типа, которые реализуют интерфейс Speaker.

    Теперь мы можем написать функцию, которая принимает интерфейс Speaker. Ей неважно, передадите вы туда собаку, кошку или любой другой тип, умеющий говорить.

    Это и есть полиморфизм: единый интерфейс взаимодействия с объектами разных типов.

    Пустой интерфейс interface{}

    Интерфейс без методов называется пустым интерфейсом: interface{} (в новых версиях Go есть алиас any). Ему удовлетворяет абсолютно любой тип, так как любой тип имеет «ноль или более» методов.

    Хотя это выглядит удобно, злоупотреблять interface{} не стоит, так как вы теряете преимущества строгой типизации.

    Заключение

    Сегодня мы сделали огромный шаг вперед. Мы научились: * Группировать данные с помощью слайсов, карт и структур. * Добавлять логику к данным с помощью методов. * Писать гибкий код, используя интерфейсы и полиморфизм.

    Эти инструменты — «хлеб и масло» Go-разработчика. В следующей статье мы разберем, как обрабатывать ошибки (ведь в Go нет исключений!) и как работать с файловой системой, чтобы наши программы стали по-настоящему полезными.

    3. Магия конкурентности: глубокое погружение в горутины, каналы и примитивы синхронизации

    Магия конкурентности: глубокое погружение в горутины, каналы и примитивы синхронизации

    Добро пожаловать в одну из самых захватывающих частей нашего курса Go Developer: Путь до Strong Junior. Если интерфейсы делают архитектуру вашего приложения гибкой, то конкурентность делает его производительным и отзывчивым.

    Go часто называют «языком облачной эры», и во многом это заслуга его уникальной модели работы с конкурентностью. В то время как другие языки (Java, C++, Python) годами боролись со сложностью потоков операционной системы, Go предложил элегантное решение, встроенное прямо в ядро языка.

    Сегодня мы разберем, что такое горутины, как заставить их общаться через каналы и как не выстрелить себе в ногу, работая с общей памятью.

    Конкурентность против Параллелизма

    Прежде чем писать код, важно понять разницу в терминах, которые часто путают.

    Конкурентность (Concurrency) — это способность программы иметь дело* с несколькими задачами одновременно. Это про структуру программы. Представьте, что вы один жонглируете тремя мячами. Вы касаетесь только одного мяча в момент времени, но процесс идет для всех трех. Параллелизм (Parallelism) — это способность программы выполнять* несколько задач одновременно. Это про физическое исполнение. Представьте, что три человека держат по одному мячу.

    Go позволяет писать конкурентный код, который автоматически становится параллельным, если у вас многоядерный процессор.

    Горутины (Goroutines)

    Горутина — это легковесный поток выполнения. Если обычный поток операционной системы (OS Thread) требует около 1-2 МБ памяти для стека, то горутина начинает всего с 2 КБ. Это позволяет запускать сотни тысяч горутин на одной машине без существенной нагрузки.

    Запуск горутины

    Чтобы запустить функцию в отдельной горутине, достаточно поставить ключевое слово go перед вызовом функции.

    Если вы запустите этот код, то, скорее всего, увидите только «Привет из main!». Почему?

    Дело в том, что программа на Go завершается тогда, когда завершается главная горутина (функция main). Когда мы пишем go sayHello(), управление мгновенно возвращается в main, следующая строка печатает сообщение, и main завершается, убивая все остальные запущенные горутины, даже если они не успели выполниться.

    Синхронизация с sync.WaitGroup

    Новички часто используют time.Sleep, чтобы подождать завершения горутин. Это плохая практика, так как мы никогда не знаем точно, сколько времени займет выполнение. Правильный способ — использовать примитив синхронизации WaitGroup из пакета sync.

    Механизм прост:

  • Add(n): Увеличиваем счетчик задач.
  • Done(): Уменьшаем счетчик (обычно вызывается через defer).
  • Wait(): Блокирует выполнение текущей горутины, пока счетчик не обнулится.
  • !Планировщик Go (Go Scheduler) мультиплексирует тысячи горутин на небольшое количество потоков ОС.

    Каналы (Channels)

    Если горутины — это рабочие, то каналы — это трубы, по которым они передают друг другу данные. Философия Go гласит:

    > Не общайтесь, используя общую память; вместо этого делитесь памятью, общаясь.

    Канал — это типизированный проводник. Вы можете отправить данные в один конец и получить их из другого.

    Объявление и использование

    Буферизированные и небуферизированные каналы

  • Небуферизированный канал (make(chan int)): У него нет места для хранения. Отправка блокирует горутину, пока кто-то не начнет читать. Чтение блокирует, пока кто-то не начнет писать. Это гарантирует синхронизацию.
  • Буферизированный канал (make(chan int, 3)): Имеет емкость. Отправка не блокирует, пока в буфере есть место. Чтение не блокирует, пока буфер не пуст.
  • Закрытие каналов и Range

    Отправитель может закрыть канал, чтобы сообщить, что данных больше не будет. Получатель может проверить это.

    > Важно: Попытка отправить данные в закрытый канал вызовет панику (panic). Чтение из закрытого канала вернет «нулевое значение» типа и false во втором возвращаемом параметре.

    Select: Многоканальное управление

    Оператор select позволяет горутине ожидать операции сразу с несколькими каналами. Это похоже на switch, но для каналов.

    select блокируется, пока один из кейсов не станет готовым к выполнению. Если готовы несколько, Go выберет один из них случайным образом.

    Тайм-ауты с select

    Очень популярный паттерн — ограничение времени ожидания ответа.

    Общая память и Мьютексы

    Иногда каналы — это слишком сложно или неэффективно. Например, если нам нужно просто безопасно увеличить счетчик из разных горутин. Если мы сделаем это без защиты, мы получим Race Condition (состояние гонки).

    Представьте код: counter++

    На низком уровне это три операции:

  • Прочитать значение counter из памяти.
  • Увеличить значение на 1.
  • Записать новое значение в память.
  • Если две горутины сделают это одновременно, они могут прочитать одно и то же старое значение, и в итоге счетчик увеличится только на 1, а не на 2.

    sync.Mutex

    Мьютекс (Mutual Exclusion) — это замок. Только одна горутина может владеть замком в один момент времени.

    Использование defer для разблокировки — это стандарт де-факто. Это гарантирует, что мьютекс будет разблокирован, даже если функция завершится паникой.

    !Слева — состояние гонки, справа — защита доступа с помощью мьютекса.

    Опасности конкурентности

    С большой силой приходит большая ответственность. Вот две главные проблемы, с которыми вы столкнетесь:

  • Race Condition: Когда результат зависит от случайного порядка выполнения. Лечится мьютексами или каналами. Go имеет встроенный детектор гонок: запускайте тесты с флагом go test -race.
  • Deadlock (Взаимная блокировка): Ситуация, когда все горутины ждут друг друга, и никто не может продолжить работу. Например, если вы пытаетесь читать из канала, в который никто никогда не напишет.
  • Заключение

    Конкурентность в Go — это мощный инструмент, который требует смены мышления. Вместо того чтобы думать о последовательном выполнении инструкций, вы начинаете думать о потоках данных и независимых агентах (горутинах), которые взаимодействуют друг с другом.

    Мы изучили: * Как запускать горутины и ждать их завершения с WaitGroup. * Как передавать данные через каналы. * Как управлять потоками данных с помощью select. * Как защищать общие данные с помощью Mutex.

    В следующей статье мы перейдем к практическому применению этих знаний и рассмотрим работу с сетью и создание HTTP-серверов, где конкурентность играет ключевую роль.

    4. Стандартная библиотека, работа с сетью HTTP и написание Unit-тестов

    Стандартная библиотека, работа с сетью HTTP и написание Unit-тестов

    В предыдущих статьях мы прошли путь от базового синтаксиса до магии конкурентности с горутинами и каналами. Теперь, когда вы умеете писать эффективный асинхронный код, пришло время заставить его взаимодействовать с внешним миром.

    Go часто называют языком «с батарейками в комплекте» (batteries included). Это означает, что его стандартная библиотека настолько богата и продумана, что для создания полноценного веб-сервера или микросервиса вам часто даже не нужны сторонние фреймворки. Сегодня мы разберем «джентльменский набор» Go-разработчика: работу с сетью, обработку JSON и, конечно же, написание надежных тестов.

    Философия стандартной библиотеки

    Стандартная библиотека Go (stdlib) — это эталон кода. Если вы хотите научиться писать идиоматичный Go-код, просто откройте исходники пакетов net/http или io.

    Ключевые пакеты, которые вы будете использовать ежедневно:

    * fmt: Форматированный ввод-вывод (аналог printf в C). * os: Взаимодействие с операционной системой (файлы, переменные окружения, аргументы запуска). * io и bufio: Абстракции для ввода-вывода. Интерфейсы io.Reader и io.Writer — это фундамент, на котором строится вся работа с данными в Go. * encoding/json: Сериализация и десериализация данных. * net/http: Клиент и сервер для работы с HTTP.

    Работа с HTTP: Клиент

    В современном мире микросервисов приложения постоянно общаются друг с другом. Go делает отправку HTTP-запросов тривиальной задачей.

    Простой GET-запрос

    Самый быстрый способ получить данные:

    Профессиональный подход: Тайм-ауты

    Метод http.Get использует дефолтный клиент, у которого нет тайм-аутов. Если сервер, к которому вы обращаетесь, зависнет, ваша горутина тоже зависнет навечно. В продакшене это недопустимо.

    Для Strong Junior уровня вы должны всегда создавать свой экземпляр http.Client:

    Работа с HTTP: Сервер

    Написать веб-сервер на Go проще, чем на большинстве других языков. Пакет net/http предоставляет всё необходимое.

    Анатомия хендлера

    Обработчик (handler) — это функция, которая принимает два аргумента:

  • w http.ResponseWriter: интерфейс для отправки ответа клиенту.
  • r *http.Request: структура, содержащая информацию о входящем запросе (URL, заголовки, тело).
  • !Поток обработки HTTP-запроса: от клиента через маршрутизатор к конкретному обработчику.

    Работа с JSON

    JSON — это язык общения в вебе. В Go для работы с ним используются структуры и теги (struct tags).

    Теги — это метаинформация, записанная в обратных кавычках после типа поля. Она подсказывает пакету encoding/json, как называть поля при конвертации.

    Маршалинг и Анмаршалинг

    * Marshal: Преобразование структуры Go в JSON (строку байтов). * Unmarshal: Преобразование JSON в структуру Go.

    Обратите внимание на json.NewEncoder(w).Encode(user). Это более эффективно, чем json.Marshal, так как пишет данные напрямую в поток вывода (io.Writer), избегая лишнего выделения памяти под буфер.

    Тестирование: Unit-тесты

    В Go тестирование — это не опция, а встроенная часть языка. Вам не нужны JUnit, PyTest или Mocha. Всё есть в пакете testing.

    Правила создания тестов

  • Файл с тестами должен называться имяфайла_test.go.
  • Функция теста должна начинаться с префикса Test (например, TestSum).
  • Функция принимает один аргумент: t *testing.T.
  • Пример простого теста

    Допустим, у нас есть функция сложения:

    Тест для неё:

    Запуск тестов выполняется командой go test.

    Table-Driven Tests (Табличные тесты)

    Это стандарт де-факто в Go. Вместо того чтобы писать десять отдельных функций для проверки разных кейсов, мы создаем таблицу (слайс структур) с входными данными и ожидаемым результатом.

    Такой подход позволяет легко добавлять новые сценарии, просто добавляя строку в слайс tests. Метод t.Run запускает каждый кейс как подтест, что делает вывод результатов красивым и понятным.

    !Визуализация паттерна Table-Driven Tests: данные отделены от логики проверки.

    Моки и интерфейсы

    Как тестировать код, который ходит в базу данных или делает внешние HTTP-запросы? Здесь нам на помощь приходят интерфейсы, которые мы изучили ранее.

    Вместо того чтобы использовать реальную структуру, ваш код должен зависеть от интерфейса. В тестах вы подсовываете фейковую реализацию (Mock), которая возвращает заранее подготовленные данные.

    Заключение

    Сегодня мы освоили инструменты, которые превращают Go из простого языка в мощную платформу для веб-разработки:

  • Мы научились делать HTTP-запросы и поняли важность тайм-аутов.
  • Мы создали простой HTTP-сервер.
  • Мы разобрались с JSON и тегами структур.
  • Мы изучили Table-Driven Tests — золотой стандарт тестирования в Go.
  • Теперь у вас есть все кирпичики для построения реальных приложений. В следующей статье мы соединим всё это вместе и поговорим о работе с базами данных и архитектуре приложений, чтобы ваш код был не только рабочим, но и поддерживаемым.

    5. Взаимодействие с базами данных, архитектура приложений и лучшие практики разработки

    Взаимодействие с базами данных, архитектура приложений и лучшие практики разработки

    Мы подошли к финальной и самой важной части нашего курса Go Developer: Путь до Strong Junior. Вы уже умеете писать синтаксически верный код, работать с конкурентностью и создавать HTTP-серверы. Но чтобы стать Strong Junior разработчиком, этого недостаточно.

    Настоящая разработка начинается там, где код встречается с данными и бизнес-требованиями. Как сохранить данные пользователя навсегда? Как организовать проект так, чтобы в нем не запутаться через месяц? Как сделать приложение надежным?

    В этой статье мы разберем работу с базами данных через database/sql, изучим принципы Чистой Архитектуры и рассмотрим стандарты, принятые в Go-сообществе.

    Работа с базами данных: database/sql

    В отличие от многих языков, где сразу хватаются за ORM (Object-Relational Mapping), в Go принято любить SQL. Стандартная библиотека предоставляет мощный пакет database/sql, который является абстракцией над любой SQL-подобной базой данных.

    Драйверы

    Пакет database/sql — это интерфейс. Чтобы он заработал, нужен драйвер для конкретной БД (PostgreSQL, MySQL, SQLite). Сам Go не включает драйверы в стандартную поставку, их нужно скачивать.

    Для PostgreSQL стандартом де-факто является pgx или lib/pq.

    !Абстракция database/sql и роль драйвера в общении с БД

    Выполнение запросов

    В Go есть три основных метода выполнения запросов:

  • Exec: Для запросов, не возвращающих строки (INSERT, UPDATE, DELETE).
  • QueryRow: Когда мы ожидаем ровно одну строку.
  • Query: Когда мы ожидаем список строк.
  • Пример получения данных:

    > Важно: Никогда не подставляйте параметры в SQL-строку через fmt.Sprintf. Это прямой путь к SQL-инъекциям. Всегда используйте плейсхолдеры ($1, ?).

    Архитектура приложений

    Написать весь код в main.go можно для хакатона, но не для продакшена. Strong Junior должен понимать, как декомпозировать приложение.

    Standard Go Project Layout

    В сообществе Go есть негласный стандарт структуры папок:

    * cmd/: Точка входа. Здесь лежат файлы main.go. Например, cmd/api/main.go. * internal/: Приватный код приложения. Библиотеки из этой папки не могут быть импортированы другими проектами. Здесь живет бизнес-логика. * pkg/: Публичный код, который можно использовать в других проектах (например, утилиты для валидации). * api/: Спецификации API (Swagger/OpenAPI, Proto-файлы).

    Чистая Архитектура (Clean Architecture)

    Чтобы код был тестируемым и гибким, его делят на слои. Классический подход в Go включает три слоя:

  • Transport / Delivery Layer: Отвечает за "общение" с внешним миром (HTTP-хендлеры, парсинг JSON). Он ничего не знает о бизнес-логике, он просто передает данные дальше.
  • Service / Usecase Layer: Чистая бизнес-логика. Здесь происходят вычисления, проверки правил. Этот слой не знает ни про HTTP, ни про SQL.
  • Repository / Storage Layer: Работа с базой данных. Только здесь живут SQL-запросы.
  • !Слои Чистой Архитектуры: зависимости направлены внутрь

    Dependency Injection (DI)

    Как соединить эти слои? Через интерфейсы и внедрение зависимостей.

    Вместо того чтобы создавать подключение к БД внутри сервиса, мы передаем (внедряем) его снаружи.

    Это позволяет в тестах подменить реальную базу данных на мок (mock) и тестировать бизнес-логику изолированно.

    Лучшие практики (Best Practices)

    Что отличает профессиональный код от любительского?

    1. Конфигурация через переменные окружения

    Никаких паролей и портов в коде! Используйте переменные окружения (Environment Variables). Популярные библиотеки: os.Getenv (стандартная) или github.com/caarlos0/env.

    2. Graceful Shutdown (Плавное завершение)

    Когда вы деплоите новую версию приложения, старая должна завершиться корректно: дообработать текущие запросы и закрыть соединения с БД.

    3. Линтеры

    Go-компилятор строг, но линтеры еще строже. Используйте golangci-lint. Это агрегатор десятков линтеров, который найдет неиспользуемые переменные, ошибки в логике и проблемы со стилем.

    4. Обработка ошибок

    В Go ошибки — это значения. Их нельзя игнорировать.

    * Плохо: _ = db.Close() * Хорошо: Логирование ошибки, даже если мы не можем ее исправить. * Идиома: Ошибки нужно оборачивать, добавляя контекст: fmt.Errorf("не удалось найти пользователя: %w", err).

    Заключение курса

    Поздравляю! Вы прошли путь от "Hello World" до архитектуры микросервисов.

    Мы изучили: * Синтаксис и базовые типы. * Интерфейсы и полиморфизм. * Горутины и каналы. * HTTP, JSON и тестирование. * Работу с БД и архитектуру приложений.

    Теперь вы обладаете набором знаний уровня Strong Junior. Дальше вас ждет практика: напишите свой пет-проект (например, REST API для заметок), используя все изученные принципы. Удачи в мире Go!