user3162553
user3162553

Reputation: 2869

Managing circular dependencies in golang

I'm having trouble understanding the best way to organize an application dependency structure in golang.

Essentially, my application is structured like this:

accounts
  ...
  handlers
    accounts.go
  models
    account.go
  repos
    account.go

Now I have a database access object (DAO) setup for accounts and I would like to avoid putting validation logic in there because that fits the model domain better. Repos are designed to only handle interaction with the database.

Inside of models, I believe it makes sense to put validations. However, one validation such as the uniqueness of the email, requires using the DAO which throws an error about circular deps.

I'm curious what is the best way to organize validations in this paradigm? Should I really include a service layer for any interaction at all? That is, should I have a model that is just the struct for the Account and then have validations and before save hooks in the service?

Upvotes: 3

Views: 3024

Answers (2)

Zak
Zak

Reputation: 5898

Following a three-tier architecture could help to allieviate some of the problems you are seeing.

Wikipedia link

The reason that you are running into problems is because there's no single place that owns the validation of your models. There's no single place that can pull in all the information that's needed to perform that validation.

Validation is (almost always) based on a set of business-rules (or logic-rules). These rules are nothing to do with the structure of the data (owned by the models package), or the data storage (owned by the dao layer).

Adding a service layer to all code layers, to consume both the model and repo packages will given you a single place to encapsulate the logic rules that are required for validating a model. It will also give you a single logic place for this validation.

In general, I would advocate for always adding a service layer. Even if it does nothing. This rule of thumb gives a more flexible design that allows for requirements to change.

Upvotes: 5

Zan Lynx
Zan Lynx

Reputation: 54355

One thing that I did once, although I consider it kind of hackish, is to use an interface or function pointer. In your case, it would be in your model.

Then in code higher up in the application, which can use the model and the DAO without circular reference, have that code set the interface or function pointer.

The model can call that method without needing its code.

I suppose it is a form of dependency injection. I wasn't that happy with it when I did it myself, but it happened to be the easiest way at the time.

Upvotes: 0

Related Questions