๋ธ”๋กœ๊ทธ

๐Ÿฆธ๐Ÿปโ€โ™‚๏ธ ์†Œ๊ฐœ: WordPress ์—†๋Š” Headless WordPress

Leonardo Losoviz
์ž‘์„ฑ์ž: Leonardo Losoviz ยท

Matt Mullenweg ๋Œ€ WPEngine ๋…ผ์Ÿ ์ดํ›„, Reddit(๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๊ณณ์—์„œ๋„) WordPress์˜ ๋Œ€์•ˆ์„ ์ฐพ๋Š” ์‚ฌ๋žŒ๋“ค์ด ์ ์  ๋” ๋งŽ์•„์ง€๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋А๊ผˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ WordPress๋ฅผ ๋– ๋‚˜๊ณ  ์‹ถ์–ด์„œ๊ฐ€ ์•„๋‹ˆ๋ผ(์ ์–ด๋„ ๋‹น์žฅ์€ ์•„๋‹ˆ๋”๋ผ๋„), ์–ด๋–ค ์„ ํƒ์ง€๊ฐ€ ์žˆ๋Š”์ง€, ์ž ์žฌ์ ์ธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์–ผ๋งˆ๋‚˜ ์–ด๋ ค์šธ์ง€ ์ดํ•ดํ•˜๊ณ  ์‹ถ์–ด ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์œ„ํ—˜์„ ๋ถ„์‚ฐ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ณ  ์‹ถ์–ด ํ•ฉ๋‹ˆ๋‹ค.

Headless WordPress๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ„๋“ค์„ ์œ„ํ•ด, Gato GraphQL์€ ๋ฉ‹์ง„ ์ƒˆ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: WordPress ์—†๋Š” Headless WordPress.

์ด ํฌ์ŠคํŠธ์—์„œ๋Š” ์ด๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ๊ฐ€๋Šฅํ•œ์ง€ ์„ค๋ช…ํ•˜๊ณ , ๋ฐ๋ชจ ์˜์ƒ์„ ํ†ตํ•ด ์ง์ ‘ ๋ณด์—ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

Gato GraphQL์„ ๋…๋ฆฝ ์‹คํ–‰ํ˜• PHP ์•ฑ์œผ๋กœ ์‹คํ–‰ํ•˜๊ธฐ

Gato GraphQL์€ Composer๋กœ ๊ด€๋ฆฌ๋˜๋Š” ๋…๋ฆฝ ์‹คํ–‰ํ˜• PHP ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์ถ•๋˜์—ˆ์œผ๋ฉฐ, GraphQL ์„œ๋ฒ„๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ชจ๋“  PHP ์ปดํฌ๋„ŒํŠธ๊ฐ€ WordPress์— ์˜์กดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!

๋”ฐ๋ผ์„œ GraphQL ์„œ๋ฒ„๋Š” ๋…๋ฆฝ ์‹คํ–‰ํ˜• PHP ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, WordPress ๊ธฐ๋ฐ˜์ด๋“  ๋‹ค๋ฅธ ๋ฌด์—‡์ด๋“  ์–ด๋–ค PHP ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋„ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŠน์ • ์‚ฌ์šฉ ์‚ฌ๋ก€์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด WordPress ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด, ์ ์–ด๋„ ๊ทธ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋Œ€ํ•ด์„œ๋Š” ๋ฐ”๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์˜์ƒ์—์„œ๋Š” ๊ทธ๋Ÿฌํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์‹œ์—ฐํ•ฉ๋‹ˆ๋‹ค: GitHub API์™€ ์—ฐ๋™ํ•˜์—ฌ ๊ฐœ๋ฐœ ์ค‘์— GitHub Actions์—์„œ ์•„ํ‹ฐํŒฉํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œยท์„ค์น˜ํ•˜๋Š” ๋ชจ์Šต์ž…๋‹ˆ๋‹ค:

WordPress ์—†๋Š” Headless WordPress ๋ฐ๋ชจ: GraphQL ์ฟผ๋ฆฌ ์‹คํ–‰

์˜์ƒ์—์„œ GraphQL ์ฟผ๋ฆฌ๋Š” HTTP ์š”์ฒญ์„ ์‹คํ–‰ํ•˜์—ฌ GitHub Actions์—์„œ ์ƒ์„ฑ๋œ ์ตœ์‹  Gato GraphQL ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์€ ํ’€ ๋ฆฌํ€˜์ŠคํŠธ ๋ณ‘ํ•ฉ ์‹œ ์•„ํ‹ฐํŒฉํŠธ๋กœ ์—…๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

GraphQL ์‘๋‹ต์—์„œ ์–ป์€ ์•„ํ‹ฐํŒฉํŠธ์˜ URL์€ WP-CLI์— ์ฃผ์ž…๋˜์–ด, ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๋กœ์ปฌ DEV ์›น์„œ๋ฒ„์— ์ž๋™์œผ๋กœ ์„ค์น˜๋˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

(์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ด ํฌ์ŠคํŠธ์˜ ๋งˆ์ง€๋ง‰ ์„น์…˜์—์„œ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.)

์ด ์‚ฌ์šฉ ์‚ฌ๋ก€์—์„œ๋Š” WordPress ๋ฐ์ดํ„ฐ์— ์ „ํ˜€ ์ ‘๊ทผํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, GraphQL ์„œ๋ฒ„๋Š” ์ด๋ฏธ ๋…๋ฆฝ ์‹คํ–‰ํ˜• PHP ์•ฑ์œผ๋กœ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•„์š”ํ•˜๋‹ค๋ฉด GitHub Actions ์›Œํฌํ”Œ๋กœ์šฐ ๋‚ด์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

Headless WordPress ์•ฑ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๊ธฐ

WordPress ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ์—๋„, WordPress ์—†์ด ๊ทธ๊ฒƒ์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Gato GraphQL์ด ์ œ๊ณตํ•˜๋Š” GraphQL ์Šคํ‚ค๋งˆ์—๋Š” WordPress ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•„๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค: ํฌ์ŠคํŠธ, ์‚ฌ์šฉ์ž, ๋Œ“๊ธ€, ํƒœ๊ทธ, ์นดํ…Œ๊ณ ๋ฆฌ ๋“ฑ์ž…๋‹ˆ๋‹ค.

WordPress ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” PHP ๋ฆฌ์กธ๋ฒ„ ์ฝ”๋“œ๋Š” WordPress์— ์˜์กดํ•˜๋ฉฐ, ํ•ด๋‹น ์ฝ”๋“œ๋Š” WordPress๊ฐ€ ์•„๋‹Œ ์•ฑ์—์„œ๋Š” ์‹คํ–‰๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ Gato GraphQL์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋ฆฌ์กธ๋ฒ„๋“ค์ด ๊ฐ๊ฐ 2๊ฐœ์˜ ํŒจํ‚ค์ง€๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. "๋ฐ”๋‹๋ผ" PHP ๋ฒ„์ „: ๋ชจ๋“  ๋ฒ”์šฉ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒƒ
  2. WordPress ์ „์šฉ ๋ฒ„์ „: ํ•ด๋‹น ๋ฆฌ์กธ๋ฒ„๋ฅผ ์ถฉ์กฑํ•˜๋Š” WordPress ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์‹ค์ œ ํ˜ธ์ถœ์„ ํฌํ•จํ•˜๋Š” ๊ฒƒ

์˜ˆ๋ฅผ ๋“ค์–ด, ์ด GraphQL ์ฟผ๋ฆฌ์—์„œ:

{
  posts {
    id
    title
  }
}

...ํฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง์€ ๋‹ค์Œ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค:

  1. Root.posts ํ•„๋“œ: ๋ฒ”์šฉ posts ํŒจํ‚ค์ง€์— ์กด์žฌํ•ฉ๋‹ˆ๋‹ค
  2. get_posts ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•œ WordPress์šฉ ํ•ด๊ฒฐ: WordPress ์ „์šฉ posts-wp ํŒจํ‚ค์ง€์— ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

WordPress ์™ธ ํŒจํ‚ค์ง€์™€ WordPress ํŒจํ‚ค์ง€ ๊ฐ„์˜ ์ฝ”๋“œ ๋ถ„ํ• ์€ ์•ฝ 80/20%๋กœ, ์ฆ‰ ์ฝ”๋“œ์˜ 80%๋Š” ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ/CMS์—์„œ๋„ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, 20%์˜ ์ฝ”๋“œ๋งŒ ์žฌ๊ตฌํ˜„ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๋”์šฑ์ด, Gato GraphQL์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์€ ๋ชจ๋“ˆ์„ ํ†ตํ•ด ์ œ๊ณต๋˜๋ฉฐ, ๋ชจ๋“ˆ์€ ์ž์œ ๋กญ๊ฒŒ ํ™œ์„ฑํ™”ยท๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ๋ชจ๋“ˆ
์Šคํ‚ค๋งˆ ๋ชจ๋“ˆ

Modules๋Š” ๋ณด์•ˆ ๋ชฉ์ ์œผ๋กœ ๊ตฌํ˜„๋œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค: ๊ณต๊ฐœ API์—์„œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋…ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด, Users ๋ชจ๋“ˆ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ํ•ด๋‹น ํ•„๋“œ(์˜ˆ: Root.users)๊ฐ€ ์Šคํ‚ค๋งˆ์— ์ถ”๊ฐ€๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ชจ๋“ˆ์€ ๊ธฐ๋ฐ˜ PHP ํŒจํ‚ค์ง€์— ์ง์ ‘ ๋งคํ•‘๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Gato GraphQL์„ ๋…๋ฆฝ ์‹คํ–‰ํ˜• ์•ฑ์œผ๋กœ ์‹คํ–‰ํ•  ๋•Œ, ํ•„์š”ํ•œ ๋ชจ๋“ˆ/ํŒจํ‚ค์ง€๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋กœ๋“œํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ๋กœ๋“œํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํฌ์ŠคํŠธ, ์นดํ…Œ๊ณ ๋ฆฌ, ํƒœ๊ทธ ๋ฐ์ดํ„ฐ๋งŒ ํ‘œ์‹œํ•œ๋‹ค๋ฉด, posts-wp, categories-wp, tags-wp ํŒจํ‚ค์ง€(๋ฐ ๊ทธ ์˜์กด์„ฑ)๋งŒ ๋กœ๋“œํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  WordPress์—์„œ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ(Laravel ๋˜๋Š” Symfony ๋“ฑ)๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ๋•Œ๋„, ๊ทธ 3๊ฐœ์˜ WordPress ์ „์šฉ ํŒจํ‚ค์ง€๋งŒ ์ƒˆ ํ”„๋ ˆ์ž„์›Œํฌ/CMS์— ๋งž๊ฒŒ ์žฌ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๊ณ , ๊ทธ ์™ธ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ, ์˜ค๋Š˜๋ถ€ํ„ฐ Headless WordPress๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ, ๋‚˜์ค‘์— ์ตœ์†Œํ•œ์˜ ๋…ธ๋ ฅ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ๋‚˜ CMS๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์•ˆ์‹ฌ๊ฐ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฅธ API์—์„œ Gato GraphQL๋กœ ์ „ํ™˜ํ•˜๊ธฐ

์ด๋ฏธ Headless WordPress๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์•ฑ์€ WP REST API ๋˜๋Š” WPGraphQL ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.

์•ˆํƒ€๊น๊ฒŒ๋„, ์ด ๋‘ API ์ค‘ ์–ด๋А ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ WordPress์— ์ข…์†๋ฉ๋‹ˆ๋‹ค: WordPress ๋ฐ–์—๋Š” WP REST API๊ฐ€ ์—†์œผ๋ฉฐ, WPGraphQL์€ WordPress ์—†์ด๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹คํ–‰ํžˆ๋„, ๋‘˜ ์ค‘ ์–ด๋А ๊ฒƒ์ด๋“  Gato GraphQL๋กœ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Headless WordPress ์•ฑ์„ WordPress๋กœ๋ถ€ํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด ๊ฒฝ์šฐ ๋‹ค์Œ 2๋‹จ๊ณ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

  1. WP REST API ๋˜๋Š” WPGraphQL์—์„œ Gato GraphQL๋กœ ์ „ํ™˜
  2. ํ•„์š”ํ•œ WordPress ์ „์šฉ ํŒจํ‚ค์ง€ ์žฌ๊ตฌํ˜„

API ์ „ํ™˜์ด ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์ง€๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

WP REST API์—์„œ Gato GraphQL์˜ persisted queries๋กœ

Persisted Queries ํ™•์žฅ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด GraphQL๋กœ ๊ตฌ์„ฑ๋œ REST ์œ ์‚ฌ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐ REST ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•ด ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•ด๋‹น persisted query ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ๋Œ€์‹  ๊ทธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ GraphQL ์ฟผ๋ฆฌ๋Š” REST ์—”๋“œํฌ์ธํŠธ /wp-json/wp/v2/posts/๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

{
  posts {
    id
    date: dateStr(format: "Y-m-d\\TH:i:s")
    modified: modifiedDateStr(format: "Y-m-d\\TH:i:s")
    slug
    status
    link: url
    title: self {
      rendered: title
    }
    content: self {
      rendered: content
    },
    excerpt: self {
      rendered: excerpt
    }
    author
    featured_media: featuredImage
    sticky: isSticky
    categories
    tags
  }
}

API ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•˜๋ฉด persisted query๋ฅผ /graphql-query/wp/v2/posts/ ๊ฒฝ๋กœ๋กœ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ์–ด ์—”๋“œํฌ์ธํŠธ ๋งคํ•‘์ด ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

์ง€์ •๋œ ID์˜ ํฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” REST ์—”๋“œํฌ์ธํŠธ /wp-json/wp/v2/posts/{id}/๋ฅผ ์žฌํ˜„ํ•˜๋ ค๋ฉด, URL ํŒŒ๋ผ๋ฏธํ„ฐ postId์— ํฌ์ŠคํŠธ ID๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ persisted query๋Š” ์—”๋“œํฌ์ธํŠธ /graphql-query/wp/v2/posts/single/?postId={id}๋กœ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

query GetPost($postId: ID!) {
  post(by: { id: $postId }) {
    id
    date: dateStr(format: "Y-m-d\\TH:i:s")
    modified: modifiedDateStr(format: "Y-m-d\\TH:i:s")
    slug
    status
    link: url
    title: self {
      rendered: title
    }
    content: self {
      rendered: content
    },
    excerpt: self {
      rendered: excerpt
    }
    author
    featured_media: featuredImage
    sticky: isSticky
    categories
    tags
  }
}

WPGraphQL์—์„œ Gato GraphQL๋กœ

WPGraphQL๊ณผ Gato GraphQL์˜ GraphQL ์Šคํ‚ค๋งˆ๋Š” ์œ ์‚ฌํ•˜์ง€๋งŒ ์•ฝ๊ฐ„ ๋‹ค๋ฅด๋ฏ€๋กœ ์ ์‘์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Next.js WordPress ์Šคํƒ€ํ„ฐ leoloso/next-wordpress-starter๋Š” WPGraphQL๊ณผ Gato GraphQL ๋ชจ๋‘์—์„œ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์Šคํƒ€ํ„ฐ๋Š” ๋‘ ์„œ๋ฒ„ ๋ชจ๋‘์— ๋™์ผํ•œ JS ๋กœ์ง์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋‹ค๋ฅธ ๊ฒƒ์€ GraphQL ์ฟผ๋ฆฌ๋ฟ์ž…๋‹ˆ๋‹ค.

์ด ์Šคํƒ€ํ„ฐ๋Š” ๋‘ ์„œ๋ฒ„ ๊ฐ„์— ์ฟผ๋ฆฌ๋ฅผ ์ ์‘์‹œํ‚ค๋Š” ์—ฌ๋Ÿฌ ์˜ˆ์‹œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ด WPGraphQL ์ฟผ๋ฆฌ๋Š”:

fragment PostFields on Post {
  id
  categories {
    edges {
      node {
        databaseId
        id
        name
        slug
      }
    }
  }
  databaseId
  date
  isSticky
  postId
  slug
  title
}

...Gato GraphQL์šฉ์œผ๋กœ ์ด๋ ‡๊ฒŒ ์ ์‘๋ฉ๋‹ˆ๋‹ค:

fragment PostFields on Post {
  id
  categories: self {
    edges: categories(pagination: { limit: -1 }) {
      node: self {
        databaseId: id
        id
        name
        slug
      }
    }
  }
  databaseId: id
  date: dateStr
  isSticky
  postId: id
  slug
  title
}

์ƒ์„ธ: Gato GraphQL์„ ๋…๋ฆฝ ์‹คํ–‰ํ˜• PHP ์•ฑ์œผ๋กœ ์‹คํ–‰ํ•˜๊ธฐ

์—ฌ๊ธฐ์„œ๋Š” ์•ž์„œ ๋‚˜์˜จ ๋ฐ๋ชจ ์˜์ƒ์— ๋Œ€ํ•œ ์ƒ์„ธํ•œ ์„ค๋ช…์„ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์‹คํ–‰ํ•  GraphQL ์ฟผ๋ฆฌ๋Š” ํŒŒ์ผ retrieve-github-artifacts.gql์— ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ฟผ๋ฆฌ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜ GITHUB_ACCESS_TOKEN์—์„œ ์•ก์„ธ์Šค ํ† ํฐ์„ ๊ฐ€์ ธ์™€ GitHub API์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ณต๋œ ๋ณ€์ˆ˜๋กœ๋ถ€ํ„ฐ actions/artifacts ์—”๋“œํฌ์ธํŠธ์˜ ์ „์ฒด ๊ฒฝ๋กœ๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ , ์ด์— ๋Œ€ํ•ด HTTP ์š”์ฒญ์„ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

์‘๋‹ต์—์„œ ๊ฐ ์•„ํ‹ฐํŒฉํŠธ ํ•ญ๋ชฉ ๋‚ด์˜ "๋‹ค์šด๋กœ๋“œ URL"์„ ์ถ”์ถœํ•˜๊ณ , ์ด์— ๋Œ€ํ•ด ๋น„๋™๊ธฐ HTTP ์š”์ฒญ์„ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ๊ฐ "๋‹ค์šด๋กœ๋“œ URL"์˜ Location ํ—ค๋”์—์„œ ์‹ค์ œ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅํ•œ ํŒŒ์ผ์˜ URL์„ ์–ป์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ, WP-CLI์— ์ฃผ์ž…ํ•˜๊ธฐ ํŽธํ•˜๋„๋ก ๋ชจ๋“  URL์„ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

# File retrieve-github-artifacts.gql
 
query RetrieveProxyArtifactDownloadURLs(
  $repoOwner: String!
  $repoProject: String!
  $perPage: Int = 1
  $artifactName: String = ""
) {
  githubAccessToken: _env(name: "GITHUB_ACCESS_TOKEN")
    @remove
 
  # Create the authorization header to send to GitHub
  authorizationHeader: _sprintf(
    string: "Bearer %s"
    values: [$__githubAccessToken]
  )
    @remove
 
  # Create the authorization header to send to GitHub
  githubRequestHeaders: _echo(
    value: [
      { name: "Accept", value: "application/vnd.github+json" }
      { name: "Authorization", value: $__authorizationHeader }
    ]
  )
    @remove
    @export(as: "githubRequestHeaders")
 
  githubAPIEndpoint: _sprintf(
    string: "https://api.github.com/repos/%s/%s/actions/artifacts?per_page=%s&name=%s"
    values: [$repoOwner, $repoProject, $perPage, $artifactName]
  )
 
  # Use the field from "Send HTTP Request Fields" to connect to GitHub
  gitHubArtifactData: _sendJSONObjectItemHTTPRequest(
    input: {
      url: $__githubAPIEndpoint
      options: { headers: $__githubRequestHeaders }
    }
  )
    @remove
 
  # Finally just extract the URL from within each "artifacts" item
  gitHubProxyArtifactDownloadURLs: _objectProperty(
    object: $__gitHubArtifactData
    by: { key: "artifacts" }
  )
    @underEachArrayItem(passValueOnwardsAs: "artifactItem")
      @applyField(
        name: "_objectProperty"
        arguments: { object: $artifactItem, by: { key: "archive_download_url" } }
        setResultInResponse: true
      )
    @export(as: "gitHubProxyArtifactDownloadURLs")
}
 
query CreateHTTPRequestInputs
  @depends(on: "RetrieveProxyArtifactDownloadURLs")
{
  httpRequestInputs: _echo(value: $gitHubProxyArtifactDownloadURLs)
    @underEachArrayItem(passValueOnwardsAs: "url")
      @applyField(
        name: "_objectAddEntry"
        arguments: {
          object: {
            options: { headers: $githubRequestHeaders, allowRedirects: null }
          }
          key: "url"
          value: $url
        }
        setResultInResponse: true
      )
    @export(as: "httpRequestInputs")
    @remove
}
 
query RetrieveActualArtifactDownloadURLs
  @depends(on: "CreateHTTPRequestInputs")
{
  _sendHTTPRequests(inputs: $httpRequestInputs) {
    artifactDownloadURL: header(name: "Location")
      @export(as: "artifactDownloadURLs", type: LIST)
  }
}
 
query PrintSpaceSeparatedArtifactDownloadURLs
  @depends(on: "RetrieveActualArtifactDownloadURLs")
{
  spaceSeparatedArtifactDownloadURLs: _arrayJoin(
    array: $artifactDownloadURLs
    separator: " "
  )
}

PHP ๋กœ์ง์€ Gato GraphQL ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ "Power Extensions" ๋ฒˆ๋“ค(HTTP ์š”์ฒญ ์ „์†ก ๋ฐ ๊ธฐํƒ€ ๊ธฐ๋Šฅ์— ํ•„์š”)์—์„œ ์ง์ ‘ ์ฝ”๋“œ๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

๋…๋ฆฝ ์‹คํ–‰ํ˜• PHP ์•ฑ์œผ๋กœ์„œ, ์–ด๋–ค ๋ชจ๋“ˆ์„ ์ดˆ๊ธฐํ™”ํ• ์ง€ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜๊ณ  ๊ธฐ๋ณธ๊ฐ’์ด ์•„๋‹Œ ์„ค์ •์„ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋ชจ๋“ˆ SendHTTPRequests์— https://api.github.com/repos์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ์„ ํ—ˆ์šฉํ•˜๊ณ , ๋ชจ๋“ˆ EnvironmentFields์— ํ™˜๊ฒฝ ๋ณ€์ˆ˜ GITHUB_ACCESS_TOKEN์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

GraphQL ์Šคํ‚ค๋งˆ๋Š” GraphQL ์ฟผ๋ฆฌ๊ฐ€ ์ฒ˜์Œ ์‹คํ–‰๋  ๋•Œ ์ƒ์„ฑ๋˜์–ด ๋””์Šคํฌ์— ์บ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‘ ๋ฒˆ์งธ ์‹คํ–‰๋ถ€ํ„ฐ๋Š” ์Šคํ‚ค๋งˆ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ „ํ˜€ ์‹คํ–‰๋˜์ง€ ์•Š์•„ ์‹คํ–‰ ์†๋„๊ฐ€ ๋นจ๋ผ์ง‘๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ, ๋…๋ฆฝ ์‹คํ–‰ํ˜• ์•ฑ์€ GraphQL ์„œ๋ฒ„๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ , ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ ๋’ค ์‘๋‹ต์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

<?php
// File retrieve-github-artifacts.php
 
declare(strict_types=1);
 
use GraphQLByPoP\GraphQLServer\Server\StandaloneGraphQLServer;
use PoP\Root\Container\ContainerCacheConfiguration;
 
// Load the GraphQL server via the standalone PHP components
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql/vendor/scoper-autoload.php');
 
// Load the PRO extensions via the standalone PHP components
require_once (__DIR__ . '/wordpress/wp-content/plugins/gatographql-power-extensions-bundle/vendor/scoper-autoload.php');
 
// Modules required in the GraphQL query
$moduleClasses = [
  \PoPSchema\EnvironmentFields\Module::class,
  \PoPSchema\FunctionFields\Module::class,
  \GraphQLByPoP\ExportDirective\Module::class,
  \GraphQLByPoP\DependsOnOperationsDirective\Module::class,
  \GraphQLByPoP\RemoveDirective\Module::class,
  \PoPSchema\ApplyFieldDirective\Module::class,
  \PoPSchema\SendHTTPRequests\Module::class,
  \PoPSchema\ConditionalMetaDirectives\Module::class,
  \PoPSchema\DataIterationMetaDirectives\Module::class,
];
 
// Configure the modules
$moduleClassConfiguration = [
  \PoP\GraphQLParser\Module::class => [
    \PoP\GraphQLParser\Environment::ENABLE_MULTIPLE_QUERY_EXECUTION => true,
    \PoP\GraphQLParser\Environment::USE_LAST_OPERATION_IN_DOCUMENT_FOR_MULTIPLE_QUERY_EXECUTION_WHEN_OPERATION_NAME_NOT_PROVIDED => true,
    \PoP\GraphQLParser\Environment::ENABLE_RESOLVED_FIELD_VARIABLE_REFERENCES => true,
    \PoP\GraphQLParser\Environment::ENABLE_COMPOSABLE_DIRECTIVES => true,
  ],
  \PoPSchema\SendHTTPRequests\Module::class => [
    \PoPSchema\SendHTTPRequests\Environment::SEND_HTTP_REQUEST_URL_ENTRIES => [
      '#https://api.github.com/repos/(.*)#',
    ],
  ],
  \PoPSchema\EnvironmentFields\Module::class => [
    \PoPSchema\EnvironmentFields\Environment::ENVIRONMENT_VARIABLE_OR_PHP_CONSTANT_ENTRIES => [
      'GITHUB_ACCESS_TOKEN',
    ],
  ],
];
 
// Cache the schema to disk, to speed-up execution from the 2nd time onwards
$containerCacheConfiguration = new ContainerCacheConfiguration('MyGraphQLServer', true, 'retrieve-github-artifacts', __DIR__ . '/tmp');
 
// Initialize the server
$graphQLServer = new StandaloneGraphQLServer($moduleClasses, $moduleClassConfiguration, [], [], $containerCacheConfiguration);
 
/**
 * GraphQL query to execute, stored in its own .gql file
 *
 * @var string
 */
$query = file_get_contents(__DIR__ . '/retrieve-github-artifacts.gql');
 
// GraphQL variables
$variables = [
  'repoOwner' => 'GatoGraphQL',
  'repoProject' => 'GatoGraphQL',
  'perPage' => 3
];
 
// Execute the query
$response = $graphQLServer->execute(
  $query,
  $variables,
);
 
// Print the response
echo $response->getContent();

GraphQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค(jq๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JSON ์ถœ๋ ฅ์„ ๋ณด๊ธฐ ์ข‹๊ฒŒ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค):

php retrieve-github-artifacts.php | jq

๋งˆ์ง€๋ง‰์œผ๋กœ, GraphQL ์‘๋‹ต์—์„œ ์•„ํ‹ฐํŒฉํŠธ URL์„ ์ถ”์ถœํ•˜์—ฌ WP-CLI์— ์ฃผ์ž…ํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค:

GITHUB_ARTIFACT_URLS=$(php retrieve-github-artifacts.php \
  | grep -E -o '"spaceSeparatedArtifactDownloadURLs\":"(.*)"' \
  | cut -d':' -f2- | cut -d'"' -f2- | rev | cut -d'"' -f2- | rev \
  | sed 's/\\\//\//g')
wp plugin install ${GITHUB_ARTIFACT_URLS} --force --activate

์˜์ƒ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋“ฏ์ด, WordPress ์—†์ด Gato GraphQL์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Œ์ด ์ž…์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


๋‰ด์Šค๋ ˆํ„ฐ ๊ตฌ๋…ํ•˜๊ธฐ

Gato GraphQL์˜ ๋ชจ๋“  ์—…๋ฐ์ดํŠธ๋ฅผ ๋†“์น˜์ง€ ๋งˆ์„ธ์š”.