Upgrade to Pro — share decks privately, control downloads, hide ads and more …

그럼에도 불구하고 Elixir

kakao
December 09, 2022

그럼에도 불구하고 Elixir

#함수형프로그래밍 #DeveloperExperience

이번 세션에는 카카오워크 서버 개발에 주로 사용하는 함수형 언어 엘릭서에 대해 소개해 드리고 여러 어려움에도 불구하고 저희가 엘릭서를 계속 사용하도록 이끌어준, 엘릭서의 실용주의적 특징들을 자세히 소개해드리도록 하겠습니다.

발표자 : walter.s
카카오엔터프라이즈에서 카카오워크 서버를 개발하는 walter 입니다. 실용주의를 추구하고 암벽을 오릅니다.

woogie.choi
카카오엔터프라이즈에서 카카오워크 서버를 개발하는 woogie 입니다. 재미있는 개발을 추구합니다.

kakao

December 09, 2022
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. 그럼에도 불구하고 Elixir 서동현 walter.s 최병욱 woogie.choi 카카오엔터프라이즈 Copyright 2022.

    Kakao Corp. All rights reserved. Redistribution or public display is not permitted without written permission from Kakao. if(kakao)2022 카카오워크 서버팀의 Elixir 실용주의 프로그래밍
  2. 함수형 언어 Elixir 소개 - 카카오워크 서버의 주 언어 중

    하나 - 올해로 10주년을 맞은 신생 언어 - 언어 설계부터 다양한 언어들의 장점을 토대로
  3. 함수형 언어 Elixir 소개 - 신뢰성을 보장하는 Erlang 기반 -

    쉽고 직관적인 Ruby 문법과 유사 - Clojure의 메타프로그래밍과 다형성1)
  4. 함수형 언어 Elixir 소개 - 신생 언어이지만 높은 완성도와 안정성

    - 높은 버전 호환성 - 앞으로도 언어의 큰 변경점 없을 것
  5. 카카오워크에서는 Elixir를 - 서비스 초기부터 프로토타입을 릴리즈하면서 개발 - 2년

    넘게 엘릭서 서버들을 운영중 - 엘릭서의 특성을 살릴 수 있는 곳에 활용하기 위해 노력 - 메인 API 서버 등 여러 곳에 Elixir 사용 중
  6. - 다양한 언어와 기술스택 환경 - 개발자가 언어를 넘나들며 개발

    - 객체지향에서 함수형으로 패러다임 전환의 어려움 2) 개발자 채용과 훈련 문제
  7. - Erlang VM을 k8s 컨테이너 환경 위에서 운영 - 분산

    클러스터 환경에서 배포 운용, 최적화 노하우 부족 - 최근에는 다양한 레퍼런스 참고 가능 운영 노하우와 레퍼런스 부족
  8. - 선택의 순간들과 다양한 대안들 - 비교적 레퍼런스가 풍부한 Java,

    Kotlin - 비슷하게 분산처리와 동시성에 강한 golang 다른 길은 있었지만
  9. - 정기적인 Elixir 스터디 - 한국 Elixir 밋업 참여 -

    Elixir 오픈소스 라이브러리들에 컨트리뷰트 - 더 잘 사용하기 위해 노력중 그래도 선택은 Elixir
  10. 카카오워크가 경험하는 생산성 - 최소 기능 제품(MVP) 까지 반년 이내

    - 지속적인 유지보수가 쉬운 코드 구조 - 소프트웨어 결함 방어 및 배포된 코드 실시간 수정 가능
  11. Elixir의 함수형 프로그래밍 특징 - 변환 프로그래밍: 프로그램은 데이터를 변환하는

    것이 중심4) - 데이터를 변환하는 것은 함수이며 함수들의 조합이 프로그램 - 함수들을 작고 한가지 목적에만 집중하게 만들수록, 더 유연하고 테스트 쉬운 구조 - 로직 설계의 변경은 함수의 재조합
  12. Pattern Matching - 함수형 언어에서의 선언적인 흐름제어 - Elixir는 동적

    타입의 패턴매칭 def get_y(position) do case position do {x, y} -> {:ok, y} _other -> :error end end
  13. interface MessageSender { public void sendMessage(String message); } class User

    implements MessageSender { public void sendMessage(String message) { // ঐഐച ଼੐ } } class Bot implements MessageSender { public void sendMessage(String message) { // ... } }
  14. defmodule MessageSender do # User def send_message(%{type: :user} = user,

    message) do # ঐഐച end # Bot def send_message(%{type: :bot} = bot, message) do # ... end end
  15. defmodule MessageSender do def send_message(%User{} = user, message) do #

    ঐഐച end def send_message(%Bot{} = bot, message) do # ... end end
  16. defmodule MessageSender do def send_message(%User{} = user, message) do #

    ঐഐച end def send_message(%Bot{} = bot, message) do # ... end def send_message(something_else, message) do # handle error? end end
  17. 파이프라인: Elixir의 파이프 연산자 |> - 유닉스의 파이프 명령어와 유사

    - 파이프 연산자로 연결된 구조: 파이프라인 - 함수들의 조합 "file.txt" |> File.read!() |> String.split("\n") |> Enum.sort() |> Enum.uniq() |> Enum.take(4) cat file.txt | sort | uniq | head -4 Enum.take(Enum.uniq(Enum.sort(String.split(File.read!("file.txt"), "\n"))),4)
  18. 파이프라인 예제: 장기 미접속 유저들에게 리텐션 메일 보내기 1. 주어진

    유저 목록 2. 각 유저의 마지막 로그인 기록 조회 3. 로그인 기록이 30일 이전인 유저들만 필터링 4. 유저들의 이메일 주소와 이름만 추출 5. 유저들에게 이메일 전송
  19. 파이프라인 예제: 장기 미접속 유저들에게 리텐션 메일 보내기 1. 주어진

    유저 목록 2. 각 유저의 마지막 로그인 기록 조회 3. 로그인 기록이 30일 이전인 유저들만 필터링 4. 유저들의 이메일 주소와 이름만 추출 5. 유저들에게 이메일 전송 def retention_users(user_list) do user_list # 1 |> zip_with_login_records() # 2 |> filter_login_before_days(30) # 3 |> extract_email_and_names() # 4 |> send_retention_emails() # 5 end
  20. def retention_users(user_list) do user_list # 1 |> zip_with_login_records() # 2

    |> filter_login_before_days(30) # 3 |> extract_email_and_names() # 4 |> send_retention_emails() # 5 end def send_retention_emails([]), do: [] def send_retention_emails({:error, reason} = error) do error end def send_retention_emails(user_list) when is_list(user_list) do # ... end
  21. SQL Query도 파이프라인 # ౠ੿ orgী ࣘೠ ഝࢿ ਬ੷ ݾ۾ਸ

    ಕ੉૚ਵ۽ ઑഥೞח ௪ܻ def get_active_users_by_paging(org_id, cursor) do User |> active() |> by_org(org_id) |> after_cursor(cursor) # ... |> Repo.all() end defp active(query) do query |> where([u], u.status != :deleted) end defp by_org(query, org_id) do query |> where([u], u.org_id == ^org_id) end
  22. - Phoenix: 엘릭서의 웹 프레임워크 - Phoenix의 모든 레이어는 아래와

    같은 파이프라인으로 표현 가능 5) connection |> endpoint() |> router() |> pipelines() |> controller() 프레임워크도 파이프라인이다
  23. - Phoenix의 모든 API는 커넥션을 첫번째 인자로 받으며 다른 함수를

    호출 - 파이프라인 구조로 프레임워크도 직관적 이해 connection |> endpoint() |> router() |> pipelines() |> controller() 프레임워크도 파이프라인이다
  24. - 함수형 프로그래밍: 변환 프로그래밍 관점 - 패턴 매칭 +

    파이프라인 : 직관적이고 유연한 스타일의 코드 - 패턴 매칭: 데이터 형태에 집중하여 함수 설계 - 파이프라인: 함수를 조합하는 방법이자 변환 프로그래밍의 핵심 - 비즈니스 로직부터 프레임워크까지 파이프라인 구조 함수형 프로그래밍 Recap
  25. - 개발자가 진짜 해결하고자 하는 문제에 집중하도록 - 코드 구조를

    단순화하고 변경이 쉽게 함수형 프로그래밍과 실용주의
  26. 개발자 경험과 생산성 - 개발자가 개발을 하며 겪는 경험의 만족도

    - 가능한 쉽고 직관적으로 이해하고 사용 가능해야 한다
  27. 개발자 경험과 생산성 - UX와 차이: 개발자 숙련에 따라 더

    다양한 선택지 제공 - 좋은 개발자 경험은 개발 생산성을 높인다
  28. Phoenix의 도메인 특화 문법 defmodule HelloWeb.Router do use Phoenix.Router pipeline

    :browser do plug :accepts, ["html"] plug :fetch_session plug :protect_from_forgery end scope "/", HelloWeb do pipe_through :browser get "/", PageController, :index resources "/users", UserController, only: [:index, :show, :new, :create] end end
  29. Elixir와 Erlang의 만족도 차이 Most Loved Languages - Elixir는 Erlang과

    많은 유사점 - 그러나 두 언어 사용자들의 큰 만족도 차이 - 엘릭서의 편리하고 이해하기 쉬운 문법 - 메타프로그래밍 덕분에 가능한 문법
  30. Elixir의 메타프로그래밍과 매크로 - 메타프로그래밍: 코드로 코드를 작성하는 방법 -

    고수준의 우아한 문법(매크로)으로 저수준의 코드 조작 - 매크로: 코드를 조작 가능한 함수 - 언어의 코어 스펙이 작고 매크로를 활용해 스스로 확장되는 방식 - 기본 문법들 대부분이 매크로
  31. def parse(data) do Logger.debug("Parse the data #{data}") # ... end

    매크로와 두마리 토끼: 성능과 우아함 - 컴파일 타임에 코드 최적화: 성능 향상과 깔끔한 코드를 동시에 - 엘릭서 표준 라이브러리의 Logger 모듈의 디버깅 함수
  32. 매크로와 두마리 토끼: 성능과 우아함 - 엘릭서 1.12에서 추가된 tap,

    then 매크로 "hello world" |> (fn x -> IO.puts(x) x end).() |> (&Regex.scan(~r/\w+/, &1)).()
  33. "hello world" |> tap(&IO.puts/1) |> then(&Regex.scan(~r/\w+/, &1)) 매크로와 두마리 토끼:

    성능과 우아함 - 엘릭서 1.12에서 추가된 tap, then 매크로
  34. 프레임워크의 DSL 문법 defmodule MyApp.User do use Ecto.Schema schema "users"

    do field :name, :string field :nickname, :string field :avatar_url, :string end end defmodule MyAppWeb.Types.User do use Absinthe.Schema.Notation @desc "User Type" node object(:user) do field :name, non_null(:string) field :nickname, :string field :avatar_url, :string end end Ecto (Database Schema) Absinthe (GraphQL Schema)
  35. 메타프로그래밍: 직접 만드는 DSL defmodule MyWeb.Schemas.User do def schema do

    %Schema{ title: "User", type: :object, required: [:id, :name, :status], properties: %{ id: %Schema{type: :integer}, name: %Schema{type: :string}, status: %Schema{type: :string} } } end end defmodule MyWeb.Schemas.User do use OpenApiSpex.Schemax @required [:id, :name, :status] schema "User" do property :id, :integer property :name, :string property :status, :string end end 라이브러리 기존 문법 직접 만든 DSL 문법
  36. - 강력하고 우아한 메타프로그래밍 - 매크로를 통해 스스로 확장, 안정적이고

    성숙된 문법들 - 프레임워크들이 직관적인 선언형 문법 제공 - 필요한 경우 적절히 사용하여 자유롭게 원하는 문법 창조 - 단, 매크로는 양날의 검 메타프로그래밍 Recap
  37. 오류의 종류 - NullPointException - IndexOutOfBoundsException - XXXException - 500

    Internal server error - OutOfMemoryError - 개발자의 실수 - ETC SOMETHING WENT WRONG!
  38. def get_user(id) do if !is_integer(id) or id < 1 do

    raise IllegalArgumentException end try do user = Http.get(“https://api.account.com/users/#{id}") if is_nil(user) do raise NotFoundException end user catch error in HttpException -> raise CustomExternalException end end
  39. 방어적 프로그래밍의 단점 - 복잡해진 코드로 인한 낮은 가독성 -

    코드 작성과 익셉션 정의 등으로 인한 많은 비용 발생 - 그럼에도 예외가 없다고 확신 할 수 없음 - 해결하려는 문제에 집중하지 못함 SOMETHING WENT WRONG! def get_user(id) do if !is_integer(id) or id < 1 do raise IllegalArgumentException end try do user = Http.get( “https://api.account.com/users/#{id}” ) if is_nil(user) do raise NotFoundException end user catch error in HttpException -> raise CustomExternalException end end
  40. 액터 동시성 모델 - 엘릭서에서 액터는 ‘프로세스’ - 프로세스는 특정

    작업을 수행 - 서로 격리된 프로세스들은 메세지를 보내 소통 - 프로세스는 ErlangVM위에서 돌아가며 자원이 매우 적게 듬
  41. 사용자 일괄 등록 예제 1. 엑셀을 파싱하여 등록할 사용자 정보

    목록 추출 (Parser) 2. 사용자 등록 (Register) 3. 등록 결과 메세지 전송 (Reporter)
  42. Let it Crash! Recap - 오류를 잡기위한 방어적인 프로그래밍은 소모적

    - 그냥 크래시나게 두어라 - 프로세스 관리 전략으로 스스로 복구하는 시스템 구성
  43. 엘릭서는 좋은 개발자 경험에 진심이다. - 엘릭서 릴리즈에 개발자 경험을

    강조8) - 표준 테스트 프레임워크 - 대화형 쉘 IEx - 언제 어디서나 엘릭서를 실행 할 수 있는 Livebook
  44. 테스트 프레임워크 - 엘릭서에 내장 된 표준 테스트 라이브러리 제공

    - 매크로 사용하여 간편한 문법 test "elixir test example" do assert something_is_valid() assert expect_list() == actual_list() assert expect_error_log() =~ "error" end
  45. 테스트 프레임워크 - 엘릭서에 내장 된 표준 테스트 라이브러리 제공

    - 매크로 사용하여 간편한 문법 - document + test = doctest defmodule ExampleCode do @moduledoc """ ৃܼࢲ ৘ઁ ௏٘ ݽٕੑפ׮. """ @doc """ ੋ੗ ف ѐܳ ੑ۱߉ই ف чਸ ؊ೞח ೣࣻ ## Examples iex> ExampleCode.sum(1, 2) 3 iex> ExampleCode.sum(1, -2) -1 """ def sum(a, b) do a + b end end
  46. 테스트 프레임워크 - 엘릭서에 내장 된 표준 테스트 라이브러리 제공환

    - 매크로 사용하여 간편한 문법 - document + test = doctest defmodule ExampleCode do @moduledoc """ ৃܼࢲ ৘ઁ ௏٘ ݽٕੑפ׮. """ @doc """ ੋ੗ ف ѐܳ ੑ۱߉ই ف чਸ ؊ೞח ೣࣻ ## Examples iex> ExampleCode.sum(1, 2) 3 iex> ExampleCode.sum(1, -2) -1 """ def sum(a, b) do a + b end end
  47. IEx - 엘릭서 코드를 실행할 수 있는 대화형 쉘 -

    코드 자동완성, 코드 문서 보기
  48. IEx - 엘릭서 코드를 실행할 수 있는 대화형 쉘 -

    코드 자동완성, 코드 문서 보기 - 릴리즈된 어플리케이션에 접근 및 모듈 단위 코드 재컴파일
  49. Livebook - 다양한 활용성 (임베디드, 머신러닝, 데이터 분석 등) -

    엘릭서 설치가 필요 없는 데스크탑 앱 지원
  50. 참고 문헌 1) https:/ /evrone.com/jose - valim - interview 2)

    데이비드 토마스, 『처음 배우는 엘릭서 프로그래밍』, 권두호 옮김, 한빛미디어, 2022년, p34 3) 데이비드 토마스, 앤드류 헌트, 『실용주의 프로그래머(20주년 기념판)』, 정지용 옮김, 인사이트, 2022년, p.15 4) 데이비드 토마스, 앤드류 헌트, 『실용주의 프로그래머(20주년 기념판)』, 정지용 옮김, 인사이트, 2022년, p.207 5) Chris McCord, Bruce Tate, José valim, 『Programming Phoenix ≥ 1.4 』, The Pragmatic Programmers, p.18 6) https:/ /survey.stackover fl ow.co/2022/#section - most - loved - dreaded - and - wanted - programming - scripting - and - markup - languages 7) https:/ /survey.stackover fl ow.co/2022/#section - most - loved - dreaded - and - wanted - web - frameworks - and - technologies 8) https:/ /elixirforum.com/t/elixir - v1-14-0-released/49937