gitea

Development moved to Codeberg


date: "2021-11-01T23:41:00+08:00" title: "Guidelines for Backend Development" slug: "guidelines-backend" weight: 20 toc: false draft: false menu: sidebar: parent: "developers" name: "Guidelines for Backend" weight: 20

identifier: "guidelines-backend"

Guidelines for Backend Development

Table of Contents

{{< toc >}}

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:

Package Dependencies

Since Golang don'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.

NOTICE

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,

// servcies/repository/repo.go
func CreateXXXX() error {\
  ctx, committer, err := db.TxContext()
	if err != nil {
		return err
	}
	defer committer.Close()

  // do something, if return err, it will rollback automatically when `committer.Close()` is invoked.
  if err := issues.UpdateIssue(ctx, repoID); err != nil {
      // ...
  }

  // ......

  return committer.Commit()
}

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. servcies/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"

Important Gotchas

Future Tasks

Currently, we are creating some refactors to do the following things: