Pergunta

I'm trying to apply Clean Architecture to a mobile Android App, but I still have some doubts about how to manage API calls.

Currently, the classes are structured like this:

View -> ViewModel -> UseCase -> Repository -> DataSource

It's all well and good when the API calls are about fetching some data from/sending some data to an API. The Repository handles the data I put into it or retrieve from it, and behaves like a collection.

Now, what should I do when my API call has nothing to do with some data I could put in a collection?

Example: The user hasn't received the "confirm your email" link on his email, so he clicks a button named "Resend Email Confirmation Link". Then, I'll have a ResendEmailConfirmationUseCase.

My question is: should I have a repository to make that API call?

That doesn't make sense to me, as I'll handle no data, but just send a signal to an API saying "Hey, send that confirmation email again". There is no collection of data that translates into a repository here. How would I even name that repository?

How would you approach this problem?

Foi útil?

Solução

I wouldn't.

You cannot express an action in this framework as an action.

This architecture is about things and is not about actions. Conflating the two is a sure recipe for confusion.


If you absolutely must though.

The trick is find a way to communicate the Action in terms of Things.

I can see two approaches:

  • turn the action itself into a thing.
  • ignore the action and focus on the things that bound the action.

Action as a Thing

Instead of calling a function directly, you describe the desired action in a task. Save that task to the repository and on the server there is a worker responsible for fulfilling each task.

In this case a SendMessageTask with some parameters indicating what sort of message to send, to whom, etc... The task is consumed when the task is completed.

Action hidden between States

Instead of bothering with the action look at the states (things) that surround the action. Save the state which is pre-action into the repository. The server is responsible for waiting for, or performing the action required to transition the state to the next state (post-action).

In this case you would save Unsent Message into the repository. At some point a server process loads Unsent Messages and then Sends them. It deletes the Unsent Message and then saves a Sent Message or Undelivered Message depending on the new next state.

Perspective

If you stare hard enough at both solutions, you will see that a task is just a workflow over a set of states, and vice versa states are a breakdown of a task.

That essentially means the solution is identical, it just boils down to what is the more important concept: A Request for Work, or The State of the System.


Either way the client does not directly "action" a server activity. The client only describes an action, or a state with an implied action to the server. At some later point the server chooses to fulfil the request for action, or act to transform data in a given repository.


Alternately, change this architecture. Add in the ability to instruct the server.

View -> ViewModel -> UseCase |---------------------->|-> Repository -> DataSource
                             |-> Agent -> Command ?->| 

The UseCase determines that a business activity is required and instructs the Agent to handle it. The Agent then orchestrates one or more Commands to fulfil the business activity. Those Commands may need to interact with the Repository or with other systems (such as an email server).

Its a matter requirements and of taste as to where you draw the dotted line for client vs server.

I would place the dotted line down the middle of Agent/Repository. Having the bulk of implementation on the server, and a proxy with suitable caching, sanity checks, etc... duplicated on the client. This way Agent, Command, Repository, and DataSource are truly trustable. Use Case would have a question mark, but could not do anything not already permitted by the Agents/Repositories.

That being said, it could be drawn down the middle of the UseCases, or the ViewModels just as easily to trade off responsiveness vs trust.

Licenciado em: CC-BY-SA com atribuição
scroll top