Golang / Go Crash Course 03 | Implementing Clean Architecture principles in our REST API

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's going on guys in this video we're going to apply the principles of the clean architecture by (Uncle ) Bob Martin in our Golang API but first let's do a quick overview of those principles so in order to follow a clean architecture approach in our application the application should be independent of frameworks so we are not tied to a specific framework our application should be able to replace the HTTP framework for example our application should be testable so we can test the business logic without the database or the UI we need to be independent of the UI in our case we are working on a restful api so we don't care if the client is a web browser or a mobile application or an IOT device our application should be also independent of the database so we should be able to change the persistence layer we can start using Firestore and then we can replace it by MySQL or MongoDB for example and our application should be also independent of any external agency so our business rules don't know anything about any external factors okay remember to share like and subscribe let's make Uncle Bob proud :-) and let's get started... in a previous video we created a repository to connect our application to the firestore database and let's go there so this is our repository we created an interface and we created a couple of methods one (function) to save the posts and another one to get all the existing posts from the firestore database so having this interface will allow us to keep independent the concrete implementation from the rest of the application so the rest of application like in this case this route is just accessing this interface this one actually the post repository interface and here we can change the concrete implementation so we're going to make some changes in the structure but having an interface is a good first step I'm going to copy this and I'm going to create a new file and this file is going to be the concrete implementation for the firestore database so this is going to be firestore-repo.go I'm going to paste this here and I'm going to remove the interface and here I'm going to remove everything but the interface so this is going to be our interface now we only have this implementation that is the firestore implementation for that interface but we may also have a MySQL repo for example or we can also have a MongoDB repo implementing that interface okay so other changes that we need to do here for example this constructor this needs to be NewFirestoreRepository because this is an a specific implementation for that database in the case of MongoDB we're gonna have another constructor another function that is gonna be NewMongoRepository and the same for MySQL: NewMySQLRepository so here I'm going to add the package so we don't get any of these errors and the same here now we are independent of the database so we can use Firestore, Mongo, MySQL, Postgres, Oracle or whatever you want to use (as the database) if we go to routes.go we're gonna see that we have mixed up some business logic like this with code that is specific for a controller like setting the content type of a header or writing the response or encoding a specific object so we're going to need to split this in a service layer the service layer is going to be the use case or the business logic and then the controller the controller is going to be the the code that is going to be responsible of interacting with the API layer so I'm going to create a new folder that's gonna be service and another one that is gonna be controller and our service is going to be called from the controller and the service is going to call the repository to add new posts and get existing posts from the database so I'm going to create a new file here so this is gonna be post-service.go and here this is gonna be post-controller.go okay let's create the service first so the package is gonna be service and this is going to be the interface for the service type PostService interface and we gonna have three methods we're gonna have a validate method that's going to receive a post actually the reference to the post *entity.Post we need to import that package and this is going to throw an error if there are any errors the other method is going to be the create method that's going to receive a post I'm going to copy this and it's going to return either a post or an error and the last one is going to be a FindAll method that is going to return either an array of posts or an error like that okay okay so in order to implement this interface we are going to create a struct that's going to be service and we're going to create a function that is going to be a constructor function we're going to call it NewPostService that's going to return a PostService it's going to return a struct... this struct that is going to implement this PostService interface like this okay and now let's implement these three functions so.... func and it's gonna be service this is the way that we have to use to specify that this struct is going to implement that function the same for the create function and the same for the FindAll function okay and here what we're going to validate is if we receive an empty post or a nil value as a post and in that case we are going to return an error we can use this method from the errors package this should import the errors package yes here and here we can pass the message the error message actually we can say the post is empty okay and some other conditions that we can do here is the title if post that tile is empty like this and here I forgot to return the error okay and we can do something similar here we can say that the post title is empty and if we don't get any errors we'll return nil and here what we need to do is we need to grab the logic to create a new post from here we were using here the rand library to generate a random identifier so I'm going to copy this from here sorry here and we need to add the repo so we can access it from here and I'm going to grab that from the routes file from here okay let's go back to the service and I'm going to add that here I need to import that package okay here I already have the reference so I just need to do that and you need to return this okay that's it for the create method of our service and now I need to return repo.FindAll() I forgot to add this okay and that's it for the service and I don't know why is showing an error here what is the problem....oh yeah because this is not NewPostRepository this is NewFirestoreRepository we changed the name okay and now let's move on to the controller so I'm going to grab.... first I'm going to add the package controller and now I'm going to grab pretty much all the code from here and here we need to use the service so I'm going to import the service okay I'm going to use service.PostService and this is gonna be NewPostService yeah that's the right path okay and here instead of using the repo I'm going to use this service so I'm going to rename as service and I'm going to rename this to avoid this issue it's a naming issue so I'm going to use PostService (In don't know why the package was removed...) okay and now from the controller we are using the service here and I need to add the same here so I'm going to remove these two lines and I'm going to use create and I'm going to pass the post as a parameter I need to also validate that the post contains the information that we need so I'm going to call the validate method from the post service: postService.Validate and I'm going to pass a reference to the post and I need to assign the error in case we get any errors and then I'm going to add this code to handle that error and here I created a class to avoid hardcoding this JSON and this is errors.ServiceError ... and here I need to pass the message and I need to import that package okay if we go there is basically a struct that has a message that's the only attribute okay let's go back to the controller and let's do the same for the rest of the errors first I need to use this I need to use the encoder and we need to encode that error and yeah this is one we need to pass this as the message of the struct... like that and I'm going to do the same for the rest of the error messages and the same here okay I'm going to use a different name for this variable in this case the message is going to be the message that we are setting here in the validate method it's going to be or the post is empty or the post title is empty so let's go back and the way we have to access to that message is by using the error function err1.Error() and this error function is going to return a string that's going to be the error message okay and here I forgot to handle the error so I'm going to here I'm going to receive the post and an error and I need to handle that error again result here is two and here I need to use here result okay and here we are going to set a message error saving the post okay in that's that for the AddPost function so we have our controller ready and the controller is using the PostService and the PostService is using the FirestoreRepository but we can also change this if we create a specific implementation for Mongo we can create a function that is going to be the constructor to access this Mongo repository and the same for MySQL or any other database and here I'm going to remove this file completely and I need to add an interface for this controller I forgot to add that so I'm going to do that right now type PostController interface and it's going to declare these two methods the GetPosts method that's going to receive the HTTP response writer and the HTTP request and the AddPost method here I'm going to capitalize the first letter just to keep the convention okay okay and another thing that we need to change in order to be independent of frameworks here we depend on Mux so the implementation of our API depends on Mux so we need to create a layer if tomorrow we want to use a specific HTTP server or if we or if we want to implement our own HTTP framework we should be able to do it so we're going to make a few changes here our router is implementing this handle func function and that's pretty much the only thing that is doing but we are passing here the get method the post method so what are going to do is we are going to create a router interface that is going to implement the get method and the post method if we add new operations in our API to handle all the HTTP verbs or methods we're gonna need to add the put method the delete method the patch method etc so let's do that I'm going to create a new folder that is gonna be http so I'm going to create a new file and this is going to be the interface I'm going to call it router.go and the package it's gonna be router and I'm going to create the Router interface... and here we are going to create the different methods let's go back to the server we're going to need something that receives the uri or the resource and a function that has two arguments these two arguments the response writer and the request so I'm going to grab this let's start by adding the get method and it's going to receive a URI that is going to be a string and a function that is going to receive the response writer and the request and we also need to implement a post method like that and I think we should be fine yeah and the last thing here is the initialization of the HTTP service we're going to need to add another function here that is going to be the serve function that's going to receive the port where the service is going to be listening okay now that we have our router interface let's create concrete implementation for the Mux framework so I'm going to create a new file here there's going to be mux-router.go the package is going to be router and in order to implement the router interface I gonna need to declare a struct like this type muxRouter struct and I also going to need to implement a function to have a constructor in order to be able to access this a specific implementation so you NewMuxRouter and the return type of this function is going to be the interface..Router... and it's going to return the struct like this I'm going to grab the signatures from here and I'm going to start working on the implementation for the Mux HTTP framework we'll need to pass the struct the same from the post and the same for the serve method okay and here I need to access to the Mux library and I'm going to create a variable for that and I'm going to call it muxDispatcher and here I need to use this mux.NewRouter .... like that and I need to import that library here I'm going to grab the dependency from here oh okay that's the package like that okay and for the other operation we need to use the Mux library by using this mux dispatcher and here we need to call the handle func function and to this function we need to pass the URI and function and we need to assign the get method for this like this methods and it's going to be pretty similar for the post method we'll need to change this and here we are going to use this function let's go back let's paste it here and the router is gonna be actually the muxDispatcher and here I'm going to add a log from the... fmt, fmt.Printf and the message Mux HTTP server running on port... and then we need to pass the port here okay now we have our first implementation of the router so we need to replace this I'm going to create a variable here and it's going to be HTTP router and we need to import the package replacing this one it's going to be http and here we're going to use router.Router ....this is the interface and this is going to be equals to router.NewMuxRouter ...this is the function that we are using to create a new instance of the max router.... okay and I'm going to remove this we're not going to use it and here I need to replace this by this new router that implements the interface that we created so this is going to be GET and we are going to pass the URI and we need to do the same here for this operation it's going to be httpRouter.GET and we'll remove this and the same for the post method it's going to be POST, let's remove this and this and we need to use the serve method httpRouter.SERVE and we need to pass the port like this okay I'm going to comment these two lines and let's test this.... ...okay and here we have our Mux HTTP server running on port 8000 let's go to postman and let's try the get operation oh yeah we don't have any handler for that URL so I'm going to remove this and I'm going to test just this okay let's try this okay and it's up and running so we're good great okay and now what I need to do is I need to use the controller this one from the server so I need to import the controller package here and I need to add the post controller here it's going to be controller.PostController this is the interface that we created and it's going to be controller.NewPostController ..... okay I forgot to add a function and I need to copy this from here and here I need to also add the struct so this is going to be type controller struct and this is going to return the post controller interface and it's going to return this controller and I need to add the controller here to make it implement each of these methods and the same for the other function okay now we have our controller implementing the two methods and we should be able to use it from here okay yes we don't get any errors here so here we need to pass the operation actually the GetPosts operation and here for the POST method we need to pass the AddPost operation (or the AddPost function) okay let's check yes okay... great okay I'm going to stop the server I'm going to start it again and let's go to firestore and we don't have any documents let's refresh this just in case but we don't have any documents here so let's create one okay let's check in firebase if we have this new element yes we have this new document here and let's run the get operation is actually posts and yes we get the document that we just created here okay but Mux is just one possible implementation of this router interface and we can also use any other HTTP framework such as Chi (I don't know how to pronounce it) but we are going to implement another router using that specific framework so we'll create a new file here I'm going to call it chi-router.go and we are going to implement this router interface but using this specific framework so package is gonna be router... and we need to create a structure type chiRouter struct .....like that and we're going to create a function NewChiRouter and it's going to return the interface that we have for this HTTP layer and it's going to return a reference to the chiRouter like that .... okay and now we need to implement all the different functions that we find here as part of this interface so let's do that it's going to be func and here as a reference to this struct the same for the rest of the functions and here I need to create a variable here and I'm going to close this and first I need to import that library so first I need to install this Chi library so go get and the location of the library is github.com/go-chi/chi okay and i'm going to grab this URL and i'm going to import that library here okay and here i'm going to create a variable that's going to be chiDispatcher and it's going to be equal to chi.NewRouter this is similar to Mux and here what I'm going to do is I'm going to use that dispatcher and I'm going to use the get method that's the way that Chi handles the get HTTP requests and I'm going to add the URI and the function here and something similar is gonna be for the post function instead of get this is gonna be post and I need to pass the URI and the function and here I'm going to grab this from here and then I'm going to adapt it sorry here ... for the Chi router and ....and I'm going to change the message here it's gonna be Chi HTTP server running on port and whatever port we are going to pass to this router okay I think we're good and now let's go back to the server and instead of using Mux let's use Chi okay now I'm going to run this now we can see that the Chi HTTP server is running so let's try now just to check that everything is still working as we expect so if we try to get the posts okay yes we get the post and let's create a new one just in case to check that everything still works as we expect okay I'm able to create the new document and yeah and we still can get the response that we expect from this Chi server so now we are independent of the HTTP framework so we can either use Chi or we can use Mux so we created this router interface and we created two implementations one for Mux and another one for Chi great okay and if we go here to the controller we're gonna see that we are creating this service here so something that will allow us to have a better the testability is going to be passing this service in this case we are going to use the controller....a more efficient way to do this is going to be using some kind of dependency injection framework or mechanism now we are going to do this by passing this service in here in this constructor so it's gonna be service and the service is going to implement this PostService interface... ... and I'm going to assign this variable to that argument and here I'm going to remove this okay and now what I need to do is if I go back to the server I need to import the service package and I need to pass the service here so I'm going to create the service postService .... .....service.NewPostService this is the constructor and I'm going to pass this post service here to the controller so now we assign this service using this function or this constructor function okay let's go back to the server and the same happens here in the service if we go here we're gonna see that we are creating the repository here we're creating a new reference to the firestore repository and it's going to be better for a better testability of this service if we pass the repository here in the constructor so let's do that it's going to be repo and this repo is going to implement this interface and I'm going to remove this and I'm going to assign the repo to this variable and now I'm going to import the repository package... and I'm going to create the post repository here it's going to be a repository.PostRepository.... that is the interface the generic interface and then we have the concrete implementation that is repository.NewFirestoreRepository okay and now we need to pass this repository to the service great if we go here now we receive the repo in this function or this constructor and we assign that value here okay so now we have the repository in this case is the Firestore repository but if we want to integrate our application with MySQL MongoDB Postgres Oracle SQL Server or whatever we can create a new class here we implement this post repository interface and we are good to go and here we have this service and we are passing that repository so this service is also independent from the repository so we have a better separation of concerns on that as well and also we have the controller and the controller we are passing the post service we can create a different implementation for example of this post service okay let's try this I'm going to close this and I'm going to start the server again okay and let's run this and yes and we still get the data and let's create a new post let's say "Title 3" and "Text 3" yeah we get a successful response and if we go again we get all the posts we get title 2, title 3 and title 1 ...great.... if we go to the (firestore) database yes we have the title 1 the title 2 and title 3 okay now we have our application that is independent of frameworks that is independent from databases that is independent from the UI because it's a restful api so we don't care if we get requests from a mobile application or from a web browser for example and we have a testable application because we have different implementations from an interface so we can mock that interface and we can test using that mock implementation or we can also test our repo using an "in-memory" database and the same for the controller and we can mock the service and we can unit test our controller and the same for the service right we can mock the repository and we can unit test our service we can test our business logic for example the service is the place where we have our use case or our business logic so the idea here is in order to test this business logic without depending on any external libraries or any external dependencies we can mock this repository and we can make that mock implement this post repository interface and we can isolate this service and unit test it okay that's all I have for today hopefully Uncle Bob is proud :-)... thank you for watching, remember to share like and subscribe to the channel (really appreciated) and I see you in the next video, take care, bye!
Info
Channel: Pragmatic Reviews
Views: 31,528
Rating: undefined out of 5
Keywords: golang clean architecture, golang crash course, golang tutorial, go tutorial, golang firebase chi mux tutorial, learn golang, golang restful api, golang mysql, golang mongodb
Id: Yg_ae0UvCv4
Channel Id: undefined
Length: 49min 11sec (2951 seconds)
Published: Sat Jan 11 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.