Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review PR #9

Open
wants to merge 72 commits into
base: review
Choose a base branch
from
Open

Review PR #9

wants to merge 72 commits into from

Conversation

Barugoo
Copy link
Collaborator

@Barugoo Barugoo commented Mar 21, 2023

No description provided.

s.mamykin and others added 30 commits March 6, 2023 19:30
@@ -0,0 +1,23 @@
help: ## Display this help screen
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лайк за мейкфайл

@@ -0,0 +1,44 @@
version: '3.5'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

За докеркомпоуз тоже лайк!

return service.AccrualOrder{}, err
}

if resp.StatusCode() == http.StatusNoContent {
Copy link
Collaborator Author

@Barugoo Barugoo Mar 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы здесь все же возвращал кастомный ErrNotFound обернутый в поясняющий текст:

return service.AccrualOrder{}, fmt.Errorf("order not found in accrual: %w", ErrNotFound) 

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ Не уверен что сделал так как предполагалось - я возвращаю ошибку, но и объект тоже по-прежнему возвращаю.
И в вызывающей функции логирую этот момент с warning.

c.Abort()
return
}
c.Set("current_user_id", userID)
Copy link
Collaborator Author

@Barugoo Barugoo Mar 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использовать встроеные типы в качестве ключа контекста не рекомендуется тк может быть коллизия. Плюс не очень классно что хэндлеры должны знать ключ и тип значения для того чтобы достать userID.
Лучше всего сделать работу с контекстом как описано здесь:
https://bongnv.com/blog/2021-04-10-safely-use-go-context-to-pass-data/

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️
Спасибо за замечание. Я сделал отдельные методы, которые немного облегчают задачу и позволяют хендлерам не знать ключ.
К сожалению, как я понял, заиспользовать специальный тип для ключа не получается с gin.Context. Ну или это не так просто.
gin-gonic/gin#1123 (comment)

Comment on lines 17 to 27
func (z *ZeroLogAdapter) Warn(err error) {
z.Logger.Warn().Stack().Err(err).Msg("")
}

func (z *ZeroLogAdapter) Err(err error) {
z.Logger.Error().Stack().Err(err).Msg("")
}

func (z *ZeroLogAdapter) Fatal(err error) {
z.Logger.Fatal().Stack().Err(err).Msg("")
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для ошибок, варнингов и фаталов нам тоже нужна возможность добавлять сообщения пояснительные, иначе понять где этот лог записан будет нвеозможно

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я так понял ты эту проблему решаешь выводом стэктрейса но это плохая практика - стектрейс дорогой и его неудобно читать по сравнению с сообщением "unable to get user blah-blah"

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ вместо трейса теперь есть второй параметр msg.

return
}

u.logger.Err(err)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай напишем пояснение к ошибке (и сделаем метод который это позволяет делать), а-ля:

u.Logger.Errf(err, "unable to create new user")

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

}

user, err := u.userService.CreateNewUser(credentials)
if err == nil {
Copy link
Collaborator Author

@Barugoo Barugoo Mar 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай уменьшим вложенность, сделав happy-path в конце функции:

user, err := u.userService.CreateNewUser(credentials)
if errors.Is(err, service.ErrPwdIsNotValid) || errors.Is(err, service.ErrLoginIsNotValid) {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
}
if err != nil {
    u.Logger.Errf(err, "unable to create new user")
    c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    return
}

tkn, err := token.Generate(user.ID, u.apiSecret, u.tokenLifespan)
if err != nil {
    u.logger.Errf(err, "unable to generate new token")
    c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
     return
}

c.Header("Authorization", fmt.Sprintf("Bearer %s", tkn))
c.JSON(http.StatusOK, gin.H{"message": "success"})

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ тут и в других хендлерах порефакторил этот момент.

}
order, err := u.orderService.AddOrder(currentUserID, orderNumber)
if err != nil {
if err == service.ErrEntityAlreadyExists {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше использовать errors.Is

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

userID := getCurrentUserIDFromContext(c)

balance, err := u.userService.GetBalance(userID)
if err != nil {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут надо бы залоггировать

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

func getCurrentUserIDFromContext(c *gin.Context) int {
currentUserIDAsAny, ok := c.Get("current_user_id")
if !ok {
panic("cannot get current user id. check the endpoint is protected.")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мы никогда не паникуем в хэндлерах, если ты хочешь зафиксировать наличие мидлвари перед хэндлером то лучше написать на это тесты, а здесь просто возвращать ошибку

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ переделал на возврат ошибки.

}
defer rows.Close()

return hydrateOrders(rows)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется что это не особо уменьшает дублированние кода (эта функция используется в двух местах только) зато сильно ухудшает читаемость. Давай тело функций гидраторов вставим непосредственно внутрь метода

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment on lines 59 to 62
case entity.AccrualStatusRegistered:
fallthrough
case entity.AccrualStatusProcessing:
order.Status = entity.OrderStatusProcessing
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно написать два значения в кейсе через запятую чтобы не делать fallthrough:

case entity.AccrualStatusRegistered, entity.AccrualStatusProcessing:
    order.Status = entity.OrderStatusProcessing

for _, order := range orders {
accrualOrder, err := o.AccrualClient.GetOrder(order.OrderNumber)
if err != nil {
o.Logger.Err(err)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут и далее хорошо бы пояснения к логу

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment on lines 5 to 11
func InvokeFunctionWithInterval(duration time.Duration, functionToInvoke func()) {
ticker := time.NewTicker(duration)
for {
<-ticker.C
functionToInvoke()
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

было бы неплохо сюда еще прокинуть контекст и внутри цикла делать select по <-ticker.C и <-ctx.Done - в ctx.Done кейсе делать return

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment on lines 118 to 119
if err != nil {
if err == service.ErrEntityAlreadyExists {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Опять же, лучше уменьшать вложенность когда это возможно (а здесь это возможно)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

"order_number" VARCHAR NOT NULL,
"status" INTEGER NOT NULL,
"accrual_status" INTEGER NOT NULL,
"accrual" DOUBLE PRECISION NOT NULL,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну и самое главное - арифметика с сырыми floating-point значениями крайне нерекомендована при работе с деньгами. Поэтому я просил как можно скорее принести работу на проверку ментором.

Флоты в силу своей реализации в компутере имеют проблемы с точностью представления чисел и округление тут не спасает: например нам надо поделить 100 рублей между тремя юзерами. Мы делаем

var sum float64 = 100
var usersCount float64 = 3

res := sum/usersCount // округляется в 33, куда делся рубль?

Есть два решения этой проблемы

  • Хранить в БД деньги интами в копейках. В коде складывать, вычитать, умножать и делить (не забывая при этом проверять остаток, чего нельзя делать с флотами) тоже используя инты. Правда когда будем общаться с клиентом надо будет делить/умножать на 100, тк ТЗ требует два знака после запятой
  • Работать в коде с этим пакетом https://pkg.go.dev/github.com/shopspring/decimal, хранить в базе в numeric

Я рекомендую первый подход

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ переделал на вариант с копейками.

s.mamykin and others added 14 commits March 24, 2023 17:36
…вместе с заказом со статусом Unregistered ошибку, которая будет залогирована
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants