package storage

В коде есть ошибки типов, небезопасный SQL, нет атомарности выплаты и идемпотентности, а счётчик имеет гонку. Исправление требует согласованной транзакции и безопасной публикации событий.

Короткий ответ

Что ответить на собеседовании

В коде есть ошибки типов, небезопасный SQL, нет атомарности выплаты и идемпотентности, а счётчик имеет гонку. Исправление требует согласованной транзакции и безопасной публикации событий.

Подробный разбор

Ответ с пояснениями

Условие

package storage

import ( "database/sql" "errors" "fmt" "time"

"github.com/company/my-service/internal/event"
"github.com/company/my-service/internal/metric"

)

type Partner struct { ID string db:"id" json:"id" Balance float32 db:"balance" json:"balance" Hold float32 db:"hold" json:"hold" }

type PayoutRepository struct { db sql.DB eventPublisher event.Publisher metricCollector metric.Collector totalPayouts int }

func NewPayoutRepository() *PayoutRepository { db, err := sql.Open("postgres", "host=localhost user=postgres password=postgres dbname=affiliate") if err != nil { panic(err) }

r := PayoutRepository{
	db:              db,
	eventPublisher:  event.NewKafkaPublisher("localhost:9092"),
	metricCollector: metric.NewPrometheusCollector("localhost:9090"),
}
go func() {
	for range time.Tick(time.Minute) {
		_ = r.metricCollector.Send("TotalPayouts", r.totalPayouts)
	}
}()
return r

}

func (r *PayoutRepository) Payout(advertiserID, partnerID string, amount float32) error { advertiser, err := r.find(advertiserID) if err != nil { return err }

partner, err := r.find(partnerID)
if err != nil {
	return err
}

advertiser.Balance -= amount
partner.Hold -= amount
partner.Balance += amount

if err := r.save(advertiser); err != nil {
	return err
}
if err := r.save(partner); err != nil {
	return err
}

r.totalPayouts++
return nil

}

func (r *PayoutRepository) find(id string) (Partner, error) { p := Partner{} err := r.db.QueryRow("SELECT * FROM partners WHERE id = " + id).Scan(&p) if err != nil { if err == sql.ErrNoRows { return p, errors.New("partner not found") } return p, err } return p, nil }

func (r *PayoutRepository) save(p Partner) error { _, err := r.db.Exec( fmt.Sprintf("UPDATE partners SET balance = %s, hold = %s WHERE id = %s", p.Balance, p.Hold, p.ID), ) if err != nil { return err } if err := r.eventPublisher.Publish("BalanceChanged", p); err != nil { return err } return nil }

Ответ

В коде есть ошибки типов, небезопасный SQL, нет атомарности выплаты и идемпотентности, а счётчик имеет гонку. Исправление требует согласованной транзакции и безопасной публикации событий.

Поле db должно быть *sql.DB: sql.Open возвращает указатель; конструктор возвращает &r и ошибку вместо неуправляемого panic. Scan требует отдельных адресов полей, SQL — параметров, а не конкатенации или %s для чисел. Секреты и адреса передавайте через конфигурацию.

Храните суммы точно, проверяйте положительность, валюту, остатки и смысл Hold. Все изменения выполняйте в одной транзакции с защитой от конкурентной выплаты и повторного operation ID. Публикацию вынесите в transactional outbox: ошибка Kafka после UPDATE не должна превращать повтор запроса в двойную выплату. Счётчик синхронизируйте, цикл метрик сделайте отменяемым. Отдельно обработайте случай одинаковых участников; конкретное правило зависит от бизнеса.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее