Signed-off-by: appleboy <appleboy.tw@gmail.com> Reviewed-on: https://gitea.com/gitea/docs/pulls/195 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: appleboy <appleboy.tw@gmail.com> Co-committed-by: appleboy <appleboy.tw@gmail.com>
8.1 KiB
date, slug, sidebar_position, aliases
| date | slug | sidebar_position | aliases | |
|---|---|---|---|---|
| 2021-11-01T23:41:00+08:00 | guidelines-backend | 20 |
|
後端開發指南
背景
Gitea 使用 Golang 作為後端編程語言。它使用了許多第三方包,也自己編寫了一些包。 例如,Gitea 使用 Chi 作為基本的 Web 框架。Xorm 是一個 ORM 框架,用於與數據庫交互。 因此,管理這些包非常重要。在開始編寫後端代碼之前,請遵循以下指南。
包設計指南
包列表
為了保持代碼的可理解性並避免循環依賴,擁有良好的代碼結構非常重要。Gitea 後端分為以下幾個部分:
build: 幫助構建 Gitea 的腳本。cmd: 所有 Gitea 的實際子命令,包括 web、doctor、serv、hooks、admin 等等。web將啟動 Web 服務。serv和hooks將由 Git 或 OpenSSH 調用。其他子命令可以幫助維護 Gitea。tests: 常見的測試工具函數tests/integration: 集成測試,用於測試後端回歸tests/e2e: 端到端測試,用於測試前端和後端的兼容性和視覺回歸。
models: 包含由 xorm 用於構建數據庫表的數據結構。它還包含查詢和更新數據庫的函數。應避免依賴其他 Gitea 代碼。可以在某些情況下例外,例如日誌記錄。models/db: 基本的數據庫操作。所有其他models/xxx包應依賴於此包。GetEngine函數應僅從models/調用。models/fixtures: 單元測試和集成測試中使用的示
date: "2021-11-01T23:41:00+08:00" slug: "guidelines-backend" sidebar_position: 20 aliases:
- /zh-tw/guidelines-backend
Guidelines for Backend Development
Background
Gitea uses Golang as the backend programming language. It uses many third-party packages and also write some itself. For example, Gitea uses Chi as basic web framework. Xorm is an ORM framework that is used to interact with the database. So it's very important to manage these packages. Please take the below guidelines before you start to write backend code.
Package Design Guideline
Packages List
To maintain understandable code and avoid circular dependencies it is important to have a good code structure. The Gitea backend is divided into the following parts:
build: Scripts to help build Gitea.cmd: All Gitea actual sub commands includes web, doctor, serv, hooks, admin and etc.webwill start the web service.servandhookswill be invoked by Git or OpenSSH. Other sub commands could help to maintain Gitea.tests: Common test utility functionstests/integration: Integration tests, to test back-end regressionstests/e2e: E2e tests, to test front-end and back-end compatibility and visual regressions.
models: Contains the data structures used by xorm to construct database tables. It also contains functions to query and update the database. Dependencies to other Gitea code should be avoided. You can make exceptions in cases such as logging.models/db: Basic database operations. All othermodels/xxxpackages should depend on this package. TheGetEnginefunction should only be invoked frommodels/.models/fixtures: Sample data used in unit tests and integration tests. Oneymlfile means one table which will be loaded into database when beginning the tests.models/migrations: Stores database migrations between versions. PRs that change a database structure MUST also have a migration step.
modules: Different modules to handle specific functionality in Gitea. Work in Progress: Some of them should be moved toservices, in particular those that depend on models because they rely on the database.modules/setting: Store all system configurations read from ini files and has been referenced by everywhere. But they should be used as function parameters when possible.modules/git: Package to interactive withGitcommand line or Gogit package.
public: Compiled frontend files (javascript, images, css, etc.)routers: Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) must not depend on routers.routers/apiContains routers for/api/v1aims to handle RESTful API requests.routers/installCould only respond when system is in INSTALL mode (INSTALL_LOCK=false).routers/privatewill only be invoked by internal sub commands, especiallyservandhooks.routers/webwill handle HTTP requests from web browsers or Git SMART HTTP protocols.
services: Support functions for common routing operations or command executions. Usesmodelsandmodulesto handle the requests.templates: Golang templates for generating the html output.
Package Dependencies
Since Golang doesn't support import cycles, we have to decide the package dependencies carefully. There are some levels between those packages. Below is the ideal package dependencies direction.
cmd -> routers -> services -> models -> modules
From left to right, left packages could depend on right packages, but right packages MUST not depend on left packages. The sub packages on the same level could depend on according this level's rules.
:::warning
Why do we need database transactions outside of models? And how?
Some actions should allow for rollback when database record insertion/update/deletion failed.
So services must be allowed to create a database transaction. Here is some example,
// services/repository/repository.go
func CreateXXXX() error {
return db.WithTx(func(ctx context.Context) error {
// do something, if err is returned, it will rollback automatically
if err := issues.UpdateIssue(ctx, repoID); err != nil {
// ...
return err
}
// ...
return nil
})
}
You should not use db.GetEngine(ctx) in services directly, but just write a function under models/.
If the function will be used in the transaction, just let context.Context as the function's first parameter.
// models/issues/issue.go
func UpdateIssue(ctx context.Context, repoID int64) error {
e := db.GetEngine(ctx)
// ...
}
:::
Package Name
For the top level package, use a plural as package name, i.e. services, models, for sub packages, use singular,
i.e. services/user, models/repository.
Import Alias
Since there are some packages which use the same package name, it is possible that you find packages like modules/user, models/user, and services/user. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use snake_case for import aliases.
i.e. import user_service "code.gitea.io/gitea/services/user"
Implementing io.Closer
If a type implements io.Closer, calling Close multiple times must not fail or panic but return an error or nil.
Important Gotchas
- Never write
x.Update(exemplar)without an explicitWHEREclause:- This will cause all rows in the table to be updated with the non-zero values of the exemplar - including IDs.
- You should usually write
x.ID(id).Update(exemplar).
- If during a migration you are inserting into a table using
x.Insert(exemplar)where the ID is preset:- You will need to
SET IDENTITY_INSERT `table` ONfor the MSSQL variant (the migration will fail otherwise) - However, you will also need to update the id sequence for postgres - the migration will silently pass here but later insertions will fail:
SELECT setval('table_name_id_seq', COALESCE((SELECT MAX(id)+1 FROM `table_name`), 1), false)
- You will need to
Future Tasks
Currently, we are creating some refactors to do the following things:
- Correct that codes which doesn't follow the rules.
- There are too many files in
models, so we are moving some of them into a sub packagemodels/xxx. - Some
modulessub packages should be moved toservicesbecause they depend onmodels.