visit
This story is about pain, agony, and denial of ready-made solutions. It is also about changes that improve the code’s readability and help the development team stay happy. The object of this post is an interface that helps a program communicate with a database.
Disclaimer: If we would use various such as in this project, most probably we would not face this issue, yet, we decided to write our implementation, so this created the issue and therefore this post.
The problem with this interface was its size — in one single interface! That’s a lot of methods and that is not what interface should look like. And since we develop in Go, we have to know (and follow) one of which are:The bigger the interface, the weaker the abstraction. © Rob PikeThe further we developed the project, the heavier this interface grew and soon it became clear that to continue the development with fewer bugs, less time spent understanding the code, and more comfort, this interface should be refactored. We as a team could not use this interface with flexibility. We could not tell from the first glance what it does since it does everything. And that forced me to start its refactoring. Which is what I want to share with you.
// IStorage represents database methods
type IStorage interface {
User() User
Profile() Profile
Agreement() Agreement
AgreementChanges() AgreementChanges
Milestone() Milestone
Contact() Contact
Notification() Notification
Payout() Payout
WebhookEvent() WebhookEvent
Referral() Referral
VerificationCode() VerificationCode
Feedback() Feedback
PlatformTransfer() PlatformTransfer
}
user, err := api.Storage.GetUserByID(ctx, userID)
err := api.Storage.DenyAgreement(ctx, agreement)
err := api.Storage.UpdateUserDeviceToken(ctx,
model.UserDeviceToken{...})
user, err := api.Storage.User().Get(ctx, userID)
err := api.Storage.Agreement().Deny(ctx, agreement)
err := api.Storage.User().UpdateDeviceToken(ctx,
model.UserDeviceToken{...})
// IStorage represents database methods
type IStorage interface {
// User() User
// Profile() Profile
// Agreement() Agreement
// AgreementChanges() AgreementChange
// Milestone() Milestone
// Contact() Contact
// Notifications() Notification
// Payout() Payout
// WebhookEvents() WebhookEvent
// Referrals() Referral
// VerificationCodes() VerificationCode
// Feedback() Feedback
// PlatformTransfe() PlatformTransfer
// this is just a temporary change not to
break all the functionality. Will reimplment this
// for the version mentioned above as a
next step with just a small chucks under
improvements
User
Profile
Agreement
AgreementChange
Milestone
Contact
Notification
Payout
WebhookEvent
Referral
VerificationCode
Feedback
PlatformTransfer
}
// Notification inferface contains methods for
notifications manipulation
type Notification interface {
CreateNotification(ctx context.Context, n
*model.Notification) (model.Notification, error)
GetNotification(ctx context.Context, id
int64) (model.Notification, error)
GetUnreadNotificationsCount(ctx
context.Context, receiverID int64) (int, error)
ListNotification(ctx context.Context,
userID int64) ([]model.Notification, error)
SetNotificationAsRead(ctx
context.Context, id int64) error
ResetUnreadNotifications(ctx
context.Context, receiverID int64) error
}
// IStorage represents database methods
type IStorage interface {
Feedback() Feedback
// this is just a temporary change not to
break all the functionality. Will reimplment this
// for the version mentioned above as a
next step with just a small chucks under
improvements
User
Profile
Agreement
AgreementChange
Milestone
Contact
Notification
Payout
WebhookEvent
Referral
VerificationCode
PlatformTransfer
}
func (s *Storage) Feedback() Feedback {
return &FeedbackClient{cfg: s.cfg,
logger: s.logger, pool: s.pool}
}
// Feedback inferface contains methods for
feedback manipulation
type Feedback interface {
Create(ctx context.Context, f
*feedback.Feedback) (*feedback.Feedback, error)
}
type FeedbackClient struct {
cfg config.Config
logger *logrus.Entry
pool *pgxpool.Pool
}
err = api.Storage.Profile().Update(ctx, user)
if err != nil {
api.Logger.WithFields(logrus.Fields{"err":
err}).Error("Update failed")
}
After refactoring large interfaces always check for ‘side effects”. Basically
If you ever face the same issue, please share your solutions in the comments, since I am really curious about what else I might have done. Also, please share your thoughts on what I could (and still can) do to make it even better. Any feedback is more than welcome.
Previously published at .