I've referred to a repository instead of a DAO in this answer, but the distinction is moot for the purpose of this answer. The central focus here is on the distinction between the interface and implementation of whatever storage mechanism/pattern you're using.
I'll also point out that you didn't specify which language you're working with, but most popular languages (that I know of) specifically have interface and class constructs for this very purpose. I am assuming you are using such a language.
The main thing I want you to consider is the distinction between a class and its interface.
- The interface sits on the boundary between the persistence layer and its consumers (usually the application/domain layers)
- The class is a private implementation detail of that layer. It's also responsible for figuring out how to map that private implementation and have it integrate with the aforementioned interface
This leads to a two-fold answer when you refer to "a DAO", because your question seems to be built on the presumption of a 1-1 relationship of DAO interfaces and classes, which is not an inevitable fact.
The interface is designed based on the codebase's domain (I'm using domain as a vague term here that means "however the codebase wants to tackle these concepts"). If your domain logic has a separate model for a user and a group, then I would expect to see two separate interfaces, i.e. IUserRepository
and IGroupRepository
.
If you didn't do this, and you'd instead create a single IUsersAndGroupsRepository
, then I'd say you're leaking your persistence implementation as part of this interface design, which is a leaky abstraction and something to be avoided.
However, that doesn't mean that you need two separate UserRepository
and GroupRepository
classes to implement these interfaces. One class is able to implement multiple interfaces.
Since your class is your private persistence implementation, it is the representation of your persistence technology, and it will be designed with that consideration in mind (i.e. the data store, not the domain). This can (but doesn't have to) mean that you can do a "one class per database table" approach if you so choose.
Before we get to that decision, I just want to point out that the ability to design the interfaces and the classes individually, for their own reasons, is one of the cornerstones of having a properly abstracted layer. How the persistence layer works internally and how it plays with other layers are two separate concepts that should be designed each for their own individual reasons, and only when those two concepts have been fleshed out should you then start thinking about how to get these two concepts to integrate with one another.
If you only build one concept, and then build the other as an integration piece, you're creating a leaky abstraction. Either you're forcing your persistence implementation to mirror the domain structure (which restricts you from a different persistence implementation even if it's better) or you're designing your domain-driven interface based on a persistence implementation detail (which causes your domain to depend on your persistence tech). Both of these are bad situations to be in.
So, we've established that we are going to design the persistence implementation based on its own private needs. We have a single database table, but two domain interfaces. So the main question here is whether we should have two classes or one to handle the implementation. When you drill down into it, the main question here is what the benefit is of merging the classes.
If your repositories are only a "bag of queries", with no reusability between the individual (public) repository methods, then there's not really a benefit to lumping all these methods into a single class. In that case, I suggest you keep them separate simply to keep each class as small and digestible as possible.
However, there might be optimizations behind the public methods. You might be able to leverage some reusable queries that apply to multiple interactions with the database table, or you might have some kind of caching mechanic based on already fetched data from this table, where sharing this between user methods and group methods can lead to further caching benefits.
This is the part where you need to find a justification, but if a justification exists, it is perfectly acceptable for you to create a single class that handles all interactions with this data table.
Just to address an unmentioned edge case: it's also possible to have two separate classes which share a common ancestor (either through inheritance or composition) which contains the reusable bits. But this is a more granular approach that I'm skipping in favor of keeping focus on the two main avenues that you should consider here.
Such a class would implement two separate interfaces which, to a domain consumer, look like they're two separate repositories. The fact that they're actually referring to the same underlying database table is a secret that no one outside of the persistence layer is aware of, and therefore cannot possibly form a dependency on.
IF you ever break this table up, you would then be able to do so by only needing to change your persistence implementation, without needing to impact your domain/application logic, and that is exactly why/how we develop clean abstractions between layers in our codebase.