SOLID, DRY, KISS, YAGNI: ЧТО ЭТО ЗА АРХИТЕКТУРНАЯ ШЛЯПА? 💻
Сегодня хочу рассказать, что это за буквы такие и всегда ли удаётся их применять на работе (спойлер:не всегда )
SOLID
Пять букв, которые спрашивают на собеседовании. Фактически — это 5 принципов, которых "нужно" придерживаться при написании кода, но это не значит, что ты обязан слепо следовать этим принципам, всегда есть исключения ради бизнес-логики проекта
1. S (Single Responsibility Principle) — принцип единственной ответственности, который означает, что ты должен писать код так, чтобы каждый модуль в твоём приложении отвечал за что-то конкретное, а каждая функция делала что-то одно. Логгер — логгирует, функция заказа — создаёт заказ. Я думаю, тут всё понятно
2. O (Open-Closed Principle) — принцип открытости-закрытости, при котором ты должен по возможности добавлять новое поведение, а не переписывать уже работающий код. То есть суть в том, чтобы проектировать код так, чтобы новое поведение можно было добавлять без постоянного переписывания уже работающей логики
Давай сразу на примере:
У нас есть Transfer с полем Balance, а также метод GetBalance, который просто возвращает этот Balance:
И твоя задача — реализовать метод, который не просто вернёт баланс, но и проверит, что он больше нуля. И вот ты можешь залезть в метод GetBalance и изменить логику в нём, но намного правильнее создать новый метод CheckAndGetBalance и реализовать логику там следующим образом
То есть ты не меняешь текущую логику, а расширяешь, добавляя новую, потому что если ты изменишь исходный метод GetBalance, ты можешь столкнуться с проблемой, когда его сигнатура и логика поменялась, и теперь тебе нужно поменять обработку из этой функции везде, где эта функция вызывалась. Ещё лучше было бы создать структуру BalanceChecker и вынести логику проверки туда, но суть я объяснил...
3. L (Liskov Substitution Principle) — принцип подстановки Барбары Лисков, который говорит о том, что если у тебя есть код, который работает с абстракцией (с интерфейсом, к примеру), то ты должен иметь возможность менять реализации и программа продолжит работать корректно
Опять же, пример:
Допустим, у нас есть интерфейс Animal, который имеет метод Eat()
Теперь мы создадим структуры Dog (собака) и Puppy (щенок)
Теперь я могу реализовать метод Eat() у обеих структур, тем самым они будут реализовывать интерфейс Animal
А также я создам функцию EatAnimal, которая вообще ничего не будет знать о наших структурах и работать чисто с интерфейсом
И прикол в том, что если я теперь создам структуру Dog и Puppy, я смогу обе структуры передать в функцию EatAnimal и фактически логика никак не меняется от переданного аргумента, просто будет вызываться разная реализация
И главное: если одна сущность может использоваться вместо другой через общую абстракцию, такая замена не должна ломать ожидаемое поведение программы
При этом недостаточно просто реализовать методы: реализация должна ещё и соблюдать ожидаемый контракт. Если Puppy.Eat() внезапно делает panic, формально интерфейс реализован, но принцип уже нарушается
4. I (Interface Segregation Principle) — принцип разделения интерфейсов, который говорит о том, чтобы вместо одного большого интерфейса делать несколько точечных. Для чего? Для того, чтобы клиент, который опирается на интерфейс, не зависел от ненужных ему методов
Фактически, вместо того, чтобы делать так:
Ты делаешь следующим образом:
Думаю тут понятно, если что, задавай вопросы! Едем дальше⌨️
5. D (Dependency Inversion Principle) — принцип инверсии зависимостей, который говорит о том, что высокоуровневые и низкоуровневые модули должны зависеть от абстракций, а не друг от друга. Если непонятно, давай разбирать :)
Допустим, у нас есть сервис переводов:
И конкретная реализация репозитория, которая работает только с PostgreSQL:
Это плохо, потому что мы зависим от конкретной реализации, и если нас завтра попросят поддержать MySQL, то нам придётся добавлять ещё и MySQLRepository в TransferService, а потом ещё и Redis, и MongoDB... короче, чтобы такого не было, мы можем положить в TransferService интерфейс, а не конкретную реализацию:
Короче, суть этого принципа в том, что бизнес-логика не должна жёстко зависеть от конкретной базы данных, библиотеки или внешнего сервиса. Вместо этого она зависит от абстракции, а конкретная реализация уже подставляется снаружи
DRY, KISS, YAGNI
DRY (Don't Repeat Yourself) — не повторяй сам себя. Короче, если код повторяется, посмотри, можешь ли ты вынести его в функцию. Также не дублируй одну и ту же логику в разных местах, выноси в общие хелперы/функции
KISS (Keep it simple, stupid) — не усложняй решение просто так, код должен быть максимально простым, насколько это возможно. И вообще, пиши код так, чтобы его было легко читать даже без комментариев (не говорю, что комментарии не нужны)
YAGNI (You Aren't Gonna Need It) — не реализуй то, что тебе сейчас не нужно. Ну, допустим, тебя попросили добавить ручку для создания заказа, а ты пошёл ещё и ручку удаления заказа написал. Зачем? Хрен знает, не делай так
А ГДЕ ЖЕ НАРУШАЮТСЯ ВСЕ ЭТИ ПРИНЦИПЫ НА ПРАКТИКЕ?
Да практически на каждом проекте есть нарушения того же SOLID, к примеру у меня на одном из проектов был некий TransferService, который одновременно вычислял среднюю цену, писал событие в брокер, создавал задачу в постгре и отправлял уведомление, и очевидно сервис слишком много делает (нарушение SRP), но когда у тебя маленький проект, то само соблюдение SOLID может только делать код сложнее и руинить сроки разработки
Стоит понимать, что, придя на работу, на проект, у тебя уже будет устоявшаяся кодовая база, и вряд ли она на 100% будет соответствовать всем этим принципам, а менять что-то слишком дорого по времени, да и кажется бессмысленным, потому что всё работает, рефакторинг займёт время, а реальной пользы бизнесу может вообще не принести
Как-то так. Это мой первый технический пост, который я написал руками, буду рад фидбэку, ну и спасибо, что прочитал(-а), это тебе🏆
Кто я | Чат айтишников | Мои услуги
Сегодня хочу рассказать, что это за буквы такие и всегда ли удаётся их применять на работе (спойлер:
SOLID
Пять букв, которые спрашивают на собеседовании. Фактически — это 5 принципов, которых "нужно" придерживаться при написании кода, но это не значит, что ты обязан слепо следовать этим принципам, всегда есть исключения ради бизнес-логики проекта
1. S (Single Responsibility Principle) — принцип единственной ответственности, который означает, что ты должен писать код так, чтобы каждый модуль в твоём приложении отвечал за что-то конкретное, а каждая функция делала что-то одно. Логгер — логгирует, функция заказа — создаёт заказ. Я думаю, тут всё понятно
2. O (Open-Closed Principle) — принцип открытости-закрытости, при котором ты должен по возможности добавлять новое поведение, а не переписывать уже работающий код. То есть суть в том, чтобы проектировать код так, чтобы новое поведение можно было добавлять без постоянного переписывания уже работающей логики
Давай сразу на примере:
У нас есть Transfer с полем Balance, а также метод GetBalance, который просто возвращает этот Balance:
type Transfer struct {
Balance int
}
func (t *Transfer) GetBalance() int {
return t.Balance
}И твоя задача — реализовать метод, который не просто вернёт баланс, но и проверит, что он больше нуля. И вот ты можешь залезть в метод GetBalance и изменить логику в нём, но намного правильнее создать новый метод CheckAndGetBalance и реализовать логику там следующим образом
func (t *Transfer) CheckAndGetBalance() (int, error) {
balance := t.GetBalance()
if balance <= 0 {
return 0, errors.New("check balance failed, balance <= 0")
}
return balance, nil
}То есть ты не меняешь текущую логику, а расширяешь, добавляя новую, потому что если ты изменишь исходный метод GetBalance, ты можешь столкнуться с проблемой, когда его сигнатура и логика поменялась, и теперь тебе нужно поменять обработку из этой функции везде, где эта функция вызывалась. Ещё лучше было бы создать структуру BalanceChecker и вынести логику проверки туда, но суть я объяснил...
3. L (Liskov Substitution Principle) — принцип подстановки Барбары Лисков, который говорит о том, что если у тебя есть код, который работает с абстракцией (с интерфейсом, к примеру), то ты должен иметь возможность менять реализации и программа продолжит работать корректно
Опять же, пример:
Допустим, у нас есть интерфейс Animal, который имеет метод Eat()
type Animal interface {
Eat() string
}Теперь мы создадим структуры Dog (собака) и Puppy (щенок)
type Dog struct {
Name string
}
type Puppy struct {
Name string
Age int
}Теперь я могу реализовать метод Eat() у обеих структур, тем самым они будут реализовывать интерфейс Animal
func (d Dog) Eat() string {
return "I am dog, and I am eating"
}
func (p Puppy) Eat() string {
return "I am puppy, and I am eating"
}А также я создам функцию EatAnimal, которая вообще ничего не будет знать о наших структурах и работать чисто с интерфейсом
func EatAnimal(a Animal) string {
return a.Eat()
}И прикол в том, что если я теперь создам структуру Dog и Puppy, я смогу обе структуры передать в функцию EatAnimal и фактически логика никак не меняется от переданного аргумента, просто будет вызываться разная реализация
func main() {
puppy := Puppy{Name: "puppy", Age: 1}
dog := Dog{Name: "dog"}
EatAnimal(dog)
EatAnimal(puppy)
}И главное: если одна сущность может использоваться вместо другой через общую абстракцию, такая замена не должна ломать ожидаемое поведение программы
При этом недостаточно просто реализовать методы: реализация должна ещё и соблюдать ожидаемый контракт. Если Puppy.Eat() внезапно делает panic, формально интерфейс реализован, но принцип уже нарушается
4. I (Interface Segregation Principle) — принцип разделения интерфейсов, который говорит о том, чтобы вместо одного большого интерфейса делать несколько точечных. Для чего? Для того, чтобы клиент, который опирается на интерфейс, не зависел от ненужных ему методов
Фактически, вместо того, чтобы делать так:
type BaseService interface {
GetUser() User
GetOrder() Order
GetTransfer() Transfer
}
type UserHandler struct {
service BaseService // UserHandler не нуждается в GetOrder и GetTransfer
}Ты делаешь следующим образом:
type UserService interface {
GetUser() User
}
type OrderService interface {
GetOrder() Order
}
type TransferService interface {
GetTransfer() Transfer
}
type UserHandler struct {
service UserService
}Думаю тут понятно, если что, задавай вопросы! Едем дальше
5. D (Dependency Inversion Principle) — принцип инверсии зависимостей, который говорит о том, что высокоуровневые и низкоуровневые модули должны зависеть от абстракций, а не друг от друга. Если непонятно, давай разбирать :)
Допустим, у нас есть сервис переводов:
type TransferService struct {
pgRepository PostgresTransferRepository
}И конкретная реализация репозитория, которая работает только с PostgreSQL:
type PostgresTransferRepository struct {
db *sql.DB
}
func (r PostgresTransferRepository) GetTransfer(id int) Transfer {
// идём в PostgreSQL
return Transfer{}
}Это плохо, потому что мы зависим от конкретной реализации, и если нас завтра попросят поддержать MySQL, то нам придётся добавлять ещё и MySQLRepository в TransferService, а потом ещё и Redis, и MongoDB... короче, чтобы такого не было, мы можем положить в TransferService интерфейс, а не конкретную реализацию:
type TransferRepository interface {
GetTransfer(id int) Transfer
}
type TransferService struct {
repository TransferRepository
}Короче, суть этого принципа в том, что бизнес-логика не должна жёстко зависеть от конкретной базы данных, библиотеки или внешнего сервиса. Вместо этого она зависит от абстракции, а конкретная реализация уже подставляется снаружи
DRY, KISS, YAGNI
DRY (Don't Repeat Yourself) — не повторяй сам себя. Короче, если код повторяется, посмотри, можешь ли ты вынести его в функцию. Также не дублируй одну и ту же логику в разных местах, выноси в общие хелперы/функции
KISS (Keep it simple, stupid) — не усложняй решение просто так, код должен быть максимально простым, насколько это возможно. И вообще, пиши код так, чтобы его было легко читать даже без комментариев (не говорю, что комментарии не нужны)
YAGNI (You Aren't Gonna Need It) — не реализуй то, что тебе сейчас не нужно. Ну, допустим, тебя попросили добавить ручку для создания заказа, а ты пошёл ещё и ручку удаления заказа написал. Зачем? Хрен знает, не делай так
А ГДЕ ЖЕ НАРУШАЮТСЯ ВСЕ ЭТИ ПРИНЦИПЫ НА ПРАКТИКЕ?
Да практически на каждом проекте есть нарушения того же SOLID, к примеру у меня на одном из проектов был некий TransferService, который одновременно вычислял среднюю цену, писал событие в брокер, создавал задачу в постгре и отправлял уведомление, и очевидно сервис слишком много делает (нарушение SRP), но когда у тебя маленький проект, то само соблюдение SOLID может только делать код сложнее и руинить сроки разработки
Стоит понимать, что, придя на работу, на проект, у тебя уже будет устоявшаяся кодовая база, и вряд ли она на 100% будет соответствовать всем этим принципам, а менять что-то слишком дорого по времени, да и кажется бессмысленным, потому что всё работает, рефакторинг займёт время, а реальной пользы бизнесу может вообще не принести
Как-то так. Это мой первый технический пост, который я написал руками, буду рад фидбэку, ну и спасибо, что прочитал(-а), это тебе
Кто я | Чат айтишников | Мои услуги
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍4🔥4
CONTEXT в GO или «ГАЛЯ, У НАС ОТМЕНА» ❌
Всем доброго дня, сегодня разберём, зачем нам вообще пакет «context» в Go и что интересного внутри там есть. Если тебя на собеседовании спрашивают про контекст в гошке и твой ответ звучит примерно так: «ну… это хуйня, которую я в функцию первым аргументом прокидываю», то этот пост для тебя
Что такое context
Это объект, который передаётся между функциями и хранит некую информацию, которая может пригодиться нескольким частям твоего приложения: допустим, к тебе пришёл запрос от клиента на покупку бургера, а у тебя приложение архитектурно построено так:
Handler -> Service -> Repository -> DB (1)
То есть в хэндлере ты принимаешь запрос, вызывая нужный сервис для обработки нового заказа, а сервис в свою очередь идёт в репозиторий, а репозиторий уже обращается к базе данных для создания заказа. Так вот, представим, что в моменте (например, когда исполнение запроса уже дошло до репозитория), пользователь отменил запрос, потому что случайно мискликнул и теперь нажал на крестик. Нужно ли теперь продолжать флоу и идти в базу, создавая заказ? Ну конечно же нет, в этом больше нет смысла
Но сам репозиторий не узнает о том, что запрос был отменён, кто-то ему должен об этом сказать. И вот этот «кто-то» — это и есть context. Сам контекст не несёт никакой бизнес-логики, его основная задача: дать информацию о том, что что-то изменилось, пока мы обрабатывали заказ, то есть дать некий сигнал, на который уже мы должны отреагировать
Как это работает
Схема (1), если накидать немного кода, выглядит примерно так:
А если посмотреть в интерфейс Context, то выглядит он так
В комментариях есть описание каждого метода. Мы поняли, что метод Done() возвращает канал, который закроется, когда context будет отменён, а значит мы можем с помощью инструкции select дожидаться, когда закрытие канала разблокирует наш case и мы вернём ошибку контекста:
В репозитории мы используем метод ExecContext, поэтому гошка на этом уровне разруливает сама, нам нужно лишь передать контекст, если он отменится — отменится и запрос (ну это если упростить, по факту там database/sql передает отмену драйверу, и запрос может быть прерван, если драйвер его поддерживает)
Важный момент, перед тем как пойти дальше: сам по себе context и его отмена не останавливает выполнение функции, он даёт лишь сигнал через канал, а нам нужно на него реагировать
Как нам самим объявлять context?
Пока у нас был только r.Context(), который мы вытащили из http.Request, но нам бы хотелось самим делать контекст, потому что таким образом отменять хочется не только запросы пользователей, но и другие, более внутренние операции
context.Background() создаёт корневой контекст и у него пока нет ни дедлайна, ни функции отмены, ни каких-либо значений, пока мы просто создали оболочку, которую можем куда-либо прокинуть
Есть ещё context.TODO(), но он используется только в том случае, когда ты понимаешь, что он здесь должен быть, но пока не знаешь, какой именно context сюда нужно передать. Например, если ты рефакторишь старый код и ещё не успел протащить нормальный контекст сверху:
Окей, у нас есть контекст. Как его отменять, то есть слать те самые сигналы?
context.WithCancel() — функция, которая возвращает нам новый контекст + функцию отмены этого контекста
Что тут произошло?
В этом кейсе мы отменяем контекст вручную, когда захотим этого сами, но если же мы хотим отменить контекст через какое-то время автоматически, то есть ограничить выполнение функции по времени, то лучше сделать это через context.WithTimeout():
Фактически, WithTimeout от WithCancel отличается тем, что принимает время (time.Duration) вторым аргументом и собственно через это время автоматически отменяется контекст
Внимательный читатель спросит: «а нахрена нам здесь defer cancel(), если ты говоришь, что контекст сам отменится», а я отвечу:
Суть в том, что в примере выше контекст отменяется автоматически только через 2 секунды, но поход в репозиторий (а дальше и в бд) может закончиться сильно раньше, например через 150 мс, и тогда нам этот контекст уже не нужен и его можно отменить раньше, чем запланировано по таймауту
Немного про наследование контекстов
Как вы поняли, во все функции по типу WithCancel и WithTimeout мы передаем уже существующий контекст и на его основе создаём новый, и поэтому тут работает следующее правило: если отменяется контекст, то и отменяются все контексты, которые были созданы от него
Например:
Несмотря на то, что в childCtx есть таймаут в 10 секунд, он отменится вместе с вызовом parentCancel(), причем контекст отменяется сверху вниз, если отменяется childCtx, то parentCtx будет жить
Помимо функции WithTimeout существует ещё и WithDeadline — функция, которая работает так же, как и WithTimeout, но задаёшь ты не время работы контекста, а время, до которого он работает
Вернёмся к Err() у интерфейса Context
Какая конкретно ошибка вернётся при отмене контекста зависит от того, как мы его отменили:
context.Canceled — контекст был отменён через cancel
context.DeadlineExceeded — контекст был отменён через timeout/deadline
Теперь про Value() у интерфейса Context
Мы можем в наш контекст положить значение по ключу, например для логов (про это отдельно), с помощью context.WithValue
А потом, в другом месте приложения, можем вытащить значение по ключу
В качестве домашки почитайте про WithoutCancel, у меня лимиты :)
Кто я | Чат айтишников | Мои услуги
Всем доброго дня, сегодня разберём, зачем нам вообще пакет «context» в Go и что интересного внутри там есть. Если тебя на собеседовании спрашивают про контекст в гошке и твой ответ звучит примерно так: «ну… это хуйня, которую я в функцию первым аргументом прокидываю», то этот пост для тебя
Что такое context
Это объект, который передаётся между функциями и хранит некую информацию, которая может пригодиться нескольким частям твоего приложения: допустим, к тебе пришёл запрос от клиента на покупку бургера, а у тебя приложение архитектурно построено так:
Handler -> Service -> Repository -> DB (1)
То есть в хэндлере ты принимаешь запрос, вызывая нужный сервис для обработки нового заказа, а сервис в свою очередь идёт в репозиторий, а репозиторий уже обращается к базе данных для создания заказа. Так вот, представим, что в моменте (например, когда исполнение запроса уже дошло до репозитория), пользователь отменил запрос, потому что случайно мискликнул и теперь нажал на крестик. Нужно ли теперь продолжать флоу и идти в базу, создавая заказ? Ну конечно же нет, в этом больше нет смысла
Но сам репозиторий не узнает о том, что запрос был отменён, кто-то ему должен об этом сказать. И вот этот «кто-то» — это и есть context. Сам контекст не несёт никакой бизнес-логики, его основная задача: дать информацию о том, что что-то изменилось, пока мы обрабатывали заказ, то есть дать некий сигнал, на который уже мы должны отреагировать
Как это работает
Схема (1), если накидать немного кода, выглядит примерно так:
//handler.go
func Handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
service.CreateOrder(ctx)
}
//service.go
func (s *Service) CreateOrder(ctx context.Context) error {
return s.repository.CreateOrder(ctx)
}
//repo.go
func (r *Repository) CreateOrder(ctx context.Context) error {
_, err := r.db.ExecContext(
ctx, "INSERT INTO orders (...) VALUES (...)",
)
return err
}
А если посмотреть в интерфейс Context, то выглядит он так
type Context interface {
Deadline() (deadline time.Time, ok bool) //возвращает дедлайн (при наличии), после которого контекст должен быть отменён. второй аргумент (ok) и говорит о том, есть ли deadline у данного контекста
Done() <-chan struct{} //возвращает канал, который закроется при отмене контекста (может быть nil)
Err() error //пока канал открыт, возвращает nil, после — возвращает ошибку context.Canceled или context.DeadlineExceeded
Value(key any) any //context может хранить значение по ключу, связанное с ним, а фактически с конкретным запросом или операцией
}В комментариях есть описание каждого метода. Мы поняли, что метод Done() возвращает канал, который закроется, когда context будет отменён, а значит мы можем с помощью инструкции select дожидаться, когда закрытие канала разблокирует наш case и мы вернём ошибку контекста:
//service.go
func (s *Service) CreateOrder(ctx context.Context) error {
select {
case <-ctx.Done(): //этот кейс выберется только тогда, когда канал будет закрыт, в любом другом случае идем в default и в repository
return ctx.Err() //возвращаем причину, по которой контекст отменился
default:
}
return s.repository.CreateOrder(ctx)
}
В репозитории мы используем метод ExecContext, поэтому гошка на этом уровне разруливает сама, нам нужно лишь передать контекст, если он отменится — отменится и запрос (ну это если упростить, по факту там database/sql передает отмену драйверу, и запрос может быть прерван, если драйвер его поддерживает)
Важный момент, перед тем как пойти дальше: сам по себе context и его отмена не останавливает выполнение функции, он даёт лишь сигнал через канал, а нам нужно на него реагировать
Как нам самим объявлять context?
Пока у нас был только r.Context(), который мы вытащили из http.Request, но нам бы хотелось самим делать контекст, потому что таким образом отменять хочется не только запросы пользователей, но и другие, более внутренние операции
func main() {
ctx := context.Background()
service.Run(ctx)
}context.Background() создаёт корневой контекст и у него пока нет ни дедлайна, ни функции отмены, ни каких-либо значений, пока мы просто создали оболочку, которую можем куда-либо прокинуть
Есть ещё context.TODO(), но он используется только в том случае, когда ты понимаешь, что он здесь должен быть, но пока не знаешь, какой именно context сюда нужно передать. Например, если ты рефакторишь старый код и ещё не успел протащить нормальный контекст сверху:
func CreateOrder() error {
ctx := context.TODO()
return repository.CreateOrder(ctx)
}Окей, у нас есть контекст. Как его отменять, то есть слать те самые сигналы?
context.WithCancel() — функция, которая возвращает нам новый контекст + функцию отмены этого контекста
func main() {
ctx := context.Background()
newCtx, cancel := context.WithCancel(ctx)
go worker(newCtx)
cancel()
}
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("останавливаем работу:", ctx.Err())
return
default:
fmt.Println("работаем...")
time.Sleep(time.Second)
}
}
}Что тут произошло?
1. Мы создали корневой контекст через context.Background()
2. С помощью context.WithCancel(ctx) создали дочерний контекст, который мы можем отменить
3. Запустили в отдельной горутине worker, в котором итерируемся в цикле for пока наш контекст, переданный в функцию, не отменился
4. Отменяем этот контекст в функции main
5. Получаем срабатывание кейса с <-ctx.Done() и выход из функции worker
В этом кейсе мы отменяем контекст вручную, когда захотим этого сами, но если же мы хотим отменить контекст через какое-то время автоматически, то есть ограничить выполнение функции по времени, то лучше сделать это через context.WithTimeout():
func (s *Service) CreateOrder(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
return s.repository.CreateOrder(ctx)
}Фактически, WithTimeout от WithCancel отличается тем, что принимает время (time.Duration) вторым аргументом и собственно через это время автоматически отменяется контекст
Внимательный читатель спросит: «а нахрена нам здесь defer cancel(), если ты говоришь, что контекст сам отменится», а я отвечу:
Суть в том, что в примере выше контекст отменяется автоматически только через 2 секунды, но поход в репозиторий (а дальше и в бд) может закончиться сильно раньше, например через 150 мс, и тогда нам этот контекст уже не нужен и его можно отменить раньше, чем запланировано по таймауту
Немного про наследование контекстов
Как вы поняли, во все функции по типу WithCancel и WithTimeout мы передаем уже существующий контекст и на его основе создаём новый, и поэтому тут работает следующее правило: если отменяется контекст, то и отменяются все контексты, которые были созданы от него
Например:
func main() {
parentCtx, parentCancel := context.WithCancel(context.Background())
childCtx, childCancel := context.WithTimeout(parentCtx, 10*time.Second)
defer parentCancel()
defer childCancel()
}Несмотря на то, что в childCtx есть таймаут в 10 секунд, он отменится вместе с вызовом parentCancel(), причем контекст отменяется сверху вниз, если отменяется childCtx, то parentCtx будет жить
Помимо функции WithTimeout существует ещё и WithDeadline — функция, которая работает так же, как и WithTimeout, но задаёшь ты не время работы контекста, а время, до которого он работает
Вернёмся к Err() у интерфейса Context
Какая конкретно ошибка вернётся при отмене контекста зависит от того, как мы его отменили:
context.Canceled — контекст был отменён через cancel
context.DeadlineExceeded — контекст был отменён через timeout/deadline
Теперь про Value() у интерфейса Context
Мы можем в наш контекст положить значение по ключу, например для логов (про это отдельно), с помощью context.WithValue
//main.go
ctx := context.WithValue(ctx, "requestID", "abc-123")
А потом, в другом месте приложения, можем вытащить значение по ключу
//service.go
requestID := ctx.Value("requestID")
В качестве домашки почитайте про WithoutCancel, у меня лимиты :)
Кто я | Чат айтишников | Мои услуги
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12🔥5👍2
Всем привет, выкладываю первый урок из моего курса по Golang 🤓
https://www.youtube.com/watch?v=NzKkQz4w7DU
Ролики планирую выкладывать раз в неделю, ждите :)
Буду рад вашей поддержке, всё по базе, ну и посты в этом канале постараюсь писать параллельно, чтобы закрыть теоретическую базу 🙏🏻❤️
https://www.youtube.com/watch?v=NzKkQz4w7DU
Ролики планирую выкладывать раз в неделю, ждите :)
Буду рад вашей поддержке, всё по базе, ну и посты в этом канале постараюсь писать параллельно, чтобы закрыть теоретическую базу 🙏🏻❤️
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
GOLANG с НУЛЯ. Первый урок: основы языка
В этом видео я базово прошёлся по темам: что такое Go, зачем он нужен, переменные и типы данных, условия и циклы, функции, указатели и подробнее разобрал строки, потому что про них чаще всего любят спрашивать на собеседованиях
Здесь больше по Go и Backend:…
Здесь больше по Go и Backend:…
❤7🔥2👍1