Project directory design is a further implementation of code layering design. It is recommended that you read carefully first: Code Layering

Project Structure🔥 - 图1tip

This is a directory design for business projects with the GoFrame framework. The main idea originates from the three-layer architecture but has been improved and refined in practice to better fit engineering practices and modern advancements.

1. Project Directory Structure

The basic directory structure of GoFrame business projects is as follows (taking Single Repo as an example):

  1. /
  2. ├── api
  3. ├── hack
  4. ├── internal
  5. ├── cmd
  6. ├── consts
  7. ├── controller
  8. ├── dao
  9. ├── logic
  10. ├── model
  11. | ├── do
  12. └── entity
  13. └── service
  14. ├── manifest
  15. ├── resource
  16. ├── utility
  17. ├── go.mod
  18. └── main.go

Project Structure🔥 - 图2info

🔥 Important Tip 🔥: The framework’s project directory adopts a generalized design to meet the needs of projects with varying levels of complexity, but you can increase or decrease the default directories as needed in actual projects. For example, in scenarios lacking i18n/template/protobuf requirements, you can directly delete the corresponding directories. Similarly, for very simple business projects (such as validation/demonstration projects) that do not require strict use of dao/logic/model directories and features, you can directly delete the corresponding directories and implement business logic directly in the controller. Everything can be flexibly chosen and assembled by the developer!

Directory/File NameExplanationDescription
apiExternal InterfaceThe input/output data structure definition for providing external services. Considering version management needs, it often exists as api/xxx/v1….
hackTool ScriptContains project development tools, scripts, etc. For example, configuration for CLI tools, and various shell/bat script files.
internalInternal LogicThe directory for storing business logic. Hidden visibility to the outside through Golang internal feature.
  - cmdEntry CommandDirectory for command-line management. Can manage multiple command lines.
  - constsConstant DefinitionsDefines all constants for the project.
  - controllerInterface HandlingEntrance/interface layer for receiving and parsing user input parameters.
  - daoData AccessData Access Object, an abstract object for interacting with the underlying database containing only the basic CRUD methods.
  - logicBusiness EncapsulationManagement of business logic encapsulation, specific business logic implementation, and encapsulation, often the most complex part of the project.
  - modelStructure ModelData structure management module, managing data entity objects and input/output data structure definitions.
    - doDomain ObjectUsed for converting business models and instance models in dao data operations, maintained by tools, and cannot be modified by users.
    - entityData ModelData model is a one-to-one relationship between the model and data collection, maintained by tools, and cannot be modified by users.
  - serviceBusiness InterfaceInterface definition layer for decoupling business modules. Specific interface implementations are injected in logic.
manifestDelivery ManifestContains files for program compilation, deployment, operation, and configuration. Common contents are:
  - configConfiguration ManagementDirectory for storing configuration files.
  - dockerImage FilesFiles related to Docker images, script files, etc.
  - deployDeployment FilesFiles related to deployment. By default, provides a Yaml template for Kubernetes cluster deployment, managed through kustomize.
  - protobufProtocol Filesprotobuf protocol definition files used during GRPC protocol, compiled protocol files are generated in api directory.
resourceStatic ResourcesStatic resource files. These files can be injected into release files in the form of resource packing/image compilation.
go.modDependency ManagementDependency description file using Go Module package management.
main.goEntry FileProgram entry file.

External Interface

The external interface includes two parts: Interface Definition (api) + Interface Implementation (controller).

The responsibility of the service interface is similar to the UI representation layer in three-layer architecture design, responsible for receiving and responding to client inputs and outputs, including filtering, converting, and validating input parameters, maintaining the output data structure, and calling service for business logic processing.

Interface Definition - api

The api package is used for defining data structure inputs and outputs agreed with the client, often closely bound to specific business scenarios.

Interface Implementation - controller

The controller receives the input from the api, can directly implement business logic within controller, or call one or more service packages to implement business logic and encapsulate the execution results into an agreed api output data structure.

Business Implementation

Business implementation includes two parts: business interface (service) + business encapsulation (logic).

The responsibility of business implementation is similar to the BLL business logic layer in three-layer architecture design, responsible for implementing and encapsulating specific business logic.

Project Structure🔥 - 图3info

In the following chapters, we will uniformly refer to business implementation as service, and note that it actually includes two parts.

Business Interface - service

The service package is used to decouple business module calls. Business modules often do not directly call the corresponding business module resources to implement business logic but do so by calling service interfaces. The service layer contains only interface definitions, with specific interface implementations injected into the business modules.

Business Encapsulation - logic

The logic package is responsible for implementing and encapsulating specific business logic. Codes from various levels of the project do not directly call the business modules of the logic layer but do so through the service interface layer.

Structure Model

The model package serves a role similar to the Model definition layer in the three-layer architecture. It only contains the global, common data structure definitions for reference by all business modules in the project.

Data Model - entity

Defined data structures bound to the data collection, often corresponding one-to-one with data tables.

Business Model - model

Common data structure definitions related to business, including most method input and output definitions.

Data Access - dao

The role of the dao package is similar to the DAL data access layer in three-layer architecture, responsible for converging all data access.

Project Structure🔥 - 图4

Mapping relationship between three-layer architecture design and framework code layering

2. Request Layer Flow

Project Structure🔥 - 图5

cmd

The cmd layer is responsible for guiding the program startup, its significant tasks being initialization logic, registering route objects, starting the server listener, and blocking the program operation until the server exits.

api

The upper layer server service receives client requests, converts them to Req receiving objects defined in api, performs request parameter-to-Req object attribute type conversions, executes basic validation bound to Req objects, and hands over the Req request objects to the controller layer.

controller

The controller layer is responsible for receiving Req request objects, conducting some business logic validations, can directly implement business logic within controller, or call one or more services to implement business logic, and encapsulate results into the agreed Res data structure objects for return.

model

The model layer manages all common business models.

service

service is an interface layer used for business module decoupling. service contains no specific business logic implementation, relying on the logic layer for injection of specific business logic.

logic

The business logic of the logic layer needs to perform data operations by calling dao. When calling dao, do data structure objects need to be passed for delivering query conditions and input data. After dao execution, Entity data models return data results to the service layer.

dao

The dao layer interacts with the underlying real database through the ORM abstraction component of the framework.

3. FAQ

Does the framework support common MVC development model

Of course!

As a foundational development framework with modular design, GoFrame does not constrain code design patterns and provides a powerful template engine core component for rapid template rendering development commonly seen in MVC mode. Compared to the MVC development model, we recommend using the three-layer architecture design model in complex business scenarios.

How to maintain when api and model layers have duplicate data structures

Data structures defined in api are for external use, bound to specific business scenarios (such as specific page interaction logic, single interface function), and data structures are pre-set by upper-layer display layers; data structures defined in model are for internal use only, allowing for internal modifications without affecting external api interface compatibility.

Note that data structures in model should not be directly exposed for external use, and the framework’s project design deliberately places the model directory under the internal directory. Nor should alias type definitions of model data structures be provided in the api layer for external access. Once the model data structure is applied to the api layer, changes to internal model data structures will directly affect api interface compatibility.

If duplicate data structures (or even constants, enumerations) appear in both, it is recommended to define data structures at the api layer. Internal service logic can directly access data structures at the api layer. The model layer’s data structures can also directly reference those from the api layer, but not vice versa.

Let’s see an example for better understanding:

Project Structure🔥 - 图6Project Structure🔥 - 图7

How to clearly define and manage the layering responsibilities between service and controller

The controller layer handles Req/Res external interface requests. It is responsible for receiving, validating request parameters, can directly implement business logic within controller, or call one or more services to implement business logic and encapsulate execution results into the agreed api output data structures for return. The service layer processes Input/Output internal method calls. It is responsible for internal reusable business logic encapsulation, with methods often being more granular.

Typically, when developing an interface, only implementing the business logic in the controller layer is needed, and when there is repetitive code logic, it is abstractly settled into the service layer from various controller interfaces. If Req objects are directly passed from the controller layer to service, and service directly returns Res data structure objects, this approach is coupled with external interfaces and only serves external interface services, making it difficult to reuse, thus increasing technical debt costs.

How to clearly define and manage the layering responsibilities between service and dao

This is a classic question.

Pain Point:

Commonly, developers encapsulate business logic related to data within dao code layer while service code layer only makes simple dao calls. This approach can make the dao layer, originally meant to maintain data, more burdensome, while the business logic service layer code appears light. Developers are confused, questioning where to put their business logic code, in dao or service?

Often, business logic mostly involves CURD operations on data, causing nearly all business logic to gradually accumulate in the dao layer. Business logic changes frequently necessitate modifications to dao layer code. For instance: for data query requirements, initially it may seem like a simple logic to place code in dao, but as query requirements increase or change complexity, inevitably, existing dao code requires further maintenance and modifications, possibly leading to updates in service code as well. Originally limited to service layer business logic code responsibility becomes unclear and heavily coupled with dao layer code responsibilities, leading to increased development and maintenance costs later in the project.

Suggestion:

Our suggestion: dao layer code should strive to maintain general applicability, with most scenarios not requiring additional methods, instead assembling with some generalized chain operation methods. Business logic, including what appears to be simple data operation logic, should be encapsulated within service. service contains multiple business modules, with each module managing its dao object independently. Ideally, service communicates data through method calls between services rather than arbitrarily calling dao objects of other service modules.

Why use the internal directory to contain business code

The internal directory is a feature unique to Golang language that prevents references from directories outside the peer directory. The purpose of this directory in business projects is to avoid unlimited unrestricted access between multiple projects if there are multiple sub-projects (especially in large repository management mode), making it difficult to prevent coupling of packages across different projects.