YAML 파일을 이용해서 환경 설정을 구조화하고 이를 바탕으로 유연하게 동작할 수 있도록 애플리케이션을 만드는 것은 많이 사용되는 패턴입니다. 저도 그런 패턴을 좋아해서 가급적 많은 것들을 YAML 파일로 설정할 수 있도록 하고 실제 애플리케이션 코드에서는 YAML 파일을 읽은 후 이터레이션을 돌거나 값을 참조하는 형태로 구현하고 있습니다. 그리고 이런 YAML 파일을 읽어서 구조체로 접근할 수 있다면 애플리케이션의 코드가 훨씬 가독성이 높아지게 됩니다. 하지만 YAML 파일을 이용한 설정들의 패턴이 워낙 다양하다 보니 필요할 때마다 구글에서 찾아보거나 하는 경우가 많습니다. 그래서 오늘은 제가 그동안 애플리케이션을 만들면서 만났던 다양한 YAML 파일들의 패턴을 살펴보고 이를 구조체로 표현하는 방법에 대해서 정리해 봤습니다.
그럼 하나씩 살펴 보겠습니다.
첫 번째 패턴
첫 번째로 다룰 패턴은 아래와 같습니다.
name: alden
job: sre
아주 간단한 형태의 YAML 파일입니다. 이 패턴은 아래와 같은 구조체로 표현할 수 있습니다.
type Config struct {
Name string `yaml:"name"`
Job string `yaml:"job"`
}
name
과 job
이라는 키가 있고 각각의 키는 문자열을 값으로 가지기 때문에 위와 같이 표현할 수 있습니다. 그리고 이렇게 선언한 구조체는 아래와 같은 코드를 이용해서 출력할 수 있습니다.
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
밑에 alden
의 job
키에는 sre
, devops
라는 두 개의 키가, frank
의 job
키에는 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 |