พบกับ Phoenix: กรอบงานเหมือน Rails สำหรับเว็บแอปสมัยใหม่บน Elixir

เผยแพร่แล้ว: 2022-03-11

เฟรมเวิร์กของ Phoenix ได้รับความนิยมเพิ่มขึ้นอย่างรวดเร็ว โดยนำเสนอผลงานของเฟรมเวิร์กอย่าง Ruby on Rails ในขณะเดียวกันก็เป็นหนึ่งในเฟรมเวิร์กที่เร็วที่สุดที่มีอยู่ มันทำลายตำนานที่คุณต้องเสียสละประสิทธิภาพเพื่อเพิ่มประสิทธิภาพการทำงาน

แล้วฟีนิกซ์คืออะไรกันแน่?

Phoenix เป็นเว็บเฟรมเวิร์กที่สร้างขึ้นด้วยภาษาโปรแกรม Elixir Elixir สร้างขึ้นบน Erlang VM ใช้สำหรับสร้างระบบแบบกระจายเวลาแฝงต่ำ ทนทานต่อข้อผิดพลาด ซึ่งเป็นคุณสมบัติที่จำเป็นมากขึ้นเรื่อยๆ ของเว็บแอปพลิเคชันสมัยใหม่ คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับ Elixir ได้จากบล็อกโพสต์นี้หรือคำแนะนำอย่างเป็นทางการ

หากคุณเป็นนักพัฒนา Ruby on Rails คุณควรสนใจ Phoenix อย่างแน่นอนเนื่องจากประสิทธิภาพที่เพิ่มขึ้นตามที่สัญญาไว้ นักพัฒนาของกรอบงานอื่นๆ สามารถติดตามเพื่อดูว่า Phoenix เข้าถึงการพัฒนาเว็บอย่างไร

พบกับ Phoenix บน Elixir: กรอบงานเหมือน Rails สำหรับเว็บแอปสมัยใหม่

ในบทความนี้ เราจะเรียนรู้บางสิ่งในฟีนิกซ์ที่คุณควรจำไว้หากคุณมาจากโลกของ Ruby on Rails

ต่างจาก Ruby ตรงที่ Elixir เป็นภาษาโปรแกรมที่ใช้งานได้จริง ซึ่งน่าจะเป็นข้อแตกต่างที่ใหญ่ที่สุดที่คุณอาจต้องรับมือ อย่างไรก็ตาม มีความแตกต่างที่สำคัญระหว่างสองแพลตฟอร์มนี้ที่ทุกคนที่เรียนรู้เกี่ยวกับ Phoenix ควรทราบเพื่อเพิ่มประสิทธิภาพการทำงานสูงสุด

หลักการตั้งชื่อง่ายกว่า

นี่เป็นสิ่งเล็ก ๆ แต่ก็ง่ายที่จะเลอะถ้าคุณมาจาก Ruby on Rails

ในฟินิกซ์ การประชุมคือการเขียนทุกอย่างในรูปแบบเอกพจน์ ดังนั้นคุณจะมี "UserController" แทนที่จะเป็น "UsersController" เช่นเดียวกับใน Ruby on Rails สิ่งนี้ใช้ได้กับทุกที่ยกเว้นเมื่อตั้งชื่อตารางฐานข้อมูลโดยที่หลักการคือการตั้งชื่อตารางในรูปแบบพหูพจน์

สิ่งนี้อาจดูเหมือนไม่ใช่เรื่องใหญ่หลังจากเรียนรู้ว่าจะใช้รูปแบบพหูพจน์ใน Ruby on Rails เมื่อใดและที่ใด แต่ในตอนแรกมันค่อนข้างสับสนเล็กน้อย เมื่อฉันกำลังเรียนรู้ที่จะใช้ Ruby on Rails และฉันแน่ใจว่ามันทำให้หลายคนสับสน อื่น ๆ เช่นกัน Phoenix ช่วยให้คุณไม่ต้องกังวลอีกต่อไป

การกำหนดเส้นทางนั้นง่ายต่อการจัดการ

Phoenix และ Ruby on Rails มีความคล้ายคลึงกันมากเมื่อพูดถึงการกำหนดเส้นทาง ความแตกต่างที่สำคัญคือวิธีที่คุณสามารถควบคุมวิธีการประมวลผลคำขอได้

ใน Ruby on Rails (และแอปพลิเคชันแร็คอื่นๆ) ทำได้ผ่านมิดเดิลแวร์ ในขณะที่ในฟีนิกซ์ การดำเนินการนี้ทำได้โดยใช้สิ่งที่เรียกว่า "ปลั๊ก"

ปลั๊กคือสิ่งที่คุณใช้ในการประมวลผลการเชื่อมต่อ

ตัวอย่างเช่น มิดเดิลแวร์ Rails::Rack::Logger จะบันทึกคำขอใน Rails ซึ่งใน Phoenix จะทำโดยใช้ปลั๊ก Plug.Logger โดยทั่วไป อะไรก็ตามที่คุณสามารถทำได้ในมิดเดิลแวร์ของ Rack สามารถทำได้โดยใช้ปลั๊ก

นี่คือตัวอย่างของเราเตอร์ในฟีนิกซ์:

 defmodule HelloWorld.Router do use HelloWorld.Web, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end scope "/", HelloWorld do pipe_through :browser get "/", HelloController, :index end scope "/api", HelloWorld do pipe_through :api end end

ขั้นแรกให้ดูที่ท่อ

นี่คือกลุ่มปลั๊กที่จะส่งคำขอ คิดว่ามันเป็นกองมิดเดิลแวร์ สามารถใช้ ตัวอย่างเช่น เพื่อตรวจสอบว่าคำขอต้องการ HTML ดึงเซสชัน และตรวจสอบให้แน่ใจว่าคำขอนั้นปลอดภัย ทั้งหมดนี้เกิดขึ้นก่อนถึงตัวควบคุม

เนื่องจากคุณสามารถระบุไปป์ไลน์ได้หลายแบบ คุณจึงสามารถเลือกปลั๊กที่จำเป็นสำหรับเส้นทางเฉพาะได้ ตัวอย่างเช่น ในเราเตอร์ของเรา เรามีไปป์ไลน์ที่แตกต่างกันสำหรับคำขอหน้า (HTML) และ API (JSON)

ไม่เหมาะสมเสมอไปที่จะใช้ไปป์ไลน์เดียวกันสำหรับคำขอประเภทต่างๆ ฟีนิกซ์ให้ความยืดหยุ่นแก่เรา

มุมมองและเทมเพลตเป็นสองสิ่งที่แตกต่างกัน

การดูในฟีนิกซ์ไม่เหมือนกับการดูใน Ruby on Rails

Views ใน Phoenix รับผิดชอบการแสดงเทมเพลตและจัดเตรียมฟังก์ชันที่ทำให้ข้อมูลดิบง่ายขึ้นสำหรับเทมเพลต มุมมองในฟีนิกซ์ใกล้เคียงกับผู้ช่วยใน Ruby on Rails มากที่สุด ด้วยการเพิ่มการแสดงเทมเพลต

มาเขียนตัวอย่างที่แสดงคำว่า Hello, World! ในเบราว์เซอร์

ทับทิมบนราง:

แอพ/controllers/hello_controller.rb:

 class HelloController < ApplicationController def index end end

แอพ/views/hello/index.html.erb:

 <h1>Hello, World!</h1>

ฟีนิกซ์:

เว็บ/controllers/hello_controller.ex:

 defmodule HelloWorld.HelloController do use HelloWorld.Web, :controller def index(conn, _params) do render conn, "index.html" end end

เว็บ/views/hello_view.ex:

 defmodule HelloWorld.HelloView do use HelloWorld.Web, :view end

เว็บ/แม่แบบ/hello/index.html.eex:

 <h1>Hello, World!</h1>

ตัวอย่างเหล่านี้แสดง “Hello, World!” ในเบราว์เซอร์แต่มีข้อแตกต่างที่สำคัญบางประการ

ขั้นแรก คุณต้องระบุเทมเพลตที่คุณต้องการแสดงอย่างชัดเจนใน Phoenix ซึ่งแตกต่างจาก Ruby on Rails

ต่อไป เราต้องรวมมุมมองในฟีนิกซ์ซึ่งอยู่ระหว่างตัวควบคุมและเทมเพลต

ตอนนี้คุณอาจสงสัยว่าทำไมเราต้องดูถ้ามันว่างเปล่า? กุญแจสำคัญที่นี่คือ ภายใต้ประทุน Phoenix รวบรวมเทมเพลตเป็นฟังก์ชันคร่าวๆ เท่ากับโค้ดด้านล่าง:

 defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end

คุณสามารถลบแม่แบบ และใช้ฟังก์ชันการเรนเดอร์ใหม่ และคุณจะได้ผลลัพธ์แบบเดียวกัน สิ่งนี้มีประโยชน์เมื่อคุณต้องการส่งคืนข้อความหรือแม้แต่ JSON

โมเดลก็เป็นเช่นนั้น: โมเดลข้อมูล

ในฟีนิกซ์ โมเดลส่วนใหญ่จัดการกับการตรวจสอบความถูกต้องของข้อมูล สคีมา ความสัมพันธ์กับโมเดลอื่นๆ และวิธีการนำเสนอ

การระบุสคีมาในโมเดลอาจฟังดูแปลกในตอนแรก แต่ช่วยให้คุณสร้างฟิลด์ "เสมือน" ได้อย่างง่ายดาย ซึ่งเป็นฟิลด์ที่ไม่คงอยู่ในฐานข้อมูล ลองมาดูตัวอย่าง:

 defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end end

ที่นี่เรากำหนดสี่ฟิลด์ในตาราง "ผู้ใช้": ชื่อ อีเมล รหัสผ่าน และ password_hash

ไม่มีอะไรน่าสนใจที่นี่มากนัก ยกเว้นช่อง "รหัสผ่าน" ที่ตั้งค่าเป็น "เสมือน"

ซึ่งช่วยให้เราสามารถตั้งค่าและรับฟิลด์นี้โดยไม่ต้องบันทึกการเปลี่ยนแปลงในฐานข้อมูล มีประโยชน์เพราะเราจะมีตรรกะในการแปลงรหัสผ่านนั้นเป็นแฮช ซึ่งเราจะบันทึกไว้ในฟิลด์ “password_hash” แล้วบันทึกลงในฐานข้อมูล

คุณยังต้องสร้างการย้ายข้อมูล จำเป็นต้องใช้สคีมาในโมเดลเนื่องจากฟิลด์จะไม่โหลดลงในโมเดลโดยอัตโนมัติเหมือนใน Ruby on Rails

ความแตกต่างระหว่าง Phoenix และ Ruby on Rails คือโมเดลไม่สามารถจัดการกับการคงอยู่ของฐานข้อมูลได้ สิ่งนี้ถูกจัดการโดยโมดูลที่เรียกว่า “Repo” ซึ่งกำหนดค่าด้วยข้อมูลฐานข้อมูล ดังแสดงในตัวอย่างด้านล่าง:

 config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"

โค้ดนี้รวมอยู่ในไฟล์การกำหนดค่าเฉพาะของสภาพแวดล้อม เช่น config/dev.exs หรือ config/test.exs ซึ่งช่วยให้เราใช้ Repo เพื่อดำเนินการกับฐานข้อมูล เช่น สร้างและอัปเดต

 Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end

นี่เป็นตัวอย่างทั่วไปในตัวควบคุมในฟีนิกซ์

เราให้ชื่อผู้ใช้และอีเมลแก่ผู้ใช้ และ Repo พยายามสร้างบันทึกใหม่ในฐานข้อมูล จากนั้น เราสามารถเลือกที่จะจัดการกับความพยายามที่ประสบความสำเร็จหรือล้มเหลวตามที่แสดงในตัวอย่าง

เพื่อให้เข้าใจโค้ดนี้ดีขึ้น คุณต้องเข้าใจการจับคู่รูปแบบใน Elixir ค่าที่ส่งคืนโดยฟังก์ชันคือทูเพิล ฟังก์ชันจะส่งกลับค่าทูเพิลสองค่า สถานะ และจากนั้นจะเป็นโมเดลหรือเซ็ตการแก้ไข ชุดการแก้ไขเป็นวิธีติดตามการเปลี่ยนแปลงและตรวจสอบแบบจำลอง (ชุดการเปลี่ยนแปลงจะกล่าวถึงในหัวข้อถัดไป)

ทูเพิลตัวแรก จากบนลงล่าง ที่ตรงกับรูปแบบของทูเพิลที่ส่งคืนโดยฟังก์ชันที่พยายามแทรกผู้ใช้ลงในฐานข้อมูล จะเรียกใช้ฟังก์ชันที่กำหนดไว้

เราสามารถตั้งค่าตัวแปรสำหรับสถานะแทนที่จะเป็นอะตอม (ซึ่งโดยพื้นฐานแล้วสิ่งที่สัญลักษณ์อยู่ใน Ruby) แต่จากนั้นเราจะจับคู่ทั้งความพยายามที่ประสบความสำเร็จและล้มเหลว และจะใช้ฟังก์ชันเดียวกันสำหรับทั้งสองสถานการณ์ การระบุอะตอมที่เราต้องการจับคู่ เราจะกำหนดฟังก์ชันเฉพาะสำหรับสถานะนั้น

Ruby on Rails มีฟังก์ชันการคงอยู่ของโมเดลผ่าน ActiveRecord ซึ่งเป็นการเพิ่มความรับผิดชอบให้กับโมเดล และบางครั้งอาจทำให้การทดสอบโมเดลมีความซับซ้อนมากขึ้น ในฟินิกซ์ สิ่งนี้ถูกแยกออกจากกันในลักษณะที่เหมาะสมและป้องกันไม่ให้โมเดลทุกรุ่นบวมด้วยตรรกะการคงอยู่

ชุดการเปลี่ยนแปลงช่วยให้มีกฎการตรวจสอบและการเปลี่ยนแปลงที่ชัดเจน

ใน Ruby on Rails การตรวจสอบและแปลงข้อมูลอาจเป็นสาเหตุของข้อบกพร่องที่หายาก เนื่องจากไม่ชัดเจนในทันทีเมื่อมีการเปลี่ยนข้อมูลโดยการเรียกกลับ เช่น "before_create" และการตรวจสอบความถูกต้อง

ใน Phoenix คุณทำการตรวจสอบและการแปลงเหล่านี้อย่างชัดเจนโดยใช้เซ็ตการแก้ไข นี่เป็นหนึ่งในคุณสมบัติที่ฉันโปรดปรานในฟีนิกซ์

มาดูเซ็ตการแก้ไขโดยเพิ่มหนึ่งชุดไปยังรุ่นก่อนหน้า:

 defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :email, :password]) |> validate_required([:email, :password]) end end

ที่นี่ชุดการแก้ไขทำสองสิ่ง

ขั้นแรกเรียกฟังก์ชัน "cast" ซึ่งเป็นรายการที่อนุญาตพิเศษของฟิลด์ที่อนุญาต คล้ายกับ "strong_parameters" ใน Ruby on Rails จากนั้นจะตรวจสอบว่าฟิลด์ "อีเมล" และ "รหัสผ่าน" รวมอยู่ด้วย ทำให้ฟิลด์ "ชื่อ" ไม่จำเป็น. ด้วยวิธีนี้ เฉพาะฟิลด์ที่คุณอนุญาตเท่านั้นที่สามารถแก้ไขได้โดยผู้ใช้

ข้อดีของแนวทางนี้คือ เราไม่ได้จำกัดแค่เซ็ตการแก้ไขเดียว เราสามารถสร้างชุดการเปลี่ยนแปลงได้หลายชุด ชุดการเปลี่ยนแปลงสำหรับการลงทะเบียนและอีกชุดสำหรับอัปเดตผู้ใช้นั้นเป็นเรื่องปกติ บางทีเราต้องการเพียงแค่ฟิลด์รหัสผ่านเมื่อลงทะเบียน แต่ไม่ใช่เมื่ออัปเดตผู้ใช้

เปรียบเทียบวิธีการนี้กับสิ่งที่ทำกันทั่วไปใน Ruby on Rails คุณจะต้องระบุว่าการตรวจสอบควรรันเฉพาะบน "สร้าง" เท่านั้น บางครั้งสิ่งนี้ทำให้ Rails ยากในการค้นหาว่าโค้ดของคุณกำลังทำอะไรอยู่เมื่อคุณมีโมเดลที่ซับซ้อน

ฟังก์ชันการนำเข้านั้นตรงไปตรงมา แต่ยืดหยุ่นได้

ฟังก์ชันส่วนใหญ่ของไลบรารีที่เรียกว่า "Ecto" ถูกนำเข้ามาในโมเดล โมเดลมักจะมีเส้นนี้อยู่ใกล้ด้านบน:

 use HelloPhoenix.Web, :model

โมดูล “HelloPhoenix.Web” จะอยู่ใน “web/web.ex” ในโมดูลควรมีฟังก์ชันที่เรียกว่า "โมเดล" ดังนี้

 def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end

คุณสามารถดูโมดูลที่เรานำเข้าจาก Ecto ได้ที่นี่ คุณสามารถลบหรือเพิ่มโมดูลอื่นๆ ที่คุณต้องการได้ที่นี่ และโมดูลเหล่านั้นจะถูกนำเข้าไปยังทุกรุ่น

นอกจากนี้ยังมีฟังก์ชันที่คล้ายกัน เช่น "มุมมอง" และ "ตัวควบคุม" ซึ่งมีจุดประสงค์เดียวกันสำหรับมุมมองและตัวควบคุม ตามลำดับ

quote และ use คำสำคัญอาจดูสับสน สำหรับตัวอย่างนี้ คุณสามารถนึกถึงใบเสนอราคาเป็นการเรียกใช้โค้ดนั้นโดยตรงในบริบทของโมดูลที่เรียกใช้ฟังก์ชันนั้น ดังนั้นจะเทียบเท่ากับการเขียนโค้ดในใบเสนอราคาในโมดูล

ใช้คำสำคัญยังช่วยให้คุณเรียกใช้โค้ดในบริบทของตำแหน่งที่เรียกว่า โดยพื้นฐานแล้วจำเป็นต้องมีโมดูลที่ระบุ จากนั้นเรียกใช้มาโคร __using__ บนโมดูลที่เรียกใช้ในบริบทของตำแหน่งที่ถูกเรียก คุณสามารถอ่านเพิ่มเติมเกี่ยวกับใบเสนอราคาและการใช้งานในคู่มืออย่างเป็นทางการ

สิ่งนี้ช่วยให้คุณเข้าใจว่าฟังก์ชั่นบางอย่างอยู่ในเฟรมเวิร์กอย่างไร และช่วยลดความรู้สึกที่ว่าเฟรมเวิร์กนั้นสร้าง "เวทย์มนตร์" อย่างมาก

การทำงานพร้อมกันคือหัวใจหลัก

การทำงานพร้อมกันฟรีในฟีนิกซ์เพราะเป็นคุณสมบัติหลักของน้ำอมฤต คุณได้รับแอปพลิเคชันที่สามารถวางไข่ได้หลายกระบวนการและทำงานบนหลายคอร์โดยไม่ต้องกังวลเรื่องความปลอดภัยและความน่าเชื่อถือของเธรด

คุณสามารถสร้างกระบวนการใหม่ใน Elixir ได้ง่ายๆ ดังนี้:

 spawn fn -> 1 + 2 end

ทุกอย่างหลังจาก spawn และก่อน end จะทำงานในกระบวนการใหม่

อันที่จริง ทุกคำขอในฟีนิกซ์จะได้รับการจัดการตามกระบวนการของตนเอง Elixir ใช้พลังของ Erlang VM เพื่อนำเสนอการทำงานพร้อมกันที่เชื่อถือได้และมีประสิทธิภาพ "ฟรี"

สิ่งนี้ทำให้ Phoenix เป็นตัวเลือกที่ยอดเยี่ยมสำหรับการเรียกใช้บริการที่ใช้ WebSockets เนื่องจาก WebSockets จำเป็นต้องรักษาการเชื่อมต่อแบบเปิดระหว่างไคลเอนต์และเซิร์ฟเวอร์ (ซึ่งหมายความว่าคุณต้องสร้างแอปพลิเคชันของคุณเพื่อให้สามารถจัดการกับการเชื่อมต่อพร้อมกันได้หลายพันครั้ง)

ข้อกำหนดเหล่านี้จะเพิ่มความซับซ้อนอย่างมากให้กับโครงการที่สร้างบน Ruby on Rails แต่ฟีนิกซ์สามารถปฏิบัติตามข้อกำหนดเหล่านี้ได้ฟรีผ่าน Elixir

หากคุณต้องการใช้ WebSockets ในแอปพลิเคชัน Phoenix คุณจะต้องใช้ Channels เทียบเท่ากับ ActionCable ใน Ruby on Rails แต่การตั้งค่าไม่ซับซ้อนเพราะคุณไม่จำเป็นต้องเรียกใช้เซิร์ฟเวอร์แยกต่างหาก

Phoenix ทำให้การสร้างเว็บแอปสมัยใหม่ไม่ยุ่งยาก

แม้ว่าเราจะมุ่งเน้นไปที่ความแตกต่างเป็นหลัก แต่ Phoenix มีบางสิ่งที่เหมือนกันกับ Ruby on Rails

Phoenix เป็นไปตามรูปแบบ MVC เดียวกันกับ Ruby on Rails คร่าวๆ ดังนั้นการหาว่าโค้ดใดที่ไม่น่าจะยากในตอนนี้ เมื่อคุณทราบถึงความแตกต่างหลักๆ แล้ว Phoenix ยังมีตัวสร้างที่คล้ายกันกับ Ruby on Rails สำหรับการสร้างโมเดล ตัวควบคุม การโยกย้าย และอื่นๆ

หลังจากเรียนรู้ Elixir คุณจะค่อยๆ เข้าใกล้ระดับประสิทธิภาพ Ruby on Rails เมื่อคุณคุ้นเคยกับ Phoenix มากขึ้นแล้ว

สองสามครั้งที่ฉันรู้สึกไม่มีประสิทธิภาพเท่าที่ฉันพบปัญหาที่อัญมณีใน Ruby แก้ไขได้ และฉันไม่พบห้องสมุดที่คล้ายกันสำหรับ Elixir โชคดีที่ช่องว่างเหล่านี้ค่อยๆ ถูกเติมเต็มโดยชุมชน Elixir ที่กำลังเติบโต

ความแตกต่างใน Phoenix ช่วยแก้ปัญหามากมายที่มาพร้อมกับการจัดการโครงการ Ruby on Rails ที่ซับซ้อน แม้ว่าจะไม่แก้ปัญหาทั้งหมด แต่ก็ช่วยผลักดันคุณไปในทิศทางที่ถูกต้อง การเลือกเฟรมเวิร์กที่ช้าเพราะคุณต้องการประสิทธิภาพการทำงานไม่ใช่ข้อแก้ตัวที่ถูกต้องอีกต่อไป Phoenix ให้คุณมีทั้งสองอย่าง