Project directory design is a further implementation of code layering design. It is recommended that you read carefully first: Code Layering
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):
/
├── api
├── hack
├── internal
│ ├── cmd
│ ├── consts
│ ├── controller
│ ├── dao
│ ├── logic
│ ├── model
│ | ├── do
│ │ └── entity
│ └── service
├── manifest
├── resource
├── utility
├── go.mod
└── main.go
🔥 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 Name | Explanation | Description |
---|---|---|
api | External Interface | The input/output data structure definition for providing external services. Considering version management needs, it often exists as api/xxx/v1... . |
hack | Tool Script | Contains project development tools, scripts, etc. For example, configuration for CLI tools, and various shell/bat script files. |
internal | Internal Logic | The directory for storing business logic. Hidden visibility to the outside through Golang internal feature. |
- cmd | Entry Command | Directory for command-line management. Can manage multiple command lines. |
- consts | Constant Definitions | Defines all constants for the project. |
- controller | Interface Handling | Entrance/interface layer for receiving and parsing user input parameters. |
- dao | Data Access | Data Access Object, an abstract object for interacting with the underlying database containing only the basic CRUD methods. |
- logic | Business Encapsulation | Management of business logic encapsulation, specific business logic implementation, and encapsulation, often the most complex part of the project. |
- model | Structure Model | Data structure management module, managing data entity objects and input/output data structure definitions. |
- do | Domain Object | Used for converting business models and instance models in dao data operations, maintained by tools, and cannot be modified by users. |
- entity | Data Model | Data model is a one-to-one relationship between the model and data collection, maintained by tools, and cannot be modified by users. |
- service | Business Interface | Interface definition layer for decoupling business modules. Specific interface implementations are injected in logic . |
manifest | Delivery Manifest | Contains files for program compilation, deployment, operation, and configuration. Common contents are: |
- config | Configuration Management | Directory for storing configuration files. |
- docker | Image Files | Files related to Docker images, script files, etc. |
- deploy | Deployment Files | Files related to deployment. By default, provides a Yaml template for Kubernetes cluster deployment, managed through kustomize . |
- protobuf | Protocol Files | protobuf protocol definition files used during GRPC protocol, compiled protocol files are generated in api directory. |
resource | Static Resources | Static resource files. These files can be injected into release files in the form of resource packing/image compilation. |
go.mod | Dependency Management | Dependency description file using Go Module package management. |
main.go | Entry File | Program 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.
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.
Mapping relationship between three-layer architecture design and framework code layering
2. Request Layer Flow
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:
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.