В архитектуре кода иногда разделяют слой сущностей и слой моделей. В этой статье я расскажу о них и приведу два примера кода на языке Golang.
Чистая структура кода — это набор рекомендаций и принципов, направленных на то, чтобы сделать код более читаемым, удобным и масштабируемым. В контексте архитектуры программного обеспечения обычной практикой является разграничивать задачи путем разделения кодовой базы на отдельные слои. Два общих слоя в этом смысле — это слои сущностей и моделей. В теории это разные уровни, но иногда они могут быть объединены в один.
Концепции сущностей и моделей
Сущности не зависят от конкретного механизма хранения и предназначены для отображения бизнес-логики. Сущности часто носят уникальные имена и связаны с другими сущностями, а также могут использовать методы для выполнения операций, характерных для данного домена. Это простые объекты (POJOs в Java, POCOs в C#), которые отражают основные понятия бизнес-домена: пользователь, продукт, заказ и т.д.. Эти объекты в основном сосредоточены на бизнес-логике и не зависят от каких-либо внешних ресурсов или инфраструктуры.
Модели данных представляют собой структуры данных, используемые для взаимодействия между различными частями приложения, например, между пользовательским интерфейсом, API и системами хранения данных. Модели часто выступают в качестве объектов передачи данных (DTO), которые помогают в сериализации, десериализации и проверке данных. Они в основном сосредоточены на структуре данных и их отображении, а не на бизнес-логике. Как правило, они легко поддаются сериализации в различные форматы, такие как JSON или XML.
Ключевые характеристики и различия
Основные характеристики слоя сущностей:
Представляет основные бизнес-концепции и правила.
Независимость от внешних ресурсов, инфраструктуры или фреймворков.
Содержит состояние и поведение бизнес-объектов.
Основные характеристики слоя моделей
Обеспечивает структурированное представление данных для связи между различными частями приложения.
Может включать логику проверки данных, сериализации и десериализации.
Отделяет проблемы отображения и обработки данных от основной бизнес-логики.
Различия между слоем сущностей и слоем моделей:
Фокус: сущности сосредоточены на основной бизнес-логике, в то время как модели фокусируются на структуре и представлении данных.
Зависимости: сущности не зависят от внешних ресурсов или инфраструктуры, в то время как модели могут взаимодействовать с различными частями приложения, такими как пользовательский интерфейс, API или системы хранения данных.
Функции: сущности передают состояние и поведение бизнес-объектов, а модели выполняют проверку, сериализацию и десериализацию данных.
Связь: сущности представляют основные концепции бизнес-области и обычно не сильно привязаны к конкретным технологиям или фреймворкам. Модели могут быть связаны с UI-фреймворками или системами хранения данных.
Изменения: изменения на слое сущностей обычно происходят в результате модификации бизнес-логики или правил, в то время как изменения на слое моделей — из-за обновления структуры данных или способа передачи данных между различными частями приложения.
Уровень сущностей и уровень моделей — это отдельные аспекты чистой структуры кода, где сущности сосредоточены на основной бизнес-логике, а модели — на структуре, представлении и манипулировании данными в приложении. Разделяя эти аспекты, разработчики могут создавать более удобные в обслуживании, масштабируемые и читаемые базы кодов.
Объединять их или нет, зависит от конкретных требований
Слой сущностей и слой моделей имеют разное значение. И есть достаточно причин, чтобы использовать их раздельно. Но в некоторых сценариях их объединяют.
Возможны ситуации, когда различие между слоем сущностей и слоем моделей становится менее четким, и кажется, что эти слои сливаются. Это может произойти в небольших приложениях, где разделение этих слоев не дает существенных преимуществ. Однако необходимо понимать преимущества и последствия слияния этих уровней.
Что дает объединение уровней сущностей и моделей:
Упрощение: в небольших приложениях или приложениях с ограниченной областью применения разделение слоев может добавить ненужную сложность.
Быстрое прототипирование: на ранних стадиях разработки приложения или быстрого прототипирования может быть полезно объединить слои для ускорения разработки.
Ограниченная бизнес-логика: в некоторых приложениях бизнес-логика может быть минимальной или тесно связанной со структурами данных.
У объединения слоев могут быть недостатки, особенно они становятся заметными по мере роста сложности приложения:
Масштабируемость и ремонтопригодность: при слиянии слоев может стать сложнее масштабировать приложение или поддерживать кодовую базу.
Нарушение принципа единой отчетности: слияние может привести к появлению классов, обрабатывающих как основную бизнес-логику, так и отображение, сериализацию или проверку данных.
Тесная связь: Слияние слоев может привести к более сильной связи между бизнес-логикой и представлением данных, что затрудняет изменение одного аспекта без влияния на другой.
Объединять или разделять слои в микросервисном приложении?
Сложность: Если микросервисный продукт имеет минимальную бизнес-логику или логика тесно связана со структурами данных, объединение слоев может быть более практичным. Однако если приложение имеет сложные бизнес-правила или требует четкого разделения между данными и логикой, лучше разделить слои.
Простота обслуживания: Разделение слоев может помочь сделать код более удобным для обслуживания за счет изоляции изменений и минимизации влияния модификаций.
Возможность повторного использования: В некоторых случаях микросервису может потребоваться поделиться своими доменными сущностями или моделями данных с другими микросервисами или частями системы. В таких сценариях разделение уровней сущностей и моделей может помочь повторно использовать компоненты и поддержании четкой границы между ними.
Эволюция: По мере развития микросервисов и роста их функциональности может возникнуть необходимость разделения слоев. Объединение может подойти для оперативной разработки или подготовки прототипов, но со временем может возникнуть необходимость в рефакторинге кода для разделения слоев.
Объединение слоев иногда используется в микросервисных приложениях. Решение о слиянии или разделении зависит от конкретных требований, сложности и контекста каждого микросервиса.
Методы обработки данных в объединенных уровнях
После слияния вы все еще можете обрабатывать данные и управлять ими различными способами. Ниже приведены некоторые общие техники и шаблоны для обработки данных в таких сценариях:
Сервисный уровень: внедрение сервисного уровня для обработки бизнес-логики приложения, оркестровки вызовов внешних служб и управления транзакциями.
Шаблон репозитория: реализуйте паттерн репозитория для передачи логики доступа к данным и их месту их хранения. Это поможет отделить проблемы доступа к данным от объединенных сущностей и моделей, что облегчит изменение базового хранилища данных или внедрить стратегию кэширования.
Шаблон сопоставления данных: используйте паттерн Data Mapper для преобразования данных между различными форматами отображения данных. Это поможет сохранить разделение между различными видами представления информации и облегчит адаптацию к изменениям в структурах данных.
Валидация: реализуйте логику валидации в отдельных классах или в качестве части сервисного уровня. Это поможет обеспечить согласованность данных и соблюдать необходимых ограничений даже при объединении слоев.
Объекты передачи данных (DTO): в некоторых случаях для передачи данных между различными частями приложения или между приложением и внешними системами может потребоваться создание отдельных объектов.
Паттерн адаптера: используйте паттерн адаптера для преобразования данных между различными форматами или интерфейсами. Это поможет разделить задачи и упростить интеграцию со сторонними системами или переключение между различными вариантами хранения данных.
Объединение уровней сущностей и моделей не лишает вас возможности использовать различные техники и паттерны для эффективной обработки и управления данными.
Пример разделения
Я собираюсь реализовать пример кода, в который слои сущностей и моделей используются отдельно.
Следующий код является частью файла entity.go.
type Product struct {
ID int
Name string
Description string
Price float64
Quantity int
}
// Entity: User
type User struct {
ID int
Name string
Email string
Password string
Address string
}
Ниже приведен код содержимого файла model.go.
package models
// Product Model
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
ThumbnailURL string `json:"thumbnail_url"`
}
// CartItem Model
type CartItem struct {
ProductID int `json:"product_id"`
Quantity int `json:"quantity"`
TotalPrice float64 `json:"total_price"`
}
Обычно разные объекты всегда используют разные файлы сущностей и файлы моделей.
Далее — содержимое файла main.go.
package main
import (
"encoding/json"
"fmt"
"github.com/<your-git-username-and-path>/entity_model/entities"
"github.com/<your-git-username-and-path>/entity_model/models"
)
func main() {
// Create a Product entity
product := entities.Product{
ID: 1,
Name: "iPhone 13",
Description: "A powerful smartphone",
Price: 999.99,
Quantity: 10,
}
// Create a Product from the Product entity
productModel := models.Product{
Name: product.Name,
Price: product.Price,
ThumbnailURL: "https://example.com/thumbnail.jpg",
}
// Create a CartItem from the Product entity and a quantity
quantity := 2
cartItemModel := models.CartItem{
ProductID: product.ID,
Quantity: quantity,
TotalPrice: product.Price * float64(quantity),
}
// Output the Product, Product, and CartItem as JSON
productJSON, _ := json.Marshal(product)
fmt.Println("Product:", string(productJSON))
productModelJSON, _ := json.Marshal(productModel)
fmt.Println("ProductModel:", string(productModelJSON))
cartItemModelJSON, _ := json.Marshal(cartItemModel)
fmt.Println("CartItemModel:", string(cartItemModelJSON))
}
В этом примере я задаю сущности Product и User и модели ProductModel и CartItemModel. Затем мы создаем экземпляры этих сущностей и моделей и выводим их в формате JSON.
Пример объединения
Я приведу пример, в котором слои объединены.
Ниже приведен код файла user.go, он находится в папке models.
package models
import "time"
type User struct {
ID int `json:"id,omitempty"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Password string `json:"-"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
func (u *User) FullName() string {
return u.FirstName + " " + u.LastName
}
В приведенном выше коде структура User
представляет собой как доменную сущность, так и модель данных. В ней есть поля для информации о пользователе: ID, имя и электронная почта, а также функции для бизнес-логики, например, FullName()
. Теги JSON для сериализации и десериализации включены непосредственно в структуру.
Следующий код — это файл user.go, он находится в папке service.
package service
import (
"errors"
"github.com/<your-git-username-and-path>/models"
)
var users []models.User
func CreateUser(user models.User) (*models.User, error) {
// Perform validation, e.g., check for unique email, etc.
for _, existingUser := range users {
if existingUser.Email == user.Email {
return nil, errors.New("email already exists")
}
}
// Perform business logic, e.g., save the user to the "database" (in this case, a slice)
users = append(users, user)
return &user, nil
}
В приведенном выше коде функция CreateUser
показывает, как объединенные сущности и модели используются вместе. Сервисный уровень выполняет проверку и бизнес-логику непосредственно на структуре User
, которая представляет собой как доменную сущность, так и модель данных.
Заключение
В чистой архитектуре Go сущности размещаются в доменном уровне, который является самым внутренним уровнем архитектуры и содержит основную бизнес-логику приложения. Модели часто используются на прикладном уровне, который находится между доменным и внешним слоями. Прикладной слой координирует поток данных между доменным и внешним уровнями, а слой моделей часто используется для представления данных в виде, подходящем для конкретного случая использования или взаимодействия.
Разделение слоев является хорошей практикой для поддержания чистоты кода, масштабируемости и ремонтопригодности, но объединение слоев может уместным в ситуациях, когда сложность минимальна или приоритетом является быстрая разработка. Выбор варианта должен зависеть от требований к приложению.
От редакции
17-18 июня Слёрм проводит интенсив «Чистая архитектура приложения на Go». Вы изучите, что такое чистая архитектура на языке Golang, под руководством опытного спикера создадите сервис по работе с контактами и возможностью их группировки и получите подробный фидбэк.
У интенсива прогрессивная цена — чем ближе к дате релиза, тем дороже. До 10 июня вы можете записаться по самой низкой цене — 25 000 рублей