IT/GoLang

Go 언어에서 YAML 파일을 구조체로 표현하기 위한 일곱가지 패턴

Aaron's papa 2022. 1. 17. 22:29
반응형

YAML 파일을 이용해서 환경 설정을 구조화하고 이를 바탕으로 유연하게 동작할 수 있도록 애플리케이션을 만드는 것은 많이 사용되는 패턴입니다. 저도 그런 패턴을 좋아해서 가급적 많은 것들을 YAML 파일로 설정할 수 있도록 하고 실제 애플리케이션 코드에서는 YAML 파일을 읽은 후 이터레이션을 돌거나 값을 참조하는 형태로 구현하고 있습니다. 그리고 이런 YAML 파일을 읽어서 구조체로 접근할 수 있다면 애플리케이션의 코드가 훨씬 가독성이 높아지게 됩니다. 하지만 YAML 파일을 이용한 설정들의 패턴이 워낙 다양하다 보니 필요할 때마다 구글에서 찾아보거나 하는 경우가 많습니다. 그래서 오늘은 제가 그동안 애플리케이션을 만들면서 만났던 다양한 YAML 파일들의 패턴을 살펴보고 이를 구조체로 표현하는 방법에 대해서 정리해 봤습니다.

그럼 하나씩 살펴 보겠습니다.


첫 번째 패턴

첫 번째로 다룰 패턴은 아래와 같습니다.

name: alden
job: sre

아주 간단한 형태의 YAML 파일입니다. 이 패턴은 아래와 같은 구조체로 표현할 수 있습니다.

type Config struct {
    Name string `yaml:"name"`
    Job  string `yaml:"job"`
}

namejob이라는 키가 있고 각각의 키는 문자열을 값으로 가지기 때문에 위와 같이 표현할 수 있습니다. 그리고 이렇게 선언한 구조체는 아래와 같은 코드를 이용해서 출력할 수 있습니다.

Go 언어에서 구조체의 멤버 변수들은 대문자로 시작해야 합니다.

func main() {
    var config Config

    yamlFile, _ := ioutil.ReadFile("config.yaml")
    err := yaml.Unmarshal(yamlFile, &config)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(config.Name)
    fmt.Println(config.Job)
}

 


두 번째 패턴

두 번째로 다룰 패턴은 아래와 같습니다.

name: alden
job:
- sre
- devops
- go

두번째 패턴은 첫 번째 패턴과 키는 동일하지만 안에 들어가는 값이 다릅니다. job 키가 이제 문자열이 아니고 문자열 배열이 들어가게 됩니다. 위 YAML 파일에서 보면 job 키에는 sre, devops, go라는 문자열이 배열의 형태로 지정되어 있죠. 이 패턴은 아래와 같이 표현할 수 있습니다.

type Config struct {
    Name string   `yaml:"name"`
    Job  []string `yaml:"job"`
}

job 키를 string이 아닌 문자열 배열을 표현할 수 있도록 []string으로 표현합니다.


세 번째 패턴

세 번째로 다룰 패턴은 아래와 같습니다.

users:
- name: alden
  job: sre
- name: frank
  job: devops

이번엔 users라는 최상단 키가 존재하고 그 하위로 구조체 배열이 값으로 들어갑니다. 두번째 패턴에서는 문자열 배열 이었지만 이번에는 구조체 배열이 값으로 들어 갑니다. 먼저 users 키 하위로 들어가는 구조체들의 배열을 표현하기 위해 구조체 먼저 아래와 같이 표현할 수 있습니다.

type User struct {
    Name string `yaml:"name"`
    Job  string `yaml:"job"`
}

그리고 users 키 에는 user 구조체가 배열로 들어가기 때문에 아래와 같이 표현 할 수 있습니다.

type Config struct {
    Users []User `yaml:"users"`
}

따라서 세 번째 패턴은 두 개의 코드를 합쳐서 아래와 같이 표현할 수 있습니다.

type Config struct {
    Users []User `yaml:"users"`
}

type User struct {
    Name string `yaml:"name"`
    Job  string `yaml:"job"`
}

 


네 번째 패턴

네 번째로 다룰 패턴은 아래와 같습니다.

users:
- name: alden
  job:
  - sre
  - devops
- name: frank
  job:
  - devops
  - sre
  - genius

세 번째 패턴과 비슷하지만 users 하단에 들어가는 구조체의 job 키가 문자열이 아닌 문자열 배열로 바뀌었습니다. 따라서 아래와 같이 표현할 수 있습니다.

type Config struct {
    Users []User `yaml:"users"`
}

type User struct {
    Name string   `yaml:"name"`
    Job  []string `yaml:"job"`
}

 


다섯 번째 패턴

다섯 번째로 살펴볼 패턴은 아래와 같습니다.

- name: alden
  job:
  - sre
  - devops
- name: frank
  job:
  - devops
  - sre
  - genius

개인적으로는 시작점을 어떻게 잡아야 할지를 모르겠어서 다섯번째 패턴을 학습할 때 어려웠습니다. 네 번째 패턴과 비슷해 보이지만 users 라는 최상단 키 없이 바로 배열이 시작되기 때문입니다. 이 패턴은 아래와 같이 표현할 수 있습니다.

type Config []User

type User struct {
    Name string   `yaml:"name"`
    Job  []string `yaml:"job"`
}

네 번째 패턴과 달라진 게 보이시나요? Config 가 구조체 였던 네번째 패턴과는 다르게 다섯 번째 패턴의 Config는 구조체가 아니고 user의 배열로 표현할 수 있습니다.


여섯 번째 패턴

여섯 번째로 살펴볼 패턴은 아래와 같습니다. 여섯번째 패턴의 경우는 키가 다이내믹하게 변경된다는 특징이 있습니다.

users:
- name: alden
  job:
    sre: basic
    devops: basic
- name: frank
  job:
    devops: fluent
    sre: fluent
    genius: fluent

YAML 파일을 보면 job 밑에 aldenjob 키에는 sre, devops라는 두 개의 키가, frankjob 키에는 devops, sre, genius 이렇게 세 개의 키가 있습니다. 즉 키가 가변적이라는 이야기입니다. 그리고 가변적인 키는 문자열을 값으로 가집니다. 이 패턴은 아래와 같이 표현할 수 있습니다.

type Config struct {
    Users []User `yaml:"users"`
}

type User struct {
    Name string            `yaml:"name"`
    Job  map[string]string `yaml:"job"`
}

job이 가변 키를 가지기 때문에 동적으로 변경할 수 있도록 map 구조체를 사용합니다. 그리고 가변 키들이 map의 키가 될 수 있도록 문자열 키와 문자열 값을 가진 map을 만들어 줍니다.

그리고 이렇게 키가 가변적일 경우에는 어떻게 이터레이션을 돌 수 있을까요? 아래와 같이 for ~ range 구문의 key를 이용해서 이터레이션을 돌 수 있습니다.

func main() {
    var config Config

    yamlFile, _ := ioutil.ReadFile("config.yaml")
    err := yaml.Unmarshal(yamlFile, &config)
    if err != nil {
        fmt.Println(err)
    }

    for _, user := range config.Users {
        fmt.Println(user.Name)
        for key, value := range user.Job {
            fmt.Printf("%s : %s\n", key, value)
        }
    }
}

출력 결과는 아래와 같습니다.

alden
sre : basic
devops : basic
frank
sre : fluent
genius : fluent
devops : fluent

 


일곱 번째 패턴

이제 마지막 일곱 번째로 살펴볼 패턴은 아래와 같습니다.

users:
- name: alden
  job:
    sre:
    - basic
    - fluent
    devops:
    - basic
- name: frank
  job:
    devops:
    - fluent
    sre:
    - fluent
    genius:
    - fluent

여섯 번째 패턴에서 더 발전한 패턴입니다. 가변 키로 늘어나는 키들이 단순 문자열이 아닌 문자열 배열을 가지게 되는 패턴 입니다. 이 패턴은 아래와 같이 표현할 수 있습니다.

type Config struct {
    Users []User `yaml:"users"`
}

type User struct {
    Name string              `yaml:"name"`
    Job  map[string][]string `yaml:"job"`
}

job 멤버 변수가 문자열 배열을 값으로 가질 수 있도록 []string을 붙여 줍니다.


마치며

이상 일곱 가지 YAML 파일들의 패턴을 살펴 봤습니다. 아마 대부분의 YAML 파일이 위에 있는 일곱가지 중 하나에 속하게 될 겁니다. 혹시라도 빠진 패턴이 있다면 알려 주세요. 업데이트하겠습니다~ 이 글을 통해 Go 언어로 개발하시는 분들이 YAML 파일 파싱을 위해 구글링 하는 시간을 줄여 주었으면 좋겠네요~ 읽어 주셔서 감사합니다.

반응형

'IT > GoLang' 카테고리의 다른 글

세번째 글 - 패키지 만들기  (0) 2021.10.04
두번째 글 - 함수 사용하기  (0) 2021.09.25
첫번째 글 - Hello World on API Gateway  (0) 2021.09.22