На каком уровне системы обычно осуществляется обработка ошибок?

В данный момент я разделяю API на три слоя: handlers, service и repository. В repository, использующем PostgreSQL, я применяю <a href="https://github.com/jackc/pgx" rel="nofollow">pgx</a>. При выполнении методов Query и QueryRow я сталкиваюсь с ошибкой pgx.ErrNoRows, которую раньше обрабатывал самостоятельно, возвращая nil, 0 или пустую строку. 

При вызове из service я обрабатывал ситуацию с "пустотой", возвращая ошибку, но сейчас решил попробовать возвращать ошибку непосредственно из repository. Однако, это создает определенную вложенность в обработке. 

Ранее я использовал подход, который выглядел иначе. Какой метод вы рекомендовали бы использовать, чтобы избежать глубокой вложенности и упростить дальнейшую разработку? Я не хотел бы вносить изменения позже, поэтому был бы признателен за советы или лучшие практики, которые могли бы помочь.
  • 23 марта 2025 г. 21:05
Ответы на вопрос 2
Обработка ошибок в архитектуре, состоящей из слоев (как в вашем случае: handlers, service и repository), является важным аспектом разработки, и выбор места для обработки ошибок может значительно повлиять на читабельность и поддерживаемость кода.

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

1. **Обработка на уровне репозитория**: 
   - Ваша идея возвращать ошибку непосредственно из repository при возникновении `pgx.ErrNoRows` – это действительно разумный подход. В данном случае вы можете возвращать специфичную ошибку, например, `ErrRecordNotFound` или `NotFoundError`. Это делает поведение более предсказуемым для авторов `service`, которые могут использовать это для специальной обработки.
   
2. **Унификация ошибок**: 
   - Рассмотрите возможность использования единой структуры для всех ошибок, возвращаемых из вашего repository. Это позволит вам легко обрабатывать различные типы ошибок на более высоком уровне. Например, создайте интерфейс `CustomError`, который вы будете реализовывать для всех специфичных ошибок.

3. **Обработка ошибок в слое сервиса**: 
   - В слое `service` вы можете обрабатывать ошибки, возвращаемые из `repository`, и принимать решения на основе их типа. Например, если это `NotFoundError`, можно возвращать соответствующий ответ с кодом 404. Это уменьшает глубину вложенности, так как ошибки обрабатываются на определенном уровне, а не в каждом из слоев.

4. **Логирование**: 
   - Кроме того, важно реализовать логирование ошибок, чтобы вы могли отслеживать проблемы, не выводя их в ответ пользователю. Логирование может происходить на уровне handlers или service.

5. **Использование оберток**: 
   - Рассмотрите возможность создания оберток для частых операций с ошибками. Например, если у вас много операций, связанных с базой данных, вы можете создать функцию-обертку, которая будет выполнять запрос и обрабатывать ошибки, включая только логику, связанную с ошибками.

6. **Документирование и консистентность**: 
   - Обязательно документируйте поведение вашего API в отношении ошибок. Если ваши разработчики будут знать, какие ошибки могут возникать и как с ними работать, это значительно упростит взаимодействие между слоями.

Пример обработки ошибок может выглядеть следующим образом:

```go
// В repository
func (r *UserRepository) GetByID(id string) (User, error) {
    user := User{}
    err := r.db.QueryRow("SELECT ...").Scan(&user.Field)
    if err != nil {
        if err == pgx.ErrNoRows {
            return User{}, ErrRecordNotFound // специализированная ошибка
        }
        return User{}, err
    }
    return user, nil
}

// В service
func (s *UserService) FindUserByID(id string) (User, error) {
    user, err := s.repository.GetByID(id)
    if err != nil {
        if errors.Is(err, ErrRecordNotFound) {
            return User{}, fmt.Errorf("пользователь с id %s не найден", id)
        }
        return User{}, err // другие ошибки не обрабатываются
    }
    return user, nil
}

// В handler
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")
    user, err := h.service.FindUserByID(id)
    if err != nil {
        if errors.Is(err, ErrRecordNotFound) {
            http.Error(w, err.Error(), http.StatusNotFound)
            return
        }
        http.Error(w, "внутренняя ошибка сервиса", http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(user)
}
```

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

https://www.youtube.com/live/p9XiOOU52Qw?si=CO1Sqx...
Похожие вопросы