Rails 應(yīng)用有各種類型,規(guī)模也各有不同。有的是一個獨立的龐大的應(yīng)用,全部應(yīng)用都在同一個位置(包括管理界面、API、前端部分以及所有需要的模塊)。另一些應(yīng)用則是劃分成一系列的微服務(wù),服務(wù)之間互相通信,這樣可以把整個應(yīng)用切分成更易管理的部分。
興賓網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項目制作,到程序開發(fā),運營維護(hù)。創(chuàng)新互聯(lián)于2013年開始到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。這種微服務(wù)的架構(gòu)被稱為面向服務(wù)的架構(gòu)( SOA )。雖然我見到過的 Rails 應(yīng)用通常都傾向于成為獨立的程序,不過開發(fā)者也完全可以選擇讓多個 Rails 程序,以及與其他語言或者框架編寫的服務(wù)一起工作來完成任務(wù)。
獨立的程序不意味著一定寫的不好,但是寫的差的獨立程序被拆成微服務(wù)后大多也是很糟糕的。有多種方式可以讓你寫出清晰的(更容易測試的)代碼,同時在需要拆分應(yīng)用的時候也更輕松。
使用微服務(wù)架構(gòu)的 Rails 應(yīng)用的用例本文會討論如何實現(xiàn)一個 CMS 的網(wǎng)站??梢约僭O(shè)是一家大的報紙或者博客,有很多作者負(fù)責(zé)投稿,用戶可以按主題訂閱內(nèi)容。
Martin Fowler 有一篇很不錯的文章,介紹了為什么編輯和發(fā)布應(yīng)該分成兩個不同的系統(tǒng)。我們的用例與此類似,另外我們還要添加兩個模塊:通知和訂閱。
我們的 CMS 現(xiàn)在有四個主要的模塊:
CMS 編輯器:作者和編輯用來創(chuàng)建、編輯和發(fā)布文章。
公共的網(wǎng)站:對外提供服務(wù),瀏覽已發(fā)布的文章。
通知:通知訂閱者有新發(fā)布的文章。
訂閱:管理用戶賬號和訂閱。
Rails 應(yīng)用需要支持 SOA 嗎?是選擇獨立程序還是構(gòu)建成微服務(wù)?這里沒有對和錯之分,不過下面的問題能幫你做出決定。
團(tuán)隊的組織結(jié)構(gòu)是怎樣的?是否選擇支持 SOA 通常與技術(shù)無關(guān),而是在于開發(fā)團(tuán)隊的組織結(jié)構(gòu)。
由四個團(tuán)隊分別負(fù)責(zé)一個主要的模塊,比所有人在整個系統(tǒng)上一起工作要靠譜一些。如果你只有一個團(tuán)隊或者少數(shù)幾個開發(fā)人員,一開始就決定采用微服務(wù)架構(gòu)實際上會減慢開發(fā)的速度,這是因為需要為四個不同的組件直接的通信以及部署增加開發(fā)量。
不同的模塊規(guī)模不一樣?對于本文的例子,有一個問題提現(xiàn)的很好,對外提供服務(wù)的公共網(wǎng)站肯定要比作者和編輯使用的 CMS 編輯器的訪問壓力要大很多。
如果這些模塊都部署成分離的系統(tǒng),我們就可以單獨的控制它們的規(guī)模,為系統(tǒng)中不同的部分采用不同的緩存技術(shù)。你當(dāng)然還是可以堅持采用單一的系統(tǒng),但是那樣的話你就只能為整個系統(tǒng)一次性確定其規(guī)模,而不是對不同的組件分開處理。
不同的模塊使用不同的技術(shù)?對于 CMS 編輯器,你也許想使用 Single Page Application (SPA),采用 React 或者 Angular 技術(shù)。而對外的網(wǎng)站,會使用更傳統(tǒng)一些的服務(wù)端渲染的 Rails 應(yīng)用(為了支持 SEO)。也許通知模塊更適合 Elixir,因為這個語言對并發(fā)和并行處理支持不錯。
模塊的分離,使得你可以為每個模塊選擇最適合的編程語言。
邊界定義現(xiàn)在最重要的事情是定義好系統(tǒng)中模塊之間的邊界。
系統(tǒng)中的某個部分可能是某個外部
Server
的
Client。使用方法調(diào)用還是基于 HTTP 都不重要,它只需要知道它需要與系統(tǒng)中的其他部分進(jìn)行通信。
為此我們需要定義清晰的邊界。
當(dāng)一篇文章發(fā)布時,會發(fā)生兩件事:
首先會把文章的發(fā)布版本發(fā)送給對外的網(wǎng)站,它會返回一個發(fā)布后的 URL。
然后我們把剛創(chuàng)建的公開的 URL、話題、標(biāo)題發(fā)送到通知模塊,后者會通知到所有對話題感興趣的訂閱者。這一步可以是異步的,因為通常會耗費一些時間來通知到每一個用戶,并且這個通知是不會有反饋的。
例如,下面的代碼用來發(fā)布一篇文章。文章本身不會關(guān)心服務(wù)是通過方法調(diào)用還是 HTTP 來調(diào)用的。
class Publisher attr_reader :article, :service def initialize(article, service) @article = article @service = service end def publish mark_as_published call_service article end private def call_service service.new( author: article.author, title: article.title, slug: article.slug, category: article.category, body: article.body ).call end def mark_as_published(published_url) article.published_at = Time.zone.now article.published_url = published_url end end這種方式也可以讓我們方便測試 Publisher 類的功能,我們可以使用 TestPublisherService 來做測試,它會返回預(yù)定義的應(yīng)答。
require "rails_helper" RSpec.describe Publisher, type: :model do let(:article) { OpenStruct.new({ author: 'Carlos Valderrama', title: 'My Hair Secrets', slug: 'my-hair-secrets', category: 'Soccer', body: "# My Hair Secrets\\nHow hair was the secret to my soccer success." }) } class TestPublisherService < PublisherService def call "http://www.website.com/article/#{slug}" end end describe 'publishes an article to public website' do subject { Publisher.new(article, TestPublisherService) } it 'sets published url' do published_article = subject.publish expect(published_article.published_url).to eq('http://www.website.com/article/my-hair-secrets') end it 'sets published at' do published_article = subject.publish expect(published_article.published_at).to be_a(Time) end end end實際上 PublisherService 的具體實現(xiàn)還沒有完成,但是這不妨礙我們?yōu)榭蛻舳耍ù颂幨?Publisher)編寫測試用例來保證其按預(yù)期工作。
class PublisherService attr_reader :author, :title, :slug, :category, :body def initialize(author:, title:, slug:, category:, body:) @author = author @title = title @slug = slug @category = category @body = body end def call # coming soon end end服務(wù)間通信服務(wù)之間需要能夠互相通信。對此作為 Ruby 程序員應(yīng)該是很熟悉了,即使之前沒有做過微服務(wù)的程序。
調(diào)用某個對象的方法,只需要給它發(fā)送消息,例如調(diào)用 Time.send(:now) 就可以改變 Time.now。不管是通過方法調(diào)用還是基于 HTTP 進(jìn)行通信,原理是一樣的。我們要做的是給系統(tǒng)的其他部分發(fā)送消息,通常還需要有回應(yīng)。
使用 HTTP 協(xié)議和微服務(wù)通訊當(dāng)你的應(yīng)用需要一個來自服務(wù)端的立即響應(yīng)才能繼續(xù)執(zhí)行的時候,使用 HTTP 協(xié)議來交互將是不二的選擇。
當(dāng)你需要一個立即響應(yīng)的時候,HTTP 協(xié)議通訊將是不二的選擇。
在下面的例子中,PublisherService 類實現(xiàn)了使用 HTTP Post 方法來和后端的 Faraday 服務(wù)模塊進(jìn)行通訊。
class PublisherService < HttpService attr_reader :author, :title, :slug, :category, :body def initialize(author:, title:, slug:, category:, body:) @author = author @title = title @slug = slug @category = category @body = body end def call post["published_url"] end private def conn Faraday.new(url: Cms::PUBLIC_WEBSITE_URL) end def post resp = conn.post '/articles/publish', payload if resp.success? JSON.parse resp.body else raise ServiceResponseError end end def payload {author: author, title: title, slug: slug, category: category, body: body} end end這段代碼簡單來說就是構(gòu)造了一個需要發(fā)送給后端的數(shù)據(jù),然后通過 HTTP Post 發(fā)送到后端,并且處理從后端的返回的數(shù)據(jù)。但后端返回了正確的數(shù)據(jù),程序?qū)⒔忉屵@個數(shù)據(jù),否則程序?qū)伋鲆粋€異常。在后面我們將對這個代碼進(jìn)行詳細(xì)地解釋。
在代碼中,后端服務(wù)程序的地址保存在常量 Cms::PUBLIC_WEBSITE_URL中,這個常量的值是通過初始化代碼設(shè)置的。這樣做的好處就是允許我們使用環(huán)境變量,根據(jù)部署環(huán)境的不同(比如開發(fā)環(huán)境或者生產(chǎn)環(huán)境)來給它配置不同的值。
Cms::PUBLIC_WEBSITE_URL = ENV['PUBLIC_WEBSITE_URL'] || 'http://localhost:3000'測試我們的服務(wù)現(xiàn)在讓我們來測試 PublisherService 類,看看它是否正常工作。
在這個測試中,由于我們是在開發(fā)環(huán)境中做測試,所以并不能保證后端服務(wù)一直可用,因此我們將使用 WebMock 模塊來模擬到后端的 HTTP 請求,并返回需要的數(shù)據(jù)。
RSpec.describe PublisherService, type: :model do let(:article) { OpenStruct.new({ author: 'Carlos Valderrama', title: 'My Hair Secrets', slug: 'my-hair-secrets', category: 'Soccer', body: "# My Hair Secrets\\nHow hair was the secret to my soccer success." }) } describe 'call the publisher service' do subject { PublisherService.new( author: article.author, title: article.title, slug: article.slug, category: article.category, body: article.body ) } let(:post_url) { "#{Cms::PUBLIC_WEBSITE_URL}/articles/publish" } let(:payload) { {published_url: 'http://www.website.com/article/my-hair-secrets'}.to_json } it 'parses response for published url' do stub_request(:post, post_url).to_return(body: payload) expect(subject.call).to eq('http://www.website.com/article/my-hair-secrets') end it 'raises exception on failure' do stub_request(:post, post_url).to_return(status: 500) expect{subject.call}.to raise_error(PublisherService::ServiceResponseError) end end end處理調(diào)用失敗在系統(tǒng)使用過程中,有一件事情是絕對不可避免的,那就是對于服務(wù)端的調(diào)用可能失?。ǚ?wù)暫時不可用或者網(wǎng)絡(luò)通信超市),我們的代碼應(yīng)該要能夠正確處理這些異常。
當(dāng)遠(yuǎn)端服務(wù)不可用的時候,系統(tǒng)應(yīng)該如何響應(yīng)完全取決于開發(fā)者。在我們的 CMS 應(yīng)用中,當(dāng)遠(yuǎn)端服務(wù)不可用的時候,用戶仍然可以創(chuàng)建和編輯文章,只是不能發(fā)布任何文章。
在上面的測試?yán)又校a包含了對 HTTP Status Code 500 (服務(wù)段出現(xiàn)異常)的處理。當(dāng)測試代碼收到 500 Status Code 的時候,代碼將拋出 PublisherService::ServiceResponseError 這個異常。 ServiceResponseError 這個異常類繼承自 Error 類,目前這個類并沒有對外提供任何有用的信息,僅僅表示發(fā)生了一個錯誤。下面是這個類的相關(guān)代碼。
class HttpService class Error < RuntimeError end class ServiceResponseError < Error end end在 Martin Fowler 的一篇文章中,提出了另外一種處理服務(wù)不可用的方法(在他的文章中,他把這種方法叫做 CircuitBreaker 模式)。簡單來說,這個模式的任務(wù)就是通過某種方式檢測遠(yuǎn)端服務(wù)是否運作正常。如果運作不正常,它將阻止對響應(yīng)遠(yuǎn)端服務(wù)的調(diào)用。
我們也可以通過讓我們的應(yīng)用感知遠(yuǎn)端服務(wù)的狀態(tài)并且做出適當(dāng)?shù)姆磻?yīng)來讓我們的應(yīng)用更強(qiáng)壯。這種系統(tǒng)行為的改變,我們既可以通過類似 CircuitBreaker 的模式來自動實現(xiàn),也可以通過用戶手動關(guān)閉系統(tǒng)的某些功能來實現(xiàn)。
在我們的例子中,如果我們可以在現(xiàn)實 Publish 按鈕之前檢查一下遠(yuǎn)端 Publish 服務(wù)是否可用,那么我們就可以直接避免對不可用服務(wù)的調(diào)用。
使用隊列進(jìn)行通信HTTP 并非是與其他服務(wù)通信的唯一方式。隊列是不同的服務(wù)之間傳遞異步消息的很好的選擇。如果對于要做的事情不需要消息接收者立刻反饋,那就非常適合這種方式(例如發(fā)送郵件)。
我們的 CMS 應(yīng)用中,文章發(fā)布后,訂閱文章的主題的用戶會被通知到(通過郵件,或者網(wǎng)站通知或者推送消息),告知他們有感興趣的文章被發(fā)布。我們的程序并不需要
Notifier服務(wù)的反饋,只需要把消息發(fā)給它就行了。
之前的一篇文章,我介紹了如何使用ActiveJob,Rails 自帶的,用來處理這種后臺或者異步處理的任務(wù)。
ActiveJob 要求接收代碼也需要運行在 Rails 環(huán)境,不過它確實是一種很好的選擇,簡單易用。
使用 RabbitMQRabbitMQ是 Rails(以及 Ruby)之外的另一個選擇,可以作為不同的服務(wù)之間的一個通用的消息處理系統(tǒng)。通過 RabbitMQ 也可以處理遠(yuǎn)程方法調(diào)用(RPC),不過更多的是使用 RabbitMQ 向其他服務(wù)方式異步消息。這里有很好的 Ruby 的使用教程。
下面的類用于向
Notifier服務(wù)發(fā)送消息,通知有新文章發(fā)布。
代碼可以這樣調(diào)用:
NotifierService.new("Soccer", "My Hair Secrets", "http://localhost:3000/article/my-hair-secrets").call總結(jié)微服務(wù)并不可怕,不過確實需要仔細(xì)的處理。它會帶來很多好處。我的建議是從一個有著清晰邊界的小系統(tǒng)開始,這樣你可以很容易的劃分服務(wù)。
更多的服務(wù)意味著更多的開發(fā)運維工作(你不再只是部署一個單獨的程序,而是需要部署多個小服務(wù)),這時你也許有興趣看一下我寫的如何部署到 Docker 容器。
標(biāo)題名稱:Rails微服務(wù)架構(gòu)
文章源于:http://jinyejixie.com/article2/chddic.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣、網(wǎng)站收錄、網(wǎng)站導(dǎo)航、定制開發(fā)、微信小程序、網(wǎng)站維護(hù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)