Для тих хто переходить на Go з інших мов програмування, одним з перших запитань з яким вони зустрічаються це структура проекту, що куди і як складати. Як правило більшість сучасних застосунків, які розроблять програмісти пишуться не на чисті мові а з використанням певної кодової бази у вигляді каркасів (фреймворків). Розробники таких каркасів самі вже задали певну структуру яку більшість розробників стараються дотримуватись. Тому більшість проектів дуже похожі. Коли починають розробляти проект на якомусь фреємворку як правило таких запитань не виникає, що і де має лежати. Звісно ніхто не заставляє когось дотримуватись певної структури закладеної розробниками фреємворків, кожний може писати як хоче, і навіть деякі фреємворки мають різну структуру залежності від типу проекту. На го також є різні фреємворки але нині ми поговоримо не про них.
Існує такий репозиторій project-layout де на різних мовах розписано що і де може знаходитись. Хоч в назві є golang-standards але це не є офіційним стандартом. Тут принципі детально розписані корньові директорї проекту, це не означає що ці всі директорії повинен містити ваш проек, це лише пропозиція як можна організувати свій проект.
Ми нині поговорим про структуру REST-API додатку, звісно у цьому проекті ми не будем використовувати усі варіанти назв каталогів які є в зразку. Пропоную зробити веб застосунок для CRUD операцій який буде керувати двома сутностями User і Product. Нам потрібно дві сутності щоб можна було більш наглядно продемонструвати різні варіанти побудови проекту.
На кожну сутність ми зробимо по 5 методів
- CreateUser
- GetUsers
- GetUser
- UpdateUser
- DeleteUser
- CreateProduct
- GetProducts
- GetProduct
- UpdateProduct
- DeleteProduct
Для початку пропоную створити swagger файл в якому ми опишемо методи нашого api.
openapi: 3.0.1
info:
title: REST API
description: Standart REST API
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.0
tags:
- name: User
description: Operations about user
- name: Product
description: Operations about product
paths:
/users:
get:
tags:
- User
summary: All users
responses:
200:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
400:
description: Bad request, empty username or id
content: {}
500:
description: Internal Server Error
content: {}
x-codegen-request-body-name: body
/user:
post:
tags:
- "User"
summary: "Create a user in the store with data"
description: ""
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
"201":
description: "Created"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
"400":
description: "Bad Request"
/user/{id}:
get:
tags:
- "User"
summary: "Find user by ID"
description: "Returns a single user"
parameters:
- in: path
name: "id"
description: "Id of user"
required: true
schema:
type: "integer"
format: "int64"
responses:
"200":
description: "Successful operation"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
"400":
description: "Invalid ID supplied"
"404":
description: "User not found"
put:
tags:
- "User"
summary: "Updates a user in the store with data"
description: ""
parameters:
- in: path
name: "id"
description: "Id of user that needs to be updated"
required: true
schema:
type: "integer"
format: "int64"
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
"202":
description: "Accepted"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
"400":
description: "Invalid ID supplied"
"404":
description: "User not found"
delete:
tags:
- "User"
summary: "Deletes the user"
description: ""
parameters:
- in: "path"
name: "id"
description: "User id to delete"
required: true
schema:
type: "integer"
format: "int64"
responses:
"400":
description: "Invalid Id supplied"
"204":
description: "No Content"
/products:
get:
tags:
- Product
summary: All products
responses:
200:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Product'
400:
description: Bad request
content: {}
500:
description: Internal Server Error
content: {}
x-codegen-request-body-name: body
/product:
post:
tags:
- "Product"
summary: "Create a product in the store with data"
description: ""
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateProductRequest'
responses:
"201":
description: "Created"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Product'
"400":
description: "Bad Request"
/product/{id}:
get:
tags:
- "Product"
summary: "Find product by ID"
description: "Returns a single product"
parameters:
- in: path
name: "id"
description: "Id of product"
required: true
schema:
type: "integer"
format: "int64"
responses:
"200":
description: "Successful operation"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Product'
"400":
description: "Invalid ID supplied"
"404":
description: "Product not found"
put:
tags:
- "Product"
summary: "Updates a product in the store with data"
description: ""
parameters:
- in: path
name: "id"
description: "Id of product that needs to be updated"
required: true
schema:
type: "integer"
format: "int64"
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateProductRequest'
responses:
"202":
description: "Accepted"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Product'
"400":
description: "Invalid ID supplied"
"404":
description: "Product not found"
delete:
tags:
- "Product"
summary: "Deletes the product"
description: ""
parameters:
- in: "path"
name: "id"
description: "Product id to delete"
required: true
schema:
type: "integer"
format: "int64"
responses:
"400":
description: "Invalid Id supplied"
"204":
description: "No Content"
components:
schemas:
CreateUserRequest:
type: object
properties:
name:
description: "Name user"
type: "string"
email:
description: "email user"
type: "string"
User:
type: object
properties:
id:
type: "integer"
format: "int64"
name:
type: string
email:
type: "string"
CreateProductRequest:
type: object
properties:
name:
description: "Name product"
type: "string"
description:
description: "Product description"
type: "string"
image:
description: "Product image"
type: "string"
price:
description: "Product price"
type: "integer"
count:
description: "Product count"
type: "integer"
Product:
type: object
properties:
id:
type: "integer"
format: "int64"
name:
type: string
description:
type: "string"
price:
type: "integer"
format: "int64"
count:
type: "integer"
format: "int64"
Ми можем його добавити до нашого проекту, і як бачимо з рекомендацій project-layout такі файли краще за все розмістити в каталозі api/
Тепрер створим головний файл з якого почнеться робота усього нашого застосунку, ми його зозмістим в каталозі /cmd/project-layout/project-layout.go
На внутрішній структурі файлів ми не будем зупинятись детально, так як це не є метою даної статті.
B каталозі /pkg ми створимо коннект до бази даних який буде приймати параметри підключення доступи до бази даних.
В каталозі /configs ми створим пакет зк конфігами, це може бути як мапа так і просто константи.
Тепер ми займмось обробкою наших запитів, усі необхідні для цього файли і каталоги будуть знаходитись в каталозі /internal. Саме структурою цього каталогу і будуть відрізнятись наші дві версії одного проекту.
Створимо каталог /handlers тут будуть лежати усі наші обробники запитів, також додамо /models де будуть знаходитись наші моделі, в каталозі /repositories будуть наші репозиторії а в каталозі /services наші сервіси.
├───internal
│ ├───handlers
│ │ productHandler.go
│ │ userHandler.go
│ │
│ ├───models
│ │ productModel.go
│ │ userModel.go
│ │
│ ├───repositories
│ │ productRepository.go
│ │ userRepository.go
│ │
│ └───services
│ productService.go
│ userService.go
В інші версії нашого проекту ми зробим на кожну сутність окремий каталог /user і /product, а в середині цих каталогів будуть лежати модель, сервіс і репозиторій, також буде каталог /http якому буде наш обробник запитів. Якщо потрібно більше моделей, репозиторіїв чи сервісів, то їх можна по групувати по відповідних каталогах.
├───internal
│ ├───product
│ │ │ productModel.go
│ │ │ productRepository.go
│ │ │ productService.go
│ │ │
│ │ └───http
│ │ productHandler.go
│ │
│ └───user
│ │ userModel.go
│ │ userRepository.go
│ │ userService.go
│ │
│ └───http
│ userHandler.go