Golang에서 CLI 애플리케이션의 오류 처리

15847 단어 go
Go에서 일부 CLI 애플리케이션을 개발할 때 저는 항상 main.go 파일을 "내 애플리케이션의 입력 및 출력 포트"로 간주합니다.

왜 입력 포트입니까? 이것은 main.go 파일에 있으며, 이 파일을 컴파일하여 응용 프로그램의 실행 파일을 생성하고 여기에서 다른 모든 패키지를 "바인딩"합니다. main.go는 종속성을 시작하고 비즈니스 논리를 수행하는 패키지를 구성 및 호출하는 곳입니다.

예를 들어:

package main

import (
    "database/sql"
    "errors"
    "fmt"
    "log"
    "os"

    "github.com/eminetto/clean-architecture-go-v2/infrastructure/repository"
    "github.com/eminetto/clean-architecture-go-v2/usecase/book"

    "github.com/eminetto/clean-architecture-go-v2/config"
    _ "github.com/go-sql-driver/mysql"

    "github.com/eminetto/clean-architecture-go-v2/pkg/metric"
)

func handleParams() (string, error) {
    if len(os.Args) < 2 {
        return "", errors.New("Invalid query")
    }
    return os.Args[1], nil
}

func main() {
    metricService, err := metric.NewPrometheusService()
    if err != nil {
        log.Fatal(err.Error())
    }
    appMetric := metric.NewCLI("search")
    appMetric.Started()
    query, err := handleParams()
    if err != nil {
        log.Fatal(err.Error())
    }

    dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true", config.DB_USER, config.DB_PASSWORD, config.DB_HOST, config.DB_DATABASE)
    db, err := sql.Open("mysql", dataSourceName)
    if err != nil {
        log.Fatal(err.Error())
    }
    defer db.Close()
    repo := repository.NewBookMySQL(db)
    service := book.NewService(repo)
    all, err := service.SearchBooks(query)
    if err != nil {
        log.Fatal(err)
    }

    //other logic to handle the data

    appMetric.Finished()
    err = metricService.SaveCLI(appMetric)
    if err != nil {
        log.Fatal(err)
    }
}


여기에서 데이터베이스에 대한 연결을 구성하고, 서비스를 인스턴스화하고, 종속성을 전달하는 등의 작업을 수행합니다.

그리고 왜 애플리케이션의 출력 포트입니까? main.go의 다음 스니펫을 고려하십시오.

    repo := repository.NewBookMySQL(db)
    service := book.NewService(repo)
    all, err := service.SearchBooks(query)
    if err != nil {
        log.Fatal(err)
    }

SearchBooks에서 Service 함수의 내용을 분석해 보겠습니다.

func (s *Service) SearchBooks(query string) ([]*entity.Book, error) {
    books, err := s.repo.Search(strings.ToLower(query))
    if err != nil {
        return nil, fmt.Errorf("executing search: %w", err)
    }
    if len(books) == 0 {
        return nil, entity.ErrNotFound
    }
    return books, nil
}


저장소의 다른 함수Search를 호출합니다. 이 함수의 코드는 다음과 같습니다.

func (r *BookMySQL) Search(query string) ([]*entity.Book, error) {
    stmt, err := r.db.Prepare(`select id, title, author, pages, quantity, created_at from book where title like ?`)
    if err != nil {
        return nil, err
    }
    var books []*entity.Book
    rows, err := stmt.Query("%" + query + "%")
    if err != nil {
        return nil, fmt.Errorf("parsing query: %w", err)
    }
    for rows.Next() {
        var b entity.Book
        err = rows.Scan(&b.ID, &b.Title, &b.Author, &b.Pages, &b.Quantity, &b.CreatedAt)
        if err != nil {
            return nil, fmt.Errorf("scan: %w", err)
        }
        books = append(books, &b)
    }

    return books, nil
}


이 두 함수는 흐름을 중단하고 오류 수신 시 가능한 한 빨리 반환한다는 공통점이 있습니다. 그들은 panic 또는 os.Exit 와 같은 일부 기능을 사용하여 실행을 기록하거나 중지하려고 시도하지 않습니다. 이 절차는 main.go의 책임입니다. 이 예는 실행만 하지만log.Fatal(err), 일부 외부 서비스에 로그를 보내거나 모니터링을 위해 일부 경고를 생성하는 것과 같은 고급 논리를 사용할 수 있습니다. 이렇게 하면 main.go 에서 처리가 중앙 집중화되므로 메트릭을 수집하고 고급 오류 처리 등을 수행하는 것이 훨씬 쉽습니다.

내부 함수에서 실행os.Exit할 때 특히 주의하십시오. os.Exit를 사용하면 응용 프로그램이 즉시 중지되고 defer에서 사용한 모든 main.go가 무시됩니다. 이 예에서 SearchBooks 함수가 os.Exit를 실행하면 defer db.Close()main.go가 무시되어 데이터베이스에 문제가 발생할 수 있습니다.

이것이 권장되는 커뮤니티 표준이라는 문서를 읽은 기억이 없지만 성공적으로 사용한 사례입니다. 이 접근법에 동의하십니까? 다른 의견도 환영합니다.

좋은 웹페이지 즐겨찾기