There are 2 major line of thoughts regarding this matter. Both of them represented by a different design pattern. Both of these options also consider you're dealing with stateful entities which model aspects of your business scenario, in that sense, they're aware of the "data" that will be persisted however, they are not necessarily aware of the persistence mechanism itself.
Now, regarding the persistence mechanism, the first way of doing it is probably the most familiar to old-J2EE or Rails practitioners where the entity is fully aware that it will be loaded/saved into an underlying persistence and its interface will convey such methods as "get", "insert", "update". That's been called the "Active Record" (Martin Fowler, Patterns of Enterprise Application Architecture) pattern. That is, the entity while modelling an aspect of your business, it will represent also a straight record in the database and be able to save/load itself.
The other approach, which will be more inline with the "Clean Architecture" you mentioned, has been called by some authors the "Data Mapper" (also Martin Fowler, Patterns of Enterprise Application Architecture) pattern. In that matter the entity remains ignorant of the persistence mechanism (it will be "pure business logic, and nothing else") and you delegate to an external player (class / whatever) the responsibility of "mapping" the "data" that the entity currently holds into and out-of the persistence mechanism/layer.
In other words, when taking such an approach, you will delegate to a translator the responsibility of understanding the persistence mechanism and translating from database-to-entity and from entity-to-database. That way, your entities are never even aware that they are persisted somewhere else, much less of the inner workings of such persistence process.
The interface of persistence Data Mapper will be something along these lines:
interface IMyDataMapper {
void Save(IMyEntity entity);
IMyEntity Get(whatever criteria you use to find the entity);
}
So, from that interface, its responsibility is clear:
- It receives an entity (which is unaware of this operation) and reads its data to store it somewhere else.
- It receives criteria to finding stored data somewhere else, finds it and populates an entity object with this data to return it to you.