本篇文章给大家谈谈如何开发电影网站源码分享,以及电影网站源码排名对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。
软件架构的目标是最大限度地减少构建和维护所需系统所需的人力资源。
我们将继续前进,看看如何通过三层架构更轻松地测试我们的电影API。
分层架构的想法建立在对接口进行编程的想法之上。当一个模块通过接口与另一个模块交互时,您可以将一个服务提供者替换为另一个。
不用担心!我会尽力简单地解释。
让我们看看下面这张图。看起来很简单,对吧?
这种架构背后的逻辑每一层都有自己的责任,它们将相互独立。这样,我们可以以更独立的方式测试它们。是的,再次测试!
这些层(处理程序、服务和存储库)的作用是什么?
Handler:它是一个获取http请求并将http响应返回给客户端的层。
服务:这是我们的业务逻辑所在的层。
存储库:它是从外部(DB)或内部(内存)数据源提供所有必要数据的层。为简单起见,我们使用内存。
在学习图中接口的作用之前,我们需要先谈谈依赖注入。依赖注入仅基于抽象(接口)而不是具体类型(结构)。以这种方式,我们通过使用接口注入所有依赖项。
比如我们在项目中会有两个服务:MockService和DefaultService。借助服务接口,我们可以在Handler层使用这两个不同结构体的方法。在测试阶段,Handler与MockService交互,另一方面,它与生产中的DefaultService交互。
不用担心,当我们看到动作时,您会更好地理解:
typemovieHandlerstruct{\n\tserviceservice.IMovieService\n}\n\nfuncNewMovieHandler(msservice.IMovieService)*movieHandler{\n\treturn&movieHandler{service:ms}\n}\n\n//curl-XPATCH&34;-d&34;title&34;Beautifulfilm&39;\nfunc(mh*movieHandler)UpdateMovie(whttp.ResponseWriter,r*http.Request,pshttprouter.Params){\n//somepieceofcode\n\terr=mh.service.UpdateMovie(id,movie)\n//somepieceofcode\n}
typeDefaultMovieServicestruct{\n\tmovieReporepository.IMovieRepository\n}\n\nfuncNewDefaultMovieService(mReporepository.IMovieRepository)*DefaultMovieService{\n\treturn&DefaultMovieService{\n\t\tmovieRepo:mRepo,\n\t}\n}\n\nfunc(d*DefaultMovieService)UpdateMovie(idint,moviemodel.Movie)error{\n\t//somepieceofcode\n\terr:=d.movieRepo.UpdateMovie(id,movie)\n//somepieceofcode\n}
typeinmemoryMovieRepositorystruct{\n\tMovies[]model.Movie\n}\n\nfuncNewInMemoryMovieRepository()*inmemoryMovieRepository{\n\tvarmovies=[]model.Movie{\n\t\t{ID:1,Title:&34;,ReleaseYear:1994,Score:9.3},\n\t\t{ID:2,Title:&34;,ReleaseYear:1972,Score:9.2},\n\t\t{ID:3,Title:&34;,ReleaseYear:2008,Score:9.0},\n\t}\n\treturn&inmemoryMovieRepository{\n\t\tMovies:movies,\n\t}\n}\nfunc(i*inmemoryMovieRepository)UpdateMovie(idint,moviemodel.Movie)error{\n\t//somepieceofcode\n}
实际上,当我们调用Handler中的service方法和Service中的repository方法时,将这种关系称为“调用堆栈”是合理的。
这是我们使用调用堆栈所做的简单图。我们的计算机为我们的函数调用分配内存。
让我们把这段记忆想象成一个盒子。
我们的第一个调用(在Handler中)需要保存在内存中。
然后,我们的第二个调用(在服务中)被保存在处理程序框的内存中。
然后,我们的第三个调用(在Repository中)被保存在Service框的内存中。棘手的是我们的服务需要等待存储库功能完成。完成后,我们的处理程序需要等待服务功能完成。完成后,Handler函数就可以完成它的工作了。
完成后,我们的堆栈将是空的。这就是调用堆栈的工作方式。您可以阅读“Grokking算法书的调用堆栈”部分以了解更多信息。
这是我们如何创建设计的广泛视角。我们可以深入到我们的项目中。我们将与本文一起处理一个PATCH请求示例。
1.main.go
当客户端使用PATCH方法发送请求时,movieHandler的UpdateMovie方法使用httprouter调用。
packagemain\n\nimport(\n\t&34;\n\t&34;\n\t&34;\n\t&34;\n\t&34;\n\t&34;\n)\n\nfuncmain(){\n\tmovieInMemoryRepository:=repository.NewInMemoryMovieRepository()\n\tmovieService:=service.NewDefaultMovieService(movieInMemoryRepository)\n\tmovieHandler:=handler.NewMovieHandler(movieService)\n\n\trouter:=httprouter.New()\n\n\trouter.PATCH(&34;,movieHandler.UpdateMovie)\n\n\tlog.Println(&34;)\n\terr:=http.ListenAndServe(&34;,router)\n\tlog.Fatal(err)\n}
2a.处理程序
这是我们的API获取HTTP请求的第一层。
packagehandler\n\nimport(\n\t&34;\n\t&34;\n\t&34;\n\t&34;\n\t&34;\n\t&34;\n\t&34;\n)\n\ntypemovieHandlerstruct{\n\tserviceservice.IMovieService\n}\n\nfuncNewMovieHandler(msservice.IMovieService)*movieHandler{\n\treturn&movieHandler{service:ms}\n}\n\n//curl-XPATCH&34;-d&34;title&34;Beautifulfilm&39;\nfunc(mh*movieHandler)UpdateMovie(whttp.ResponseWriter,r*http.Request,pshttprouter.Params){\n\tid,_:=strconv.Atoi(ps.ByName(&34;))\n\n\tvarmoviemodel.Movie\n\terr:=json.NewDecoder(r.Body).Decode(&movie)\n\tiferr!=nil{\n\t\thttp.Error(w,&34;,http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\terr=mh.service.UpdateMovie(id,movie)\n\n\tiferr!=nil{\n\t\tiferrors.Is(err,service.ErrIDIsNotValid)||\n\t\t\terrors.Is(err,service.ErrTitleIsNotEmpty){\n\t\t\thttp.Error(w,err.Error(),http.StatusBadRequest)\n\t\t\treturn\n\t\t}elseiferrors.Is(err,service.ErrMovieNotFound){\n\t\t\thttp.Error(w,err.Error(),http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\t\thttp.Error(w,err.Error(),http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.WriteHeader(http.StatusNoContent)\n\tw.Write([]byte(&34;))\n}
处理程序是将来自服务的响应转换为http响应的层。如您所知,HTTP响应由状态码、标头和正文组成。
2b。处理程序测试
在继续测试处理程序之前,我想介绍mockgen包。这个包帮助我们的代码轻松测试。
给它你想要模拟的接口源路径,显示自动生成的模拟文件将在哪里创建,并让它代表你生成模拟结构实现。如果我们想测试处理层,我们需要模拟服务层。我们可以像这样安装它:
mockgen-sourceservice/movie_service_interface.go-destinationservice/mock_movie_service.go-packageservice
在测试文件中,我们应该测试可能的错误和成功案例,以增加处理程序的测试覆盖率。
funcTestMovieHandler_UpdateMovie(t*testing.T){\n\tmovieID:=&34;\n\trequestURL:=fmt.Sprintf(&34;,movieID)\n\tupdatedMovie:=model.Movie{Title:&34;}\n\tjsonStr,_:=json.Marshal(updatedMovie)\n\tps:=httprouter.Params{\n\t\t{Key:&34;,Value:movieID},\n\t}\n\n\tt.Run(&34;,func(t*testing.T){\n\t\ttypetestCasestruct{\n\t\t\treturnedServiceErrerror\n\t\t\texpectedHTTPStatusCodeint\n\t\t}\n\n\t\ttestErrors:=[]testCase{\n\t\t\t{returnedServiceErr:service.ErrIDIsNotValid,expectedHTTPStatusCode:http.StatusBadRequest},\n\t\t\t{returnedServiceErr:service.ErrTitleIsNotEmpty,expectedHTTPStatusCode:http.StatusBadRequest},\n\t\t}\n\n\t\tfor_,testError:=rangetestErrors{\n\t\t\treq,_:=http.NewRequest(http.MethodPatch,requestURL,bytes.NewBuffer(jsonStr))\n\t\t\trec:=httptest.NewRecorder()\n\n\t\t\tmockService:=service.NewMockIMovieService(gomock.NewController(t))\n\t\t\tmockService.\n\t\t\t\tEXPECT().\n\t\t\t\tUpdateMovie(1,updatedMovie).\n\t\t\t\tReturn(testError.returnedServiceErr).\n\t\t\t\tTimes(1)\n\n\t\t\tmh:=NewMovieHandler(mockService)\n\t\t\tmh.UpdateMovie(rec,req,ps)\n\n\t\t\tassert.Equal(t,testError.expectedHTTPStatusCode,rec.Code)\n\t\t}\n\t})\n\n\tt.Run(&34;,func(t*testing.T){\n\t\treq,_:=http.NewRequest(http.MethodPatch,requestURL,bytes.NewBuffer(jsonStr))\n\t\trec:=httptest.NewRecorder()\n\n\t\tmockService:=service.NewMockIMovieService(gomock.NewController(t))\n\t\tmockService.\n\t\t\tEXPECT().\n\t\t\tUpdateMovie(1,updatedMovie).\n\t\t\tReturn(service.ErrMovieNotFound).\n\t\t\tTimes(1)\n\n\t\tmh:=NewMovieHandler(mockService)\n\t\tmh.UpdateMovie(rec,req,ps)\n\n\t\tassert.Equal(t,http.StatusNotFound,rec.Code)\n\t})\n\n\tt.Run(&34;,func(t*testing.T){\n\t\treq,_:=http.NewRequest(http.MethodPatch,requestURL,bytes.NewBuffer(jsonStr))\n\t\trec:=httptest.NewRecorder()\n\n\t\tmockService:=service.NewMockIMovieService(gomock.NewController(t))\n\t\tmockService.\n\t\t\tEXPECT().\n\t\t\tUpdateMovie(1,updatedMovie).\n\t\t\tReturn(errors.New(&34;)).\n\t\t\tTimes(1)\n\n\t\tmh:=NewMovieHandler(mockService)\n\t\tmh.UpdateMovie(rec,req,ps)\n\n\t\tassert.Equal(t,http.StatusInternalServerError,rec.Code)\n\t})\n\n\tt.Run(&34;,func(t*testing.T){\n\t\treq,_:=http.NewRequest(http.MethodPatch,requestURL,bytes.NewBuffer(jsonStr))\n\t\trec:=httptest.NewRecorder()\n\n\t\tmockService:=service.NewMockIMovieService(gomock.NewController(t))\n\t\tmockService.\n\t\t\tEXPECT().\n\t\t\tUpdateMovie(1,updatedMovie).\n\t\t\tReturn(nil).\n\t\t\tTimes(1)\n\n\t\tmh:=NewMovieHandler(mockService)\n\t\tmh.UpdateMovie(rec,req,ps)\n\n\t\tassert.Equal(t,http.StatusNoContent,rec.Code)\n\t\tassert.Equal(t,&34;,rec.Body.String())\n\t})\n}
3a。服务
这一层包括我们的应用业务逻辑。通过将业务逻辑分离到特定的层,我们可以轻松编写单元测试。
packageservice\n\nimport(\n\t&34;\n\t&34;\n\t&34;\n)\n\nvar(\n\tErrIDIsNotValid=errors.New(&34;)\n\tErrTitleIsNotEmpty=errors.New(&34;)\n\tErrMovieNotFound=errors.New(&34;)\n)\n\ntypeDefaultMovieServicestruct{\n\tmovieReporepository.IMovieRepository\n}\n\nfuncNewDefaultMovieService(mReporepository.IMovieRepository)*DefaultMovieService{\n\treturn&DefaultMovieService{\n\t\tmovieRepo:mRepo,\n\t}\n}\n\nfunc(d*DefaultMovieService)UpdateMovie(idint,moviemodel.Movie)error{\n\tifid<=0{\n\t\treturnErrIDIsNotValid\n\t}\n\n\tifmovie.Title==&34;{\n\t\treturnErrTitleIsNotEmpty\n\t}\n\n\terr:=d.movieRepo.UpdateMovie(id,movie)\n\tiferrors.Is(err,repository.ErrMovieNotFound){\n\t\treturnErrMovieNotFound\n\t}\n\n\treturnnil\n}
3b。服务测试
就像在处理程序测试部分一样,我们需要模拟我们的存储库来测试我们的服务层:
mockgen-sourcerepository/movie_repository_interface.go-destinationrepository/mock_movie_repository.go-packagerepository
我会给你一个窍门。每当我们更改提供给mockgen的接口时,我们都必须再次运行mockgen命令。
到目前为止,我们有两个模拟文件,当我们向接口添加新方法时,我们需要一遍又一遍地编写这些长代码。这么乏味的方式!解决方案是Makefile。使用Makefile,我们只使用generate-mocks命令来重新创建我们的模拟文件。(makegenerate-mocks)
我们正在通过模拟存储库来测试可能的错误。
funcTestDefaultMovieService_UpdateMovie(t*testing.T){\n\tt.Run(&34;,func(t*testing.T){\n\t\tms:=NewDefaultMovieService(nil)\n\t\terr:=ms.UpdateMovie(0,model.Movie{Title:&34;})\n\t\tassert.ErrorIs(t,err,ErrIDIsNotValid)\n\t})\n\tt.Run(&34;,func(t*testing.T){\n\t\tms:=NewDefaultMovieService(nil)\n\t\terr:=ms.UpdateMovie(3,model.Movie{Title:&34;})\n\t\tassert.ErrorIs(t,err,ErrTitleIsNotEmpty)\n\t})\n\tt.Run(&34;,func(t*testing.T){\n\t\tmovie:=model.Movie{Title:&34;}\n\t\tmockRepository:=repository.NewMockIMovieRepository(gomock.NewController(t))\n\t\tmockRepository.\n\t\t\tEXPECT().\n\t\t\tUpdateMovie(6,movie).\n\t\t\tReturn(repository.ErrMovieNotFound).\n\t\t\tTimes(1)\n\n\t\tms:=NewDefaultMovieService(mockRepository)\n\t\terr:=ms.UpdateMovie(6,movie)\n\n\t\tassert.ErrorIs(t,err,ErrMovieNotFound)\n\t})\n\n\tt.Run(&34;,func(t*testing.T){\n\t\tmovie:=model.Movie{Title:&34;}\n\t\tmockRepository:=repository.NewMockIMovieRepository(gomock.NewController(t))\n\t\tmockRepository.\n\t\t\tEXPECT().\n\t\t\tUpdateMovie(2,movie).\n\t\t\tReturn(nil).\n\t\t\tTimes(1)\n\n\t\tms:=NewDefaultMovieService(mockRepository)\n\t\terr:=ms.UpdateMovie(2,movie)\n\n\t\tassert.Nil(t,err)\n\t})\n}
4.存储库
这是我们实现数据集成的地方:
packagerepository\n\nimport(\n\t&34;\n\t&34;\n)\n\nvar(\n\tErrMovieNotFound=errors.New(&34;)\n)\n\ntypeinmemoryMovieRepositorystruct{\n\tMovies[]model.Movie\n}\n\nfuncNewInMemoryMovieRepository()*inmemoryMovieRepository{\n\tvarmovies=[]model.Movie{\n\t\t{ID:1,Title:&34;,ReleaseYear:1994,Score:9.3},\n\t\t{ID:2,Title:&34;,ReleaseYear:1972,Score:9.2},\n\t\t{ID:3,Title:&34;,ReleaseYear:2008,Score:9.0},\n\t}\n\n\treturn&inmemoryMovieRepository{\n\t\tMovies:movies,\n\t}\n}\n\nfunc(i*inmemoryMovieRepository)UpdateMovie(idint,moviemodel.Movie)error{\n\tfork:=0;k<len(i.Movies);k++{\n\t\tifi.Movies[k].ID==id{\n\t\t\ti.Movies[k].Title=movie.Title\n\t\t\treturnnil\n\t\t}\n\t}\n\n\treturnErrMovieNotFound\n}
奖励:HTTP客户端插件
我们正在尝试开发RESTfulAPI,并且在开发过程中,我们希望确保它按预期工作。可以肯定的是,我们需要向我们的API发送HTTP请求。
你可以使用curl、Postman、Insomnia和JetbrainsHTTPClient插件等两种方式来做到这一点。我真的很喜欢使用JetbrainsHTTPClient插件。
使用HTTPClient插件,您可以直接在IntelliJIDEA代码编辑器中创建、编辑和执行HTTP请求。
关注七爪网,获取更多APP/小程序/网站源码资源!
关于本次如何开发电影网站源码分享和电影网站源码排名的问题分享到这里就结束了,如果解决了您的问题,我们非常高兴。
