felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.71k stars 3.38k forks source link

docs: Discussion about Repository approach in `architecture` #3055

Closed luckyhandler closed 2 years ago

luckyhandler commented 2 years ago

Discussion about the described Repository architecture

In my opinion, also according to this video the repository is a layer of abstraction or "an object that your application uses to get and store a single type of data".

In my own words, I would explain it as "data black box for one type of data", which means:

I get the data from the repository and I don't have to care where the data comes from. It can be for example retrieved

I don't have to care about that but just retrieve (and also create, update, delete) it.

What it isn't usually used for - in my opinion - is to combine or transform the data from different data providers or as "wrapper around one or more data providers" as the architecture docs state.

The corresponding code from the docs use the following code

class Repository {
    final DataProviderA dataProviderA;
    final DataProviderB dataProviderB;

    Future<Data> getAllDataThatMeetsRequirements() async {
        final RawDataA dataSetA = await dataProviderA.readData();
        final RawDataB dataSetB = await dataProviderB.readData();

        final Data filteredData = _filterData(dataSetA, dataSetB);
        return filteredData;
    }
}

I would always put this kind of business logic inside the BLoC.

In my opinion the code inside the Repository should look like this

class Repository {
    final DataProvider<Data> dataProviderNetwork = NetworkDataProvider();
    final DataProvider<Data> dataProviderCache = CacheDataProvider();

    Future<Data> getData() => dataProviderCache.hasRecentData()
            ? dataProviderCache.getData()
            : dataProviderNetwork.getData();
}

I'm just interested about different opinions on this topic and would like to kick off a little discussion about this here.

chonghorizons commented 2 years ago

I'm pretty new and there are, of course, 100 valid opinions if they all work and are maintainable.

Yeah, I was thrown off by the description in docs of the repository, that it can merge two data providers. But after I thought about it, it could make sense. My guess of the number one use case is not about merging two separate providers, but for fallbacks.

Use Case 1: RespositoryWithFallbacks

Use Case 2: RepositoryWithMerging providers

Examples: news aggregator.

Use Case 3: RepositoryMultiOut

Example: Github datafeed respository Github can broadcast all the actions happening. The Repository outputs different "channels" exposed to Blocs. Like


Thanks @luckyhandler for asking the question, because it made me more carefully think about this.

Functionally, I think the main advantage is that Repository is the most general in how it emits/send streams of data out.

Here is how I think one can compare the 3:

image link

Your question has to do with the Orange ?, which I think is, on purpose, left open to the users of Repository.

Blocs and Cubits are opinionated (blue sections) about how requests come in and how the main stream goes out. When it works, it is great. RespositoryWithFallbacks and RepositoryWithMerging could be implemented with a Cubit or a Bloc, because they have a single out stream. RepositoryMultiOut is less straightforward. One could multiplex the BlocState so it carries multiple messages.

The other blue section is about UI. Blocs and Cubits are built for UI interactions. Repository are meant to be abstracted away. UI does eventually hit the Repository layer, but only via Blocs and Cubits.

The yellow sections are typical. For example, one doesn't have to expose streams. One could have a repo that one has a fetch method, like Brian Egan's many examples: https://github.com/brianegan/flutter_architecture_samples/blob/master/todos_repository_core/lib/src/todos_repository.dart

If people don't like the restrictions, one can always use the Remi R's package Provider package directly. Blocs and Cubits are built on Provider, so that's why context.reads() work.

I have a lot to say because I just finished writing a draft of the docs for the v8 flutter_todos example (Two highlights: https://github.com/chonghorizons/bloc/blob/chong-docs-todos/docs/fluttertodostutorial.md#architecture https://github.com/chonghorizons/bloc/blob/chong-docs-todos/docs/fluttertodostutorial.md#commentary---event-flow) You can help review that doc, which is part of this pending pull request:

===

The disagreements on what goes on what abstraction layer are neverending. For that, it's about code-manageability and code-reuse, and each case may be different. But, like you said, separating non-reusable custom logic out of the repository is probably a good idea.

tag #1819.
@chonghorizons
chonghorizons commented 2 years ago

@Gene-Dana Are you a moderator? If so, I think this can be tagged with label "waiting for response" and then auto-closed if no response.

chonghorizons commented 2 years ago

I think this still counts as "documentation". @felangel can you keep the documentation label?

PS: I can't add labels. If you want me to be able to, can you give me that permission? Otherwise, I'm fine just tagging you every time to suggest a label change.