Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactored data fetchers to be more abstract #32

Merged
merged 22 commits into from
Jul 22, 2019

Conversation

kss-espeo
Copy link
Collaborator

Rationale behind this change: not all of the fetchers depend on a REST call (for example random data provider), so it doesn't make sense to put URL in the interface call. Still, we'd like a consistent interface to implement FetcherFactory once random data provider is introduced.

src/domain/common/usecase/FetchDataUseCase.js Outdated Show resolved Hide resolved
@@ -11,10 +11,6 @@ class Request {
this.state = state ? new RequestState(state) : RequestStateFactory.createState(validFrom);
}

getRawUrl() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We strive for rich domain objects that know their own capabilities. RequestUrlParser was always meant more as an internal utility for Request (in Java we'd give it package scope). Maybe there's some other way? Polymorphism comes to mind but I'm sure there are other possibilities. We could rethink the interface of a Request domain object to better fit our use cases (here specifically FetchDataUseCase).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some distinct features (such as getRawUrl) that are going to be only used by some fetchers. I take the point about polymorphism - Request could be an abstraction and we could implement RandomRequest or RestRequest. This might however be redundant if there would be 1-to-1 relation between Request and Fetcher implementation. I think we should revisit this after implementing Random Provider, but for now let's leave it as is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have different use cases and some kind of strategy to choose from them? I'm reluctant to accept it the way it is now. By "leaving it as is" you mean to not merge this for now?

Copy link
Collaborator Author

@kss-espeo kss-espeo Jul 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have different use cases and some kind of strategy to choose from them?

I agree. This PR is a first step for achieving this.

By "leaving it as is" you mean to not merge this for now?

Originally I wanted to merge this asap, then merge random provider once it's ready and then write something like a "strategy picker" refactor you mentioned before.
But if you would like me to, I could do it the hard way and design "strategy" picker before merging random provider :) . That way it would be a little harder to write and much harder to review

Copy link
Contributor

@tcichowicz tcichowicz Jul 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's some middle ground between merging this as is and producing a large PR with both the provider and the strategy.

How about we create a RequestExecutorFactory which would create (select?) a use case based on the request. Each executor would return a response given a request. We'd have different request executors:

  1. One that basically does what fetchAndSelectData function from ExecuteReadyRequestsUseCase does - maybe UrlRequestExecutor?
  2. One that can provide a random value based - RandomValueRequestExecutor?

This way we can keep the domain pure and segregate the concerns. UrlRequestExecutor would handle all the URL-related stuff so that the Request does not need to know about it at all.

Copy link
Contributor

@przemyslawjanpietrzak przemyslawjanpietrzak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls merge master to feature branch

.gitignore Outdated Show resolved Hide resolved
import Request from '@core/domain/request/Request';
import Response from '@core/domain/response/Response';

class UrlRequestExecutor implements RequestExecutor {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does RequestExecutor know that it is handling url based request? Maybe moving this knowledge to fetchDataUseCase would be cleaner approach?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed implemented like this. All url-specific stuff happens in Fetcher.

UrlRequestExecutor was just my attemp of naming a group of ['json', 'xml', 'html', 'ipfs'] dataSources. Do you have an idea for a better name?

src/domain/common/port/RequestExecutorPort.ts Outdated Show resolved Hide resolved
selectDataUseCase: SelectDataUseCase,
logger: LoggerPort,
) {
this.requestExecutors = [new UrlRequestExecutor(fetchDataUseCase, selectDataUseCase, logger)];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can move it out of constructor

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why? I think havingrequest executors represent state of this class. It doesn't make sense for RequestExecutorFactory to exist if it doesn't have and RequestExecutors.

Are you referring to some JS standard that I'm not aware of?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, my bad, sorry

}

create(contentType: string): RequestExecutor {
let chosenRequestExecutor = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds like find/some array method ;)

Copy link
Collaborator Author

@kss-espeo kss-espeo Jul 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.requestExecutors.find(re => re.canHandle(contentType)) :) . Surprised by JS, this is done even simpler than in Java.

import Response from '@core/domain/response/Response';

export default interface RequestExecutorPort {
execute(request: Request, response: Response): Promise<Response>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we both accept and return a Response? Is response an in/out param?

Copy link
Collaborator Author

@kss-espeo kss-espeo Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was to conform to existing error handling, but after your comment I got idea on how to design it so that I can remove response param.

async fetch(url): Promise<string> {
import DataFetcher from '@core/domain/common/port/DataFetcherPort';
import { HttpError } from '@core/domain/common/utils/error';
import RequestUrlParser from '@core/domain/request/RequestUrlParser';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like RequestUrlParser is now used in two different places (here and in Response domain object). Should AxiosUrlDataFetcherAdapter internalize this logic instead of calling it?

Copy link
Collaborator Author

@kss-espeo kss-espeo Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. In current state, RequestUrlParser implements some features that are distinct to the UrlRequestExecutor. It will be refactored later, as now we need this in master to deliver random data provider.

this.requestExecutors = [new UrlRequestExecutor(fetchDataUseCase, selectDataUseCase, logger)];
}

create(contentType: string): RequestExecutor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just cosmetic - it's not really a factory but a finder of existing stateless objects. The name just seems a bit misleading when you look at the implementation.

Copy link
Collaborator Author

@kss-espeo kss-espeo Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough ,renaming from Factory to Strategy

tcichowicz
tcichowicz previously approved these changes Jul 22, 2019
private readonly sendResponseToOracleUseCase: SendResponseToOracleUseCase,
private readonly requestRepository: RequestRepositoryPort,
private readonly responseRepository: ResponseRepositoryPort,
private readonly requestExecutorFactory: RequestExecutorStrategy,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming inconsistency: strategy-factory

@kss-espeo kss-espeo merged commit 3ce89ac into master Jul 22, 2019
@kss-espeo kss-espeo deleted the refactor/abstractonise-data-fetchers branch July 22, 2019 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants