字幕列表 影片播放
One of the biggest struggles for new software engineers coming to go or any other language really is how to structure your software.
對於剛接觸 go 或其他語言的軟件工程師來說,最大的困難之一就是如何構建軟件結構。
And in this video, I'm going to show you a pattern or a rule that you can follow to every time create clean and maintainable software using the repository pattern.
在本視頻中,我將向你展示一種模式或規則,你可以遵循這種模式或規則,使用版本庫模式創建乾淨、可維護的軟件。
So before we go and look into what it is, let me show you this example application without using the repository pattern.
是以,在我們研究它是什麼之前,先讓我向你展示這個不使用存儲庫模式的示例應用程序。
And if this sounds familiar, the way that I'm structuring this application, then I think that this video is going to be really important for you.
如果這聽起來很熟悉,也就是我構建這個應用程序的方式,那麼我認為這段視頻對你來說將非常重要。
So this is pretty simple.
所以這很簡單。
We have a user model.
我們有一個用戶模型。
You could imagine this is you're starting users on your database.
你可以想象一下,這是你在數據庫中啟動用戶。
And here we start actually pretty well because we start with an application struct that we can start injecting dependencies.
在這裡,我們的起點其實很高,因為我們有了一個應用程序結構,可以開始注入依賴關係。
We'll go over that and how dependency injection is also used in the repository pattern, but it's actually different.
我們將詳細介紹這一點,以及依賴注入是如何在版本庫模式中使用的,但它實際上是不同的。
It's not the same thing.
這不是一回事。
So pretty simple here on the main, we're just initializing this database.
是以,主界面非常簡單,我們只需初始化這個數據庫。
It doesn't mean it doesn't matter what it is.
這並不意味著什麼都不重要。
And here we initialize our application.
在這裡,我們對應用程序進行初始化。
So this is how I usually structure APIs or any kind of project that has global state that I want to pass.
是以,我通常會這樣構建應用程序接口或任何一種需要傳遞全局狀態的項目。
So here we have an endpoint which creates a user.
在這裡,我們有一個創建用戶的端點。
So let's take a look at it.
讓我們一起來看看吧。
This is the payload it receives.
這是它接收到的有效載荷。
We are just decoding these requests.
我們只是對這些請求進行解碼。
And then here we are creating a user on the database.
然後,我們在數據庫中創建一個用戶。
So here is clearly, we are clearly using SQL to inject, to insert the user in the database.
是以,很明顯,我們在使用 SQL 注入,在數據庫中插入用戶。
So we know the table name, we know that we're using SQL.
是以,我們知道了表名,也知道了我們使用的是 SQL。
And then here is some business logic.
然後是一些業務邏輯。
We are sending a welcome email to the user.
我們將向用戶發送歡迎電子郵件。
So here we are actually doing something good because we are abstracting the malware into its own package and then we are sending the email.
是以,我們實際上是在做一件好事,因為我們將惡意軟件抽象成了自己的套裝軟體,然後發送電子郵件。
So this is the application.
這就是申請。
It's pretty simple, but you can start to see the problems.
這很簡單,但你可以開始看到問題所在。
The first is that there is a big tight coupling between the database and the service.
首先,數據庫和服務之間的耦合度很高。
So if you think about the three layers that we might have, the transport, the service, and the storage, this is all of them combined here.
是以,如果你想想我們可能有的三個層,即傳輸層、服務層和存儲層,這裡就是它們的結合體。
So this is the transport layer, which is HTTP.
這就是傳輸層,也就是 HTTP。
This is the service because this is the business logic.
這就是服務,因為這就是業務邏輯。
So we're sending emails and we're getting the user.
是以,我們正在發送電子郵件並獲取用戶。
And we might even do some other business logic here.
我們甚至可以在這裡執行一些其他業務邏輯。
So this is clearly a service with data access embedded into it.
是以,這顯然是一種嵌入了數據訪問功能的服務。
And why is this bad?
這有什麼不好呢?
Firstly, it basically fails all of the good practices in software engineering.
首先,它基本上不符合軟件工程中的所有良好做法。
And then it also is untestable.
而且,這也是無法檢驗的。
So I'm not going to even try to test this because we need to mock the whole SQL driver here.
是以,我甚至都不打算測試這一點,因為我們需要在這裡模擬整個 SQL 驅動程序。
We need to mock the malware as well.
我們還需要模擬惡意軟件。
So you can see that this does not obey the single responsibility principle.
由此可見,這並不符合單一責任原則。
So this does a lot of things here.
是以,它可以做很多事情。
So let's take a look at how the repository pattern can actually help us.
是以,讓我們來看看存儲庫模式究竟能如何幫助我們。
So the repository pattern, and basically in a few words, this is a way to organize and abstract data access logic from the business logic or the application logic.
是以,存儲庫模式,基本上用幾句話來說,就是一種從業務邏輯或應用邏輯中組織和抽象數據訪問邏輯的方法。
Now let's take a look at how we could leverage this.
現在,讓我們來看看如何利用這一點。
So if we have, this is usually application, even if you're building an API or something else, you're going to have layers.
是以,如果我們有,這通常是應用程序,即使你正在構建一個應用程序接口或其他東西,你也會有層。
Or at least you should start to have layers because this adds a separation of concerns and a single responsibility for each layer.
或者至少應該開始分層,因為這樣可以將關注點分開,每一層都有單一的責任。
So let's go back to our transport layer, which is our HTTP.
讓我們回到傳輸層,也就是 HTTP。
You can see here it's on the main.
你可以在這裡看到它在主線上。
That is fine for now.
現在這樣就可以了。
But once we get to the service, we got the service and the storage all mixed up together as we have seen already.
但是,一旦我們要提供服務,我們就會把服務和存儲混為一談,正如我們已經看到的那樣。
And we have a storage because most applications require a state.
我們有一個存儲空間,因為大多數應用程序都需要一個狀態。
Either you're starting on a database or you're starting on just RAM.
要麼從數據庫開始,要麼從內存開始。
It doesn't matter.
沒關係。
It's still a storage.
它仍然是一個存儲器。
Or even if you're just starting on disk files on your server or something like that, it's still state that you're usually storing.
或者,即使你只是從服務器上的磁盤文件或類似文件開始,它仍然是你通常存儲的狀態。
Most applications use states.
大多數應用程序都使用狀態。
Now this here, if we apply these layers, we're automatically going to get separation of concerns, single responsibility.
現在,如果我們應用這些層級,就會自動實現關注點分離和單一責任。
We're going to encapsulate the data access and we're going to make our code testable.
我們將對數據訪問進行封裝,並使我們的代碼可測試。
And all of this is tied to dependency injection.
所有這些都與依賴注入有關。
And I'm going to show you how you can implement the repository pattern without it and why the repository pattern is not the same as dependency injection.
我將向你展示如何在沒有依賴注入的情況下實現版本庫模式,以及為什麼版本庫模式與依賴注入不同。
So let's go back to the code and refactor step-by-step into a better application.
是以,讓我們回到代碼中,一步步重構成一個更好的應用程序。
So going back to the editor, what I have quickly done here is that I have just created this service.go file.
回到編輯器,我在這裡快速創建了這個 service.go 文件。
And what we're going to do is that we're going to go step-by-step into these layers until we have the repository pattern implemented.
我們要做的就是一步一步地進入這些層,直到實現存儲庫模式。
So what we want to do is make what we have drawn there on a diagram.
是以,我們要做的就是把我們在這裡畫的東西變成圖表。
The first step is to create the user service.
第一步是創建用戶服務。
Now, there is some problems here still.
現在,這裡仍然存在一些問題。
And the first one is here.
第一份就在這裡。
What we are passing is not an interface.
我們傳遞的不是接口。
It's not something abstract.
這不是抽象的東西。
It is an implementation.
這是一種實施。
And this is the first issue that we're going to solve next.
這就是我們接下來要解決的第一個問題。
But first, we're getting a good start.
但首先,我們有了一個良好的開端。
We have a new user service that we have here, a constructor.
我們這裡有一個新的用戶服務,一個構造函數。
All fine.
一切正常。
This is optional, of course, if you want to just initialize it without the constructor, that's fine.
這當然是可選的,如果你想不使用構造函數而直接初始化,也可以。
But here we have another method.
但在這裡,我們還有另一種方法。
For example, getOneById.
例如,getOneById.
But here is our user create method that we had before.
下面是我們之前使用的用戶創建方法。
So we are still inserting the user into the database.
是以,我們仍在將用戶插入數據庫。
So this is still SQL.
是以,這仍然是 SQL。
And then here is the actual business logic using the mailer.
下面是使用郵件發送器的實際業務邏輯。
So that is fine, but we can do some improvements here.
是以,這很好,但我們可以在這裡做一些改進。
At least two improvements I see.
我認為至少有兩點改進。
And the first improvement is to actually move this data access logic out of the service layer, because this is not the place for it.
第一項改進是將數據訪問邏輯從服務層中移出,因為服務層並不適合數據訪問邏輯。
So we can just remove this for now.
是以,我們可以暫時把它刪除。
And let's keep the mailer here, because this is what we need.
讓我們把郵件留在這裡,因為這是我們需要的。
And along the way, what we're going to have is that we're going to have a function called createUsers.
一路上,我們將使用一個名為 createUsers 的函數。
I have my cursor auto-completion enabled, but let's ignore that for now.
我啟用了遊標自動補全功能,但現在先不管它。
And then the second improvement is that we can just come here and we can see that we are initializing the mailer here.
第二項改進是,我們只要來到這裡,就能看到我們正在初始化郵件發送器。
So every time we create a user, we're initializing the mailer.
是以,每次創建用戶時,我們都在初始化郵件發送器。
We're doing something good, which is we are using a mailer.
我們正在做一件好事,那就是使用郵寄工具。
Actually, it's not good because we're using the implementation, but how can we solve this?
事實上,這並不好,因為我們使用的是實現方式,但如何解決這個問題呢?
Check it out here on the mailer.
請點擊這裡查看郵件。
And this is a hint how we're going to do the structure for the storage as well.
這也為我們如何設計存儲結構埋下了伏筆。
So here we have a mailer.go.
是以,我們有一個 mailer.go。
And here we have a client interface.
在這裡,我們有一個客戶端界面。
So anyone who implements this client interface is going to be an implementation of a mailer.
是以,任何實現該客戶端接口的人都將是郵件發送器的實現者。
So here I have a mail trap.
所以,我這裡有一個郵件陷阱。
This is a service that uses to send emails.
這是一種用於發送電子郵件的服務。
And this is an implementation of that mailer.
這就是該郵件的實施。
You can see here the send code.
您可以在這裡看到發送代碼。
All this does is that communicates with the mail trap API and sends an email.
它的作用就是與郵件陷阱 API 通信併發送電子郵件。
So instead of passing here the implementation, let's pass in a mailer, which is going to be a mailer.client.
是以,與其在這裡傳遞實現,不如傳遞一個郵件發送器,也就是 mailer.client。
And here is an interface which we can just come here and let's do mailer.send and pass in our arguments as we did before.
這裡有一個接口,我們可以直接在這裡發送 mailer.send,然後像之前那樣傳入參數。
So this is one step cleaner because we can choose which mailer implementation we want to pass.
這樣就更簡單了,因為我們可以選擇要通過哪個郵件發送器來實現。
So let's come here to the main.
那麼,讓我們來看看主要部分。
Let's first create our service.
首先創建我們的服務。
There we go.
好了
And we also need to update the constructor like this to pass in the mailer interface.
我們還需要像這樣更新構造函數,以傳入郵件發送器接口。
Now we need to pass in a mailer.
現在我們需要通過郵寄的方式。
So let me just initialize this dependency as well.
是以,讓我把這個依賴關係也初始化一下。
There we go.
好了
Now what we have just seen before is actually something I want to show you.
現在,我們剛剛看到的,其實是我想給你們看的東西。
This here is this here actually the mailer is dependency injection.
這裡是這裡,實際上郵件發送器是依賴注入。
So we have an abstraction and we are injecting any dependency we want at runtime.
是以,我們有了一個抽象概念,可以在運行時注入任何我們想要的依賴關係。
This database here is not dependency injection.
這裡的數據庫不是依賴注入。
This is an implementation.
這是一個實施方案。
So you can see why the repository pattern is different than dependency injection by this example right here.
通過這個例子,你就能明白為什麼資源庫模式不同於依賴注入模式了。
This is a clear distinction between those two.
這是兩者之間的明顯區別。
Now the final step is coming here to storage.
現在,最後一步就是來這裡儲存。
I have just created this folder and I'm going to call a file called store.go.
我剛剛創建了這個文件夾,並將調用一個名為 store.go 的文件。
So what is the store.go going to have?
那麼 store.go 會有什麼呢?
It's going to basically just have an interface for the storage.
它基本上只有一個存儲界面。
So let's just call this storage and this is going to be an interface.
是以,我們姑且將其稱為存儲,這將是一個接口。
Yeah, just like so.
對,就像這樣
Now we need to...
現在我們需要
And this basically holds the definition or the blueprint for the create user which receives the user and returns an error.
這基本上是創建用戶的定義或藍圖,它接收用戶並返回錯誤信息。
What we need to do is move the user out of the main function.
我們需要做的是將用戶移出主函數。
And you can see that this structure is going to make us write cleaner codes by default because first thing is that the user does not belong here on the transport layer or the entry point of the application.
首先,用戶不屬於傳輸層或應用程序的入口點。
This is a model, a representation of the user on the database.
這是一個模型,是用戶在數據庫中的代表。
So it doesn't make sense to have it on main.
是以,把它放在主幹道上沒有任何意義。
You can see how the codes made us change the user to this layer without thinking about it.
您可以看到代碼是如何讓我們不假思索地將用戶更改為這一層的。
So you can see how this creates good practices already.
由此可見,這已經創造了良好的實踐。
Now let's create an implementation of SQL.go.
現在,讓我們創建 SQL.go 的實現。
For example, let's create this SQL.go file.
例如,讓我們創建這個 SQL.go 文件。
And just one thing is that usually I wouldn't separate things like this.
還有一點,通常我不會把這樣的事情分開。
I wouldn't create just an SQL.go file to have the whole code here.
我不會只創建一個 SQL.go 文件,而把整個代碼都放在這裡。
I would create small repositories like the user storage, the newsletter store, the post store.
我會創建小型存儲庫,如用戶存儲庫、時事通訊存儲庫和帖子存儲庫。
We do this in the backend engineering course pretty well.
我們在後端工程課程中很好地做到了這一點。
This is just to demonstrate you for this video so we can move a little bit quicker.
這只是為了在視頻中給你做個示範,以便我們能更快一點。
So let's create an actual implementation of an SQL store.
是以,讓我們創建一個 SQL 存儲的實際實現。
And let me just create also a constructor.
讓我也創建一個構造函數。
And then let's have the createUser method, which is basically all of this code here.
然後是 createUser 方法,基本上就是這裡的所有代碼。
Just this bit, not the email sending, because this is the access to the storage layer using SQL.
只有這一點,而不是發送電子郵件,因為這是使用 SQL 訪問存儲層。
So let's just copy this constructor.
是以,我們只需複製這個構造函數。
Now let's go back to the service and let's remove the database into a storage.
現在,讓我們回到服務,將數據庫移至存儲區。
So you can see that we have storage.storage.
所以你可以看到我們有 storage.storage。
And what I mentioned to you before is that if you pass this as a user repository, this is going to be even cleaner because they're just passing the parts of the user repository.
我之前跟你說過,如果你將其作為用戶存儲庫傳遞,情況會變得更簡單,因為他們只是傳遞用戶存儲庫的部分內容。
And if for some reason the user service communicated with posts, you could have the posts repo as well.
如果出於某種原因,用戶服務與帖子進行了交流,你也可以擁有帖子 repo。
So this makes it even better to test.
是以,這樣的測試效果更好。
But for our example, let's just keep the like we did here.
但在我們的示例中,還是讓我們保持原來的樣子吧。
So what I have just done is updated the constructor to receive the interface.
是以,我只是更新了構造函數,以接收接口。
So we're just receiving interfaces right now.
所以我們現在只是接收接口。
And here we can actually delete this method.
在這裡,我們可以刪除這個方法。
This was one of the ones that we didn't have.
這是我們沒有的東西之一。
And the create, we can actually pass a store.user.
創建時,我們可以通過 store.user.
Usually what you do is create here a new type to have the payload.
通常情況下,你要做的是在這裡創建一個新的類型,以容納有效載荷。
This could be, for example, createUserRequest, which we could actually...
例如,這可以是 createUserRequest,我們實際上可以...
Let's actually do this so we can have an idea of how I would do this.
讓我們實際操作一下,這樣我們就能知道我會怎麼做了。
You might want to create a user with different parameters.
您可能想創建一個具有不同參數的用戶。
So this is why I'm doing this.
這就是我這麼做的原因。
And then what you're going to do is that let's uncomment this.
然後你要做的就是取消註釋。
Let's go to the storage.
我們去儲藏室。
And let's create the user.
然後創建用戶。
And here is the user struct from the storage.
下面是存儲中的用戶結構。
Let's handle the error.
讓我們來處理這個錯誤。
And I think this is pretty much it.
我覺得差不多就是這樣了。
Let's delete this comment as well.
把這條評論也刪了吧。
And so this is pretty much the service.
服務內容大致如此。
You can see how cleaner it is and how we have separated the concerns of the older code.
你可以看到它是多麼的簡潔,以及我們是如何將舊代碼的關注點分離出來的。
You can see that we just communicate with the storage, which could be anything.
你可以看到,我們只是與存儲進行通信,而存儲可以是任何東西。
This could be a MongoDB, could be Redis.
這可能是 MongoDB,也可能是 Redis。
In this case, it's SQL, but you don't have any reference of it here.
在這種情況下,它是 SQL,但在這裡沒有任何參考。
And here is the mailer, which we don't even know which mailer it is.
這就是我們甚至不知道是哪份郵件的郵寄者。
And actually, recently I had to switch mailers for one of my projects.
實際上,最近我不得不為我的一個項目更換郵寄工具。
All I needed to do was come to the mailer and create a new implementation for the service.
我所需要做的就是來到郵寄公司,為這項服務創建一個新的實施方案。
And that's how easy it is.
就是這麼簡單。
I don't need to change any code of the services.
我不需要修改任何服務代碼。
Now, the last step is tying up everything correctly.
現在,最後一步就是正確地綁好所有東西。
We need to go to the main and inject the right dependencies.
我們需要進入主程序並注入正確的依賴項。
So we have just created the mailer.
是以,我們剛剛創建了郵件發送器。
We have here the database.
我們這裡有數據庫。
Let me actually comment this as well.
其實,我也可以這樣評論。
And this is the service.
這就是服務。
The service now receives a storage.
現在,服務將收到一個存儲空間。
So we need to initialize this storage.
是以,我們需要初始化這個存儲空間。
Let me do here.
讓我來吧。
Actually, yeah, let's just call this storage for now.
事實上,是的,我們暫且稱其為 "存儲 "吧。
It's fine.
沒關係。
And as you can see, we have here the constructor for the SQL storage.
正如你所看到的,這裡有 SQL 存儲的構造函數。
We're passing it into the service.
我們要把它傳遞到服務中去。
And then the service, what it could do is, for example, let's change our application to receive the user service, just like so.
然後,服務可以做的是,例如,讓我們改變應用程序,接收用戶服務,就像這樣。
And here, let's update this to receive the user service.
在這裡,讓我們更新一下,以接收用戶服務。
So all you do here on the transport layer, because we also need to update this.
是以,你在傳輸層上所做的一切,因為我們也需要更新。
And so all we do here on the transport layer is decode the request, because this is a JSON request.
是以,我們在傳輸層所做的就是對請求進行解碼,因為這是一個 JSON 請求。
It could be an RPST request.
可能是 RPST 請求。
It could be anything you want here.
在這裡,你想做什麼都可以。
Any transport you want, you could add it here.
你可以在這裡添加任何你想要的交通工具。
And it really doesn't matter.
這真的沒關係。
So anything related to decoding the request, it's on the transport layer.
是以,任何與解碼請求有關的工作都在傳輸層上進行。
And then we create the user finally, which all we do is go to the application, go to the service, and create the user.
最後,我們創建用戶,我們要做的就是進入應用程序,進入服務,然後創建用戶。
And here, actually, we have duplicated data, because I should delete this one.
實際上,這裡的數據是重複的,因為我應該刪除這一條。
We have already created this on the service, which is service.createUserRequest, actually.
實際上,我們已經在服務中創建了這個功能,即 service.createUserRequest。
It's on the main package.
在主包裝上。
So we have basically just implemented the repository pattern.
是以,我們基本上只是實現了存儲庫模式。
Let's go over this one more time so that you can see the whole structure in place.
讓我們再複習一遍,以便你能看到整個結構的到位情況。
The first thing is that we are initializing everything here on the main.
首先,我們要在主系統上初始化一切。
So the database dependency, the mailer, the storage, and the service as well.
是以,數據庫依賴性、郵件發送器、存儲和服務也是如此。
So the service is what's going to communicate with the transport layer.
是以,服務將與傳輸層進行通信。
And here you can see the implementation on the transport layer.
在這裡,你可以看到傳輸層的實現。
So this is the implementation details to decode the JSON request to create the user.
這就是解碼 JSON 請求以創建用戶的實現細節。
We are just communicating with the service, and we just send this to the user.
我們只是與服務進行通信,並將其發送給用戶。
So this is the transport layer.
這就是傳輸層。
If we go a little bit deeper into the service, you can see that the service communicates with the storage and a mailer.
如果我們再深入瞭解一下該服務,就會發現該服務與存儲和郵件發送器進行通信。
All of these are dependencies and are abstract.
所有這些都是抽象的依賴關係。
So this is an interface, and this is the trick for the repository pattern is to use interfaces and inject dependencies using structs.
是以,這是一個接口,而存儲庫模式的訣竅就在於使用接口,並使用結構體注入依賴關係。
And here, what we do is that we communicate with the storage.
在這裡,我們所做的就是與存儲進行溝通。
And then we send a mail because this is part of the business logic that we have.
然後我們發送郵件,因為這是我們業務邏輯的一部分。
And finally, if we go to the storage, we have here our SQL storage implementation.
最後,如果我們轉到存儲,這裡有我們的 SQL 存儲實現。
And all we do is insert the user on the table.
我們要做的就是在表格中插入用戶。
So these are the results of our refactor.
這些就是我們重構的結果。
We basically separated everything into concrete layers.
我們基本上把所有東西都抽成了具體的幾層。
Each of them are separations of concern.
每一個都是令人擔憂的分離。
So just for the transport service, they are singly responsible for one thing and one thing only.
是以,就運輸服務而言,他們只負責一件事,而且只有一件事。
And they encapsulate the data access on the storage layer here.
它們將數據訪問封裝在存儲層上。
And we made our code way more testable because what we can do now is inject in-memory implementations of, for example, the storage.
我們的代碼可測試性大大提高,因為我們現在可以對存儲等功能注入內存實現。
What we need to do just quickly to show you is what you could do is create here, for example, an in-memory.go.
我們需要做的只是快速向你展示你可以做什麼,例如,在這裡創建一個 in-memory.go。
So what you do on this implementation is just store the users on the slice in-memory.
是以,在此實現中,我們只需將用戶存儲在內存片上。
So this is really good for testing.
是以,這非常適合測試。
And once you create a test for your application, what you could do is just inject this implementation instead of the SQL one if you don't want to test the initialized SQL storage for that.
為應用程序創建測試後,如果不想測試初始化的 SQL 存儲,可以直接注入此實現,而不是 SQL 實現。
So this is the power of the repository pattern.
這就是存儲庫模式的威力。