Validation of data entry is one of those things where everyone starts out trying to make it pure and clean and (if they're smart about it) eventually gives up, because there are so many competing concerns.
The UI layer must do some forms of validation right there on the client page/form in order to provide realtime feedback to the user. Otherwise the user spends a lot of time waiting for feedback while a transaction posts across the network.
Because the client often runs on an untrusted machine (e.g. in nearly all web applications), these validation routines must be executed again server side where the code is trusted.
Some forms of validation are implicit due to input constraints; for example, a textbox may allow only numeric entry. This means that you might not have a "is it numeric?" validator on the page, but you will still need one on the back end, somewhere, since UI constraints could be bypassed (e.g. by disabling Javascript).
The UI layer must do some forms of validation at the service perimeter (e.g. server-side code in a web application) in order to insulate the system against injection attacks or other malicious forms of data entry. Sometimes this validation isn't even in your code base, e.g. ASP.NET request validation.
The UI layer must do some forms of validation just to convert user-entered data into a format that the business layer can understand; for example, it must turn the string "6/26/2017" into a DateTime object in the appropriate time zone.
The business layer should do most forms of validation because, hey, they belong in the business layer, in theory.
Some forms of validation are more efficient at the database layer, especially when referential integrity checks are needed (e.g. to ensure that a state code is in the list of 50 valid states).
Some forms of validation must occur in the context of a database transaction due to concurrency concerns, e.g. reserving a unique user name has to be atomic so some other user doesn't grab it while you are processing.
Some forms of validation can only be performed by third party services, e.g. when validating that a postal code and a city name go together.
Throughout the system, null checks and data conversion checks may occur at multiple layers, to ensure reasonable failure modes in the presence of code flaws.
I have seen some developers try to codify all the validation rules in the business layer, and then have the other layers call it to extract the business rules and reconstruct the validation at a different layer. In theory this would be great because you end up with a single source of truth. But I have never, ever seen this approach do anything other than needlessly complicate the solution, and it often ends very badly.
So if you're killing yourself trying to figure out where your validation code goes, be advised-- in a practical solution to even a moderately complex problem, validation code will end up going in several places.
There are a few solutions to this:
- Package Separation
- Review Analysis
- Static Analysis
- Runtime Analysis
Each having their pros/cons.
Package Separation
This is the easiest way that doesn't require building anything extra. It comes in two flavors:
// /app/user/model/model.go
package usermodel
type User struct {}
// /app/user/controller/controller.go
package usercontroller
import "app/user/model"
type Controller struct {}
Or:
// /app/model/user.go
package model
type User struct {}
// /app/controller/user.go
package controller
import "app/user/model"
type User struct {}
This however breaks the wholeness of the concept of User
. To understand or modify User
you need to touch several packages.
But, it does have a good property that it's more obvious when model
imports controller
, and to some extents it's enforced by language semantics.
Review Analysis
If the application isn't large (less than 30KLOC) and you have good programmers, it's usually not necessary to build anything. Organizing structures based on value will be sufficient, e.g.:
// /app/user/user.go
package user
type User struct {}
type Controller struct {}
Often the "constraint violations" are of little significance or are easy to fix. It harms clarity and understandability -- as long as you don't let it get out of hand, you don't have to worry about it.
Static/Runtime Analysis
You can also use static or runtime analysis to find these faults, via annotations:
Static:
// /app/user/user.go
package user
// architecture: model
type User struct {}
// architecture: controller
type Controller struct {}
Dynamic:
// /app/user/user.go
package user
import "app/constraint"
var _ = constraint.Model(&User{})
type User struct {}
var _ = constraint.Controller(&Controller{})
type Controller struct {}
// /app/main.go
package main
import "app/constraint"
func init() { constraint.Check() }
Both static/dynamic can also be done via fields:
// /app/user/user.go
package user
import "app/constraint"
type User struct {
_ constraint.Model
}
type Controller struct {
_ constraint.Controller
}
Of course the lookup for such things become more complicated.
Other versions
Such approaches can be used in elsewhere, not just type constraints, but also func naming, API-s etc.
https://play.golang.org/p/4bCOV3tYz7
Best Answer
A use case is not a method.
A use case is not an object.
A use case is not a layer.
A use case is a story about a user, using the software, in a particular case.
So it should come as no surprise that different use cases can reuse the same code.
But maybe you watched one of the video'spaywalled where Bob makes out like a use case is part of your architecture.
Well don't worry. It's just a name for the boxes in one of his layers. He's used other names for it:
Does this mean Uncle Bob is wrong? No. The Interactor / Use Case / Application Business Rule / Oh-pick-a-name-and-stick-with-it layer is where you ignore all of the needs of your application (details) and focus on the needs of the user going through a particular use case. But this location in your code is not THE use case. The use case is the whole story from when the user clicks a button to when they see the result.
So should interactors (or whatever you want to call them) use other interactors? Well this is the rub of following DRY to the extreme. You're not allowed to put the code from
AddUserToGroup
anywhere else right?Baloney. If
AddUserToMandatoryGroups
means something different, has a different reason to want to change thanAddUserToGroup
does then it's ok to giveAddUserToMandatoryGroups
its own add-user code. Even if right now it's a character for character copy of the code inAddUserToGroup
. If you have good reason to think these need to be able to change independent of each other it doesn't matter if right now they look identical. Apply DRY to repeating ideas. Not code.As for Dependency Injection, that still works fine whenever you want to decouple what from when.