Структура REST проекту на Go

Для тих хто переходить на 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

Усі матеріали можна переглянути на GitHub v1 і v2.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься.