I'm learning domain driven design
for Flutter apps. I understand that the model is used between the infrastructure layer and the use-case, and the entity is used between in the use case and the UI.
Let's say that my app is dealing with books and I'm storing my books in Cloud Firestore
. I have defined a very simple BookEntity
with an id and a title.
@freezed
class BookEntity with _$BookEntity {
const factory BookEntity({
required String firestoreId, // This is the ID of the document in firestore
required String title,
}) = _BookEntity;
}
I believe that the ID of the document should be in the entity, because if I need to modify this book in the Firestore I will need to know the reference of the document, right?
As you know, in firestore the id is not part of the data themselves. When I read my database, I would be using the code below.
// code not tested
FirebaseFirestore
.instance
.collection('books')
.doc('uXSin0z3gqPHwhVLCP98')
.get()
.then((DocumentSnapshot<Map<String, dynamic>> snapshot) {
snapshot.id; // -> 'uXSin0z3gqPHwhVLCP98'
snapshot.data(); // -> {title: 'To Kill a Mockingbird', price: 11.69, year: 1960}
});
Somewhere, I will need to put the id together with the data. I think the right place to do it is in the model, which is created in the repository. I think my model would probably look very similar to the Entity:
@freezed
class BookModel with _$BookModel {
const factory BookModel({
required String firestoreId,
required String title,
}) = _BookModel;
}
And, when fetching data from Firestore I would create a model with:
BookModel(
firestoreId: snapshot.id,
title: snapshot.data()?['title'],
);
This can then be converted to a BookEntity which will be consumed by the UI.
The problem is that when I am reversing the flow, when I am creating a new book, the ID of the firestore document is not known in the presentation and domain layers. Therefore my BookEntity
and BookModel
must be updated so that the id is optional. The entity and the model now look like this
@freezed
class BookEntity with _$BookEntity {
const factory BookEntity({
String? firestoreId,
required String title,
}) = _BookEntity;
}
@freezed
class BookModel with _$BookModel {
const factory BookModel({
String? firestoreId,
required String title,
}) = _BookModel;
}
The problem is that now, every time I need to access the firestoreId
field of my BookEntity
, whose data originate from Firestore, I need to test whether the firestoreId
field is null or not. But it cannot be null because the data come from Firestore, so there is always an ID. So I will either write a lot of null-checks, or use the !
(which I don't like).
In short, the "upstream" and "downstream" flows have different requirements for the firestoreId
field. The Firebase -> UI flow needs a String
, and the UI -> Firebase flow needs a String?
.
So the question is what is the best and cleanest way to handle this?
CodePudding user response:
firestoreId
should be nullable, because it make sense for BookEntity
to have firestoreId
sometime and not have firestoreId
sometime.
You probably don't need to use firestoreId
in the UI, and It's only needed when writing to the Firestore. So you can have a writeToFirestore
method and only use a single null check there.
You can also generate a new random id locally whenever a new BookEntity
is created. Using your own document id when creating a document in Firestore
One more solution is to use late final String firestoreId
, but it's skipping null check making debuging harder and doesn't work with Freezed
.