Как сжать этот код и объединить условие if и else
if category == 0 {
rows, err := h.Repo.GetAllLatestProducts(c.Context())
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
result := make([]interface{}, len(rows))
for i, product := range rows {
result[i] = dbrow.ConvertToAllLatestProducts(product)
}
} else {
rows, err := h.Repo.GetLatestProductsByCategory(c.Context(), int16(category))
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
result := make([]interface{}, len(rows))
for i, product := range rows {
result[i] = dbrow.ConvertToCategoryLatestProducts(product)
}
}
Оба условия if и else соответствуют одному и тому же процессу кода, только функции и структура разные, как их объединить, чтобы код был меньше. Я имею в виду:
var rows []postgres.GetAllLatestProductsRow
var rows []postgres.GetLatestProductsByCategoryRow
if category == 0 {
rows, err = h.Repo.GetAllLatestProducts(c.Context())
} else {
rows, err = h.Repo.GetLatestProductsByCategory(c.Context(), int16(category))
}
//Rest of the code ...
не может касаться h.Repo.GetAllLatestProducts или h.Repo.GetLatestProductsByCategory, поскольку это внешние функции. Типовая безопасность также важна.
Может быть несколько функций, таких как Featureproducts, Newproducts, я хочу создать общую функцию для возврата продуктов как json на основе динамически выбранной функции sql.
У вас проблема, десятки функций с одной и той же структурой кода - это плохо, по крайней мере, для меня это не имеет значения, читается он или нет, это просто дубликат копировать / вставлять без всякого смысла, в основном копировать / вставлять только с именем функции /SQLC изменяет имя функции и имя структуры, в остальном поток кода такой же.
SQLC генерирует автоматический код на основе запроса SQL, теперь это пустая трата времени на написание повторяющегося кода, чтобы просто преобразовать результат в JSON и вернуть его клиенту. Могут быть десятки функций SQL для возврата последних продуктов, рекомендуемых продуктов, продуктов в категории, продуктов из списка желаний, бла-бла-бла...
Все, что веб-сайт понимает, - это структура продукта, но SQLC возвращает разные структуры, поэтому нет единого типа dbResult. В любом случае сопоставление не представляет большого значения, используя отражение, мы можем проверять поля с тем же именем и преобразовывать SQL.NullString в строку и т. Д. В функции сопоставления.
Настоящая проблема для меня - это операторы if/else. Вы перенесли код в разные функции, но для меня в данной ситуации это не имеет смысла. Поскольку веб-обработчик в любом случае должен будет проверить, является ли запрос действительным или нет, определена ли категория или нет, затем проверьте, равна ли категория 0 или нет, а затем вызовите различные функции, а затем получите результат и вернитесь к клиенту. Для одной функции это может выглядеть лучше, но для реального производства это усугубит ситуацию, и вместо одной функции и блока if/else теперь у вас есть 3 функции для каждого API.
Я ищу, чтобы просто сопоставить результат SQLC с обработчиком маршрута. Поток кода всегда один и тот же, меняется только имя функции и имя структуры. Как сделать его динамическим, чтобы в моем обработчике http я мог просто написать:
return SQLCResult(c.Query("category"), GetAllFeaturedProducts, GetFeaturedProductsByCategory)
а затем на основе значения категории из c.Query("категория") SQLCResult автоматически вызовет либо GetAllFeaturedProducts, либо GetFeaturedProductsByCategory. что-то вроде функции обратного вызова, но подпись функции другая, это одна проблема.
func (q *Queries) GetAllFeaturedProducts(ctx context.Context) ([]GetAllFeaturedProductsRow, error)
func (q *Queries) GetFeaturedProductsByCategory(ctx context.Context, idCategory int16)
функция сопоставления не требуется, потому что в SQLCResult мы можем сделать что-то вроде:
MapDBStructToRestAPIStruct(&Product{}, &row, MapFields(&row))
который создаст карту имени поля и индекса, передаст строку dbresult и преобразует ее в структуру Product с использованием отражения и вернет то же самое, т.е. вернет первый параметр как результат после изменения его полей.
Я все еще ищу, как написать функцию SQLCResult, чтобы взять имя функции SQLC в качестве входных данных и затем вернуть результат, или можно сделать его более общим, поместив саму структуру Product{} в функцию SQLCResult, например:
var result := SQLCResult(&Product{}, c.Query("category") == 0, GetAllFeaturedProducts, GetFeaturedProductsByCategory)
return c.Status(fiber.StatusOK).JSON(result)
где SQLCResult вызовет GetAllFeaturedProducts или GetFeaturedProductsByCategory на основе логического условия и создаст сопоставление результата функции со структурой, переданной как 1-й аргумент, и вернет эту структуру обратно.
или может быть это конечная цель:
func (h *Handlers) GetLatestProducts(c *fiber.Ctx) error {
if c.Query("category") == 0
return c.JSON(SQLCResult(&Product{}, GetAllLatestProducts)
else
return c.JSON(SQLCResult(&Product{}, GetLatestProductsByCategory, c.Query("category"))
}
func (h *Handlers) GetFeaturedProducts(c *fiber.Ctx) error {
if c.Query("category") == 0
return c.JSON(SQLCResult(&Product{}, GetAllFeaturedProducts)
else
return c.JSON(SQLCResult(&Product{}, GetFeaturedProductsByCategory, c.Query("category"))
}
1 ответ
Есть что учесть, код действительно не имеет проблем, но его может быть трудно поддерживать, это может быть еще сложнее с более похожими сценариями, как задумано, он плохо масштабируется, и в долгосрочной перспективе может появиться больше правил, упрощающих его спагетти.
что мы хотим, так это разделение проблем, многоразовые похожие части и ясность. мы можем получить это без особых сложностей.
учитывая, что мы не можем изменить API репозитория - это может быть прямой подход - мы должны обернуть репозиторий, больше как Decorator или, в терминах Go, Shadowing.
// ProductHandler shadows product repository
type ProductHandler struct {
*Repo
}
это позволяет нам лучше инкапсулировать интерес каждого звонка
func (ph ProductHandler) GetLatestProductsByCategory(ctx context.Context, cat int) ([]interface{}, error) {
if cat == 0 {
return nil, nil
}
l, err := ph.Repo.GetLatestProductsByCategory(ctx, cat)
return ResultSet(&ProductsByCategory{}, l), err
}
func (ph ProductHandler) GetAllLatestProducts(ctx context.Context) ([]interface{}, error) {
l, err := ph.Repo.GetAllLatestProducts(ctx)
return ResultSet(&Products{}, l), err
}
при этом мы делегируем ответственность за получение или отключение категорий собственным методом и автоматически переносим набор результатов в его собственный тип, соответственно разделяя ответственность за отображение.
type Products struct {
Id string
Name string
}
type ProductsByCategory struct {
Category string
}
чтобы добиться преобразования набора результатов db в определенный тип, мы должны предоставить общий интерфейс, поэтому любому типу, который реализует этот интерфейс, разрешено преобразовывать (переводить, отображать, гидратировать являются синонимами) самого себя
type Transformer interface {
Transform(interface{}) interface{}
}
теперь каждый тип может иметь собственное преобразование из -> в
func (p Products) Transform(i interface{}) interface{} {
v, _ := i.(*dbresult)
p.Name = v.RawName
p.Id = v.RawId
return p
}
func (p ProductsByCategory) Transform(i interface{}) interface{} {
v, _ := i.(*dbresult)
p.Category = v.CategoryName
return p
}
с функцией, которая поможет нам преобразовать список данных, которые мы можем использовать повторно, когда захотим
func ResultSet(t Transformer, d []interface{}) []interface{} {
result := make([]interface{}, len(d))
for i, p := range d {
result[i] = t.Transform(p)
}
return result
}
к настоящему времени наша реализация может просто выглядеть так, и все эти части можно использовать повторно
func main() {
var category int
// h.Repo
repo := Repo{}
ph := ProductHandler{&repo}
pcat, _ := ph.GetLatestProductsByCategory(context.Background(), category)
products, _ := ph.GetAllLatestProducts(context.Background())
products = append(pcat, products...)
for _, product := range products {
fmt.Printf("%v\n", product)
}
}
хотя код использует
interface{}
в этом нет ничего плохого, в конце концов, ваши данные уже поступают из базы данных вот так, и мы просто их передаем. Тип, утверждающий их, потенциально может быть дорогостоящим, если все сделано плохо, в данном случае это не так, до вызова json marshal.
вы можете найти рабочую копию здесь, там есть макет возможных вызовов ответа db для поддержки случаев. попытаться оценить
category
и посмотрим, что произойдет