Based on the comment exchange on the question, I suspect that the source of the confusion here is about the role a repository plays in this setup.
You mentioned:
I decide to implement the builder pattern with a builder that has access to the badge storage mechanism, which would be called with an employee and create a repository specific to the employee
This implies that you consider the repository to not be the storage mechanism. While not impossible, "repository" tends to refer to the storage mechanism. Concretely, if all badges are stored in a single database table, then there is one repository that represents that table.
I might also create a repository for employee badges, but because badges only make sense in the context of an employee,
Be very careful to separate your BLL (or Domain) logic from your DAL logic here. While from a business perspective, it makes sense that badges are in scope of a person, that does not mean that the storage structure has to follow suit!
The simplest example here is that you could be storing all badges in the same table. Even though the domain logic never works with a set of "all badges" (without a person's context), the database actually does do so.
many methods in the repository would have to take the employee as a parameter.
In and of itself, that is IMO not enough justification.
I do agree that software development means automation, and automation means tackling repetition and needless effort Developers are "lazy", as they say. But it's very easy to go a bridge too far with this, often leading to shortcuts and starting to tread in the territory of bad practice.
If "I don't want to repeat the person's ID" is your main concern, then I am of the opinion that you've gone a bridge too far. That's too lazy. Because then you could also argue that almost every repository often requires a PK value (for all operations on a singular resource, other than the Create logic), which would similarly lead you to pre-configure the PK value so you don't need to put it in the method parameters.
It's technically viably, but conceptually undesirable.
Note however that there are ways to mitigate this and find a happy middle ground in cases where you'd be passing both the badge and person ID to the same method. You could create compound keys, which are a combination of your specific resource's PK and the aggregate root PK (= the resource's FK).
public struct BadgeReference
{
public int PersonId { get; }
public int BadgeId { get; }
public BadgeReference(int personId, int badgeId) : this()
{
PersonId = personId;
BadgeId = badgeId;
}
}
This way, you don't have to add an extra method parameter for the person's ID (you just substitute the int badgeId
for BadgeReference badgeReference
, and your repository can use the reference object to filter on both PK and FK. It also helps keeping things scoped neatly, making it harder to accidentally mismatch a person and a badge object.
You also mentioned:
I've read about aggregates, and from what I understand the employee would be the aggregate root and badges would be children of employees. That is what I'm trying to implement here, and I want to see how it would work together with the repository pattern.
There are a few ways to skin this cat.
Aggregates should be fetched in full. Therefore, if Badge
falls under the Person
aggregate root, any fetched person would have their badges loaded.
Note: Some devs deviate from this for performance's sake, but that is off-label usage.
Depending on how your data is stored, and how complex its retrieval is, you can take different approaches here.
- You could have a separate
PersonRepository
and BadgeRepository
, and the Domain logic composes a person from these two sources.
- You could have a separate
PersonRepository
and BadgeRepository
, but only the PersonRepository
is made public (outside of the assembly) and the BadgeRepository
is used as a dependency of the PersonRepository
.
PersonRepository
could just act as the repository for the full aggregate root.
These choices are contextual. It depends on the complexity of your data structure. Maybe a BadgeRepository
is overkill, e.g. if badges are really just an integer (albeit wrapped in a neat Badge
class, of course). Maybe BadgeRepository
is not useful since the data is somehow stored with the person data and not in a separate source (i.e. database table, file, ...). Maybe badges are very complex data and BadgeRepository
helps divide the workload away from the Person
logic.
Technically, you could pick any of these options and match them with any of the storage methods (separate table, with person data, ...), and it will always be workable. But depending on what your reasonable expectations are about the way this data is structured, some choices make more sense than others.
Therefore, I decide to implement the builder pattern with a builder that has access to the badge storage mechanism, which would be called with an employee and create a repository specific to the employee, passing it the employee and storage mechanism during instantiation.
As an aside, I'm more inclined to call that a factory pattern than a builder pattern. You're not really leveraging the "many optional configurations" part of what distinguishes a builder from a factory. This seems to be a much more straightforward "you must provide X and I'll give you Y back".
Badges
database table. Not "Tom's badges". The separation you're trying to implement sounds much more like a DDD aggregate concept, rather than something that should be implemented on the repository level. I'm trying to establish if you (a) know about aggregates (b) have possibly already dismissed aggregates and (c) if your understanding of what a repository is matches with the general definition.