Reputation: 2869
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
Reputation: 5898
Following a three-tier architecture could help to allieviate some of the problems you are seeing.
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
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