Phoenix概念篇

文章目录

  • 前言
  • Phoenix的web层概念
    • Plug
    • Endpoint
    • Router
      • Scope
      • Pipeline
    • Controller
      • Action
    • Component
  • 一次请求

前言

Elixir和Phoenix的作者也是Rails社区的核心开发者,如果是之前接触过Ruby on Rails的开发者,对Phoenix也许不会感到太陌生。笔者没有接触过Ruby on Rails,只能从Go语言的经验和角度出发去对比理解。如果说一开始Phoenix的确脱胎于Rails,经过这么多版本的迭代,加上Elixir的语法特点,Phoenix也一定产生了一些特有的变化。上一期我们学习了Phoenix入门篇,书接上回,这次我们重点介绍下Phoenix框架中的一些概念,并继续探索它的架构设计思想。希望本文能帮助你开始Phoenix开发之旅,即便最后你决定不适用Phoenix,它的架构思想也一定会对你有所启发。

首先我们来思考两个问题:

  1. 网络框架要解决的核心问题是什么?
  2. 框架的核心价值是什么?

相信每个人对这两个问题都有不同的见解,下面也仅仅是我个人对这两个问题一些看法。

我们可以先简单的认为网络服务就是在反复做一件事:接收请求,返回响应。所谓请求就是一个网络资源,它由一个网络路径标识。所以网络框架要解决的核心问题就是决定不同的路径应该由哪个函数来发出响应,其实也就是要解决路由的问题。这一点Go标准库就做的十分纯粹,所以用Go标准库来写一些小型的后端服务是非常简单惬意的。

那么纯粹的代价是什么呢,这就是网络框架的核心价值问题。框架的核心价值是效率,包括开发效率和维护成本。懂事的框架会帮你做许多琐碎的事,让你可以专注于业务逻辑。比如日志,监控等这些虽然不是核心功能,但是对于一个线上系统来说又必不可少。其次框架会为你提供一套行为规范,在你的项目越来越大时,不会陷入混乱而不得不重构。代码生成也是一个好东西,可以节省不少时间。此外,框架的复杂程度和学习成本也值得考虑,如果对于一个简单的服务,也必须要先看完几百页的说明书才能把服务跑起来,那么就要好好考虑一下这几百页的说明书值不值得看了。

另外扯一点题外话,不要提前解决还未遇到的问题。不知道大家在看到网上哪些大谈架构或者起着牛逼哄哄标题的文章时,会不会产生技术焦虑。事实上部分技术文章并不那么纯粹,都是带着引流目的的。制造焦虑也是一种营销手段,倒不是说不应该去学习,然而我们首先要理解的是问题而不是技术本身。框架也好,架构也好,什么也好,它们存在的意义是解决问题,而不是带来问题,或者说它们能解决的问题远大于其带来的问题。古人云:任何事情都是有代价的。理解了问题才能对阵下药,切勿乱投医。

言归正传,在继续阅读之前,希望你已经阅读过了Phoenix入门篇。需要说明的是,本篇主要是介绍相关概念,多是以生成项目时的初始代码为例,也不会深入代码细节,与写代码相关的细节我会在稍后的实战篇中介绍。

Phoenix的web层概念

Phoenix在v1.3版本对目录结构做了一次大的调整,有了我们今天看到的结构。经过这次调整之后,业务逻辑和web接口彻底分离。我们不再是开发一个以web为核心的应用,而是开发一个具有web接口的系统。这有很么区别呢?在以前端为核心的应用中,我们很容易从前端出发去思考后端业务逻辑,导致边界逐渐模糊,逻辑慢慢往controller里面移动。但实际上我们应该首先考虑业务逻辑,基于此来提web接口。

Phoenix也是一个MVC的框架,它把MVC中的V和C抽到了web层,构成了应用的web接口,对应着 lib/hello_web 目录, hello 是你的项目名,我们还是沿用Phoenix入门篇中的示例。

在web接口中,Phoenix抽象出了plug,endpoint,router,scope,pipeline,controller,action,component等概念,它们的整体关系如下图所示。

Plug

Plug贯穿了整个Phoenix的web接口,我们可以在web层的各个地方看到plug的身影。可以说Plug是Phoenix的核心,理解Plug是理解Phoenix框架的关键。Plug既是一个抽象概念,也有对应实体,我们需要区分一下不同地方出现的plug的含义。

作为抽象概念,Plug是一个连接转换器,它接受一个连接,经过一些处理,最后返回一个连接。然后这个连接还可以交给下一个Plug继续处理,构成一个连接转换的链条,这正是Phoenix处理请求的核心机制。听起来有点像中间件,虽然Phoenix的中间件也是通过Plug机制实现的,但是Plug本身不同于我们所熟知的中间件概念,这一点一定要分清。这里的“连接”指的是客户端连接,用 %Plug.conn{} 结构体表示。你可以把它想象成糖葫芦,连接就是竹签,Plug就是竹签上的山楂。

Plug可以是一个函数,也可以是一个模块。

如果是函数Plug,第一个参数一定是 %Plug.conn{} 结构体,第二个参数取决于应用Plug时传递的内容。第一个参数是Phoenix自动传入的,稍后我们会介绍第二个参数是如何传递的。最后函数一定要返回一个 %Plug.conn{} 结构体。举个例子:

def func_plug(conn, _opts) do# do somethingconn
end

如果是模块Plug,需要定义两个函数: init/1call/2call/2 的函数签名和上面的函数Plug是一样的,第一个参数是 %Plug.conn{} ,由Phoenix自动传入;第二个参数是 init/1 函数的返回值,也是由Phoenix自动传入。 init/1 入参的传递方式和上面的函数Plug的第二个参数的传递方式是一样的,稍后会介绍。举个例子:

defmodule HelloWeb.Plugs.ModulePlug do import Plug.Conndef init(default), do: default def call(conn, _default) do # do somethingconnend 
end

plug 的第二个身份是宏,有了Plug(连接转换器)之后,需要将它应用起来,这就是 plug 宏做的事情。它接受一个Plug和一个可选参数(默认为 [] ),这个可选参数正是函数Plug的第二个参数,也是模块Plug的 init/1 函数的参数。例如我们可以像下面这样使用上面的两个Plug:

plug :func_plug
plug HelloWeb.Plugs.ModulePlug, "your params"

当然你也可以写成带括号的形式,它们仅仅是语法上的区别:

plug(:func_plug)
plug(HelloWeb.Plugs.ModulePlug, "your params")

我们在许多文件里面都能看到 plug 的身影,比如 lib/hello_web/endpoint.exlib/hello_web/router.ex 等。因为无论是endpoint,router还是controller,它们本质上都是Plug。

Endpoint

Endpoint是整个后端服务的入口,对应着 lib/hello_web/endpoint.ex 文件。一个endpoint会做为一个opt应用启动,phoenix本质上还是一个elixir应用,在 lib/hello/application.ex 文件的 start 函数中可以看到endpoint是做为opt启动的。

def start(_type, _args) dochildren = [...# Start the Endpoint (http/https)HelloWeb.Endpoint...]# See https://hexdocs.pm/elixir/Supervisor.html# for other strategies and supported optionsopts = [strategy: :one_for_one, name: Hello.Supervisor]Supervisor.start_link(children, opts)
end

Endpoint做为入口,除了做为opt应用,它里面还“插入”了许多公共的Plug,其中最重要的是下面这行代码:

plug HelloWeb.Router

通过这行代码,我们将Router(路由器)带了进来,它会负责请求的分发,也就是解决web框架的那个最核心的问题。

Router

Router就是我们熟知的路由器,它本质上也是一个Plug,对应着 lib/hello_web/router.ex 文件。让我们来看下Phoenix为我们生成的路由器代码:

  scope "/", HelloWeb dopipe_through :browserget "/", PageController, :homeend

先来看 get "/", PageController, :home 这行代码,它注册了一个根路由 / ,方法是GET,这也是Phoenix欢迎页的路由。 PageController 是我们的控制器, :home 是此路由对应的处理函数,在Phoenix中称为Action。

除了 get ,Phoenix也为其他HTTP方法定义了对应的宏,如 headpostput 等,他们都定义在 Phoenix.Router 模块下。

虽然示例中路由被放到了Scope内,但这并不是必须的。

Scope

在Router中,路由代码被放到了 scope 代码块内。Scope其实就是组路由,它可以对具有相同前缀的路由进行分组,并且可以对组内路由设置公共中间件。比如在Router示例代码中的 scope 下面还有这么一行代码: pipe_through :browser

pipe_through 是应用中间件的宏,而 :browser 就是中间件。不过在Phoenix中,它们被称为Pipeline。

Pipeline

在Phoenix中,中间件被称为Pipeline,由 pipeline 宏来定义。借鉴图形学的概念,这里其实把Pipeline翻译为管线更加合适,但是从概念理解上,它等同于中间件。

Pipeline本质上就是一个Plug的列表,如下图所示:

如果整个web层是一串大糖葫芦,那么pipeline就是一串小糖葫芦。 :broswer 管线的定义如下:

pipeline :browser doplug :accepts, ["html"]plug :fetch_sessionplug :fetch_live_flashplug :put_root_layout, html: {HelloWeb.Layouts, :root}plug :protect_from_forgeryplug :put_secure_browser_headers
end

Pipeline内是通过 plug 宏插入的一系列Plug,是不是很像串糖葫芦。连接在真正交给Controller之前,会首先依次经过Pipeline中的Plug的处理。注意 plug :put_root_layout, html: {HelloWeb.Layouts, :root} 这行代码,它会像浏览器写回网页的公共部分,如布局。这样在Controller中就只需要返回具体的HTML内容了。

除了 :browser ,Phoenix还生成了一个 :api Pipeline,当我们编写API接口时可以使用它。

pipeline :api doplug :accepts, ["json"]
end

Controller

Controller就是我们熟知的控制器了,不出意外,它本质上也是一个Plug,对应的代码在 lib/hello_web/controllers 目录下,我们看到的欢迎页就是 page_controller.ex 渲染出来的,源码如下:

defmodule HelloWeb.PageController douse HelloWeb, :controllerdef home(conn, _params) do# The home page is often custom made,# so skip the default app layout.render(conn, :home, layout: false)end
end

欢迎页的Controller非常简单,只有一个 home/2 函数用来渲染页面。在Phoenix中,它也称为Action。

虽然Controller和Router看起来都不太像我们前面介绍的Plug,但它们的确是Plug,这就是Elixir宏的魔力。

Action

HTTP请求方法方法除了有名词属性,还有动词属性。比如针对某个路径的一次GET请求,这个动作就称为Action。在Controller中,我们也将用来处理连接请求的函数称为Action。

一个函数要作为Action,必须接受两个参数,第一个是表示连接的 %Plug.conn{} 结构体,第二个是记录着HTTP请求参数的map。在Action中,不应该有过多的业务逻辑,只保留web相关的逻辑和渲染相关的代码,保持Controller层尽量扁平。

在示例中,我们通过 render(conn, :home, layout: false) 来渲染欢迎页。 render 函数是 Phoenix.Controller 模块提供的,它有三个参数。第一个是HTTP连接,也就是 %Plug.conn{} 结构体;第二个参数是HTTP模版,可以是二进制的模版数据,也可以是一个返回模版的函数;第三个参数是渲染模版的参数,可以是关键字(Keyword),也可以是map。

注意 render 的第二个参数 :home ,这是一个返回HTML模板的函数,在Phoenix中被称为Component。

Component

Component是一个返回HTML模版的函数,当然它也需要接受一个map或keyword参数。我们可以粗浅的理解Component就是HTML模板。它对应的是lib/hello_web/controller/page_html.ex 文件。

前面我们看到渲染欢迎页的Component是 :home 函数,但是当我们打开 page_html.ex 后却怎么也找不到这个函数。这是怎么回事呢?

我们先来看下Controller和Component的命名:Controller文件叫 page_controller.ex ,Component文件名叫 page_html.ex ,模块名是 HelloWeb.PageHTML 。这些名称可都不是随便起的,注意看它们的规律,都是page开头,加一个有意义的后缀,page其实无所谓,只要相同就行,但后缀是有讲究的。我们在 render 函数中直接引用了 :home 函数,并没有指定模块名,Phoenix能找到它就是因为这些命名规约。

我们还是没有找到 :home 函数,只有这样一行代码: embed_templates "page_html/*" 。而在controllers目录下,的确有page_html这么一个目录,里面有一个名为 home.html.heex 的文件,home?难道说它们之间也有着某种神秘的联系?

没错, home.html.heex 文件会预编译为 page_html.ex 里面一个名为 home 的函数。Phoenix使用的是HEEx(HTML+EEx)模板语言,EEx是一个Elixir表达式求值的库。HEEx模板文件以 .heex 做为扩展名,所以 home.html.heex 的三个部分的含义分别是Component(函数)名称,模板类型,HEEx扩展名。

对于比较长的模板,建议使用这种独立文件的形式,而对于一些简短的模板,则可以使用函数的形式,例如:

def home(_assigns) do ~H"""Hello World! """
end 

你会发现在 lib/hello_web 目录下也有一个独立的 components 目录,它里面存放的也是Component,不过是网页公共部分,如布局等相关的模板。

一次请求

根据前面对概念的梳理,我们也能大致看到一条请求在Phoenix中处理的脉络。现在我们再次对一个HTTP请求的处理流程做一个完整的回顾与总结。

请求首先到达Endpoint,位于 lib/hello_web/endpoint.ex ,在这里有许多Plug会对请求做第一轮的处理。而在这众多的Plug之中,Router也是其中之一,在Endpoint的最后,请求被交给了Router。

Router位于 lib/hello_web/router.ex ,它主要做了两件事,分组分发与中间件。请求在这里先经过Pipeline处理,然后交由对应的Controller。

Controller位于 lib/hello_web/controllers 目录下,新项目只有 page_controller.ex 这一个Controller,其中定义了Action,它们是请求处理的最后一环。在Action中会调用业务逻辑和Component,将数据与视图结合,形成最终的响应,发送回客户端。

以上就是一次请求处理的流程,没有看错,就是这么的简单:Endpoint→Router→Controller→Action。我们暂时省略了代码上的一些细节,如果你非常熟悉Elixir的话,结合hexdocs中的文档,应该不难理解这些代码。此外在Router,Controller等里面使用的 use 宏对应的源码都在 lib/hello_web/world_web.ex 文件中。

整个请求的过程应该不难理解,你可以试着修改模板或者打印一些日志,看看页面的变化和控制台输出,日志使用的是Erlang的 :logger 模块。

本章完,下期见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/772417.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【报错】使用gradio渲染html页面无法加载本地图片

【报错】使用gradio渲染html页面无法加载本地图片 【报错】使用gradio渲染html页面无法加载本地图片[HTML] how to load local image by html output #884成功解决 【报错】使用gradio渲染html页面无法加载本地图片 在使用gradio框架渲染html页面,使用绝对路径&quo…

BUUCTF-Misc14

[WUSTCTF2020]find_me1 1.打开附件 是一个学校的校徽 2.盲文解密 发现图片属性里的备注是一串盲文 用在线盲文解密 3.得到flag

C语言笔记:重学输入和输出

ACM金牌带你零基础直达C语言精通-课程资料 本笔记属于船说系列课程之一,课程链接:ACM金牌带你零基础直达C语言精通https://www.bilibili.com/cheese/play/ep159068?csourceprivate_space_class_null&spm_id_from333.999.0.0 你也可以选择购买『船说…

AI新工具 视频迁移升级中国水墨画风格2.0;新颖的视频编辑框架提示编辑,风格转移,身份操控都不在话下;提取多种风格人脸草图

✨ 1: DomoAI 升级中国水墨画风格2.0 DomoAI是一个多功能的AI视频处理工具,可以将视频转换成多种风格,包括日本动漫、3D卡通、漫画和像素风格等。用户只需上传原始视频,通过简单的操作就能实现风格转换,制作出具有个性的高质量视…

“架构(Architecture)” 一词的定义演变历史(依据国际标准)

深入理解“架构”的客观含义,不仅能使IT行业的系统架构设计师提升思想境界,对每一个积极的社会行动者而言,也具有长远的现实意义,因为,“架构”一词,不只限于IT系统,而是指各类系统(包括社会系统…

蓝鹏智能测量仪应用于这些方面!助力发展新质生产力!

新质生产力是未来几年着重发展的方向,关于如何实现产业化升级,各厂家会在自身的基础上进行产业化调整升级,利用新工具、新手段,大幅缩短研发设计周期,从而让产品迭代速度不断加快;提升产品品质,…

堆排序(六大排序)

前面博客已经分享过堆的知识了,今天我们来分享堆排序。 堆排序 堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。 ★★★需要注意的是排升序要建大堆&#…

3、创建项目,什么是路由

一、创建项目 第一次全局安装脚手架 npm install -g vue/clivue create 项目名 二、什么是路由? 路由就是一组 key-value 的对应关系多个路由,需要经过路由器的管理 1、后端路由: 每个url地址都对应着不同的静态资源对于普通的网站。所有…

24计算机考研调剂 | 【官方】湘潭大学

湘潭大学 考研调剂要求 招生专业: 调剂基本要求: (1)基本要求同《湘潭大学2024年硕士研究生复试录取工作方案》。 (2)初试成绩要求: 初试成绩各单科均须达到A类考生进入复试的初试成绩基本要…

007 日期类型相关工具类

推荐一篇文章 http://t.csdnimg.cn/72F7Jhttp://t.csdnimg.cn/72F7J

golang+vue微服务电商系统

golangvue微服务电商系统 文章目录 golangvue微服务电商系统一、项目前置准备二、项目简介三、代码GItee地址 golang、vue redis、mysql、gin、nacos、es、kibana、jwt 一、项目前置准备 环境的搭建 官方go开发工程师参考地址:https://blog.csdn.net/qq23001186/cat…

刷题记录:最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""。 示例 1: 输入:strs ["flower","flow","flight"] 输出:"fl"示例 2: 输…

【保姆级讲解Edge兼容性问题解决方法】

🌈个人主页:程序员不想敲代码啊🌈 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家🏆 👍点赞⭐评论⭐收藏 🤝 希望本文对您有所裨益,如有不足之处,欢迎在评论区提…

java算法第32天 | 贪心算法 part02 ● 122.买卖股票的最佳时机II ● 55. 跳跃游戏 ● 45.跳跃游戏II

122.买卖股票的最佳时机II 本题中理解利润拆分是关键点! 不要整块的去看,而是把整体利润拆为每天的利润。假如第 0 天买入,第 3 天卖出,那么利润为:prices[3] - prices[0]。 相当于(prices[3] - prices[2]) (prices[…

从IO操作与多线程的思考到Redis-6.0

IO操作->线程阻塞->释放CPU资源->多线程技术提升CPU利用率 在没有涉及磁盘操作和网络请求的程序中,通常不会出现线程等待状态。线程等待状态通常是由于线程需要等待某些事件的发生,比如I/O操作完成、网络请求返回等。如果程序只是进行计算或者简…

常见端口及对应服务

6379 redis未授权 7001、7002 weblogic默认弱口令、反序列化 9200、9300 elasticsearch 参考乌云:多玩某服务器ElasticSearch命令执行漏洞 11211 memcache未授权访问 50000 SAP命令执行 50070、50030 hadoop默认端口未授权访问

6个步骤轻松实现 postman 接口压力测试(建议收藏)

这里讲是postman做接口并发测试,基础用法不做赘述 1、第一步接口可以通的情况下点击右上角save 2、将相应信息填入 3、如果是同一个接口修改不同的值如下图 4、点击左上角Runner 5、选择刚才所建接口集合、填入要执行次数 6、查看运行结果 总结: 感谢每…

【Java项目】基于SSM的高校四六级报名管理系统

背景 随着互联网的迅速推广,大学英语四六级报名管理系统在网络技术的支持下实现了迅速进步。首先,该系统需基于学生的实际需求进行开发,通过深入了解学生的需求来构建具有针对性的功能,同时利用网络为学生提供的便捷性对系统进行…

Redis数据类型bitMap以及解决的相关实际需求

在Redis数据库中,Bitmap(位图)是一种特殊的数据结构,它不是一个独立的数据类型,而是基于String类型实现的。Bitmap主要用于存储大量二进制位(0或1)的数据,这些位可以代表不同的状态或…

鸿蒙开发实例:【demo-搜索历史记录】

图片演示效果: 鸿蒙OS开发更多内容↓点击HarmonyOS与OpenHarmony技术鸿蒙技术文档开发知识更新库gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md在这。或mau123789学习,是v喔 代码演示: // 注:当前代码基于宽度为…