-
Notifications
You must be signed in to change notification settings - Fork 6
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
Conversation
src/domain/request/Request.js
Outdated
@@ -11,10 +11,6 @@ class Request { | |||
this.state = state ? new RequestState(state) : RequestStateFactory.createState(validFrom); | |||
} | |||
|
|||
getRawUrl() { |
There was a problem hiding this comment.
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
).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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:
- One that basically does what
fetchAndSelectData
function fromExecuteReadyRequestsUseCase
does - maybeUrlRequestExecutor
? - 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.
There was a problem hiding this 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
…actor/abstractonise-data-fetchers
src/domain/request/requestExecutor/RequestExecutorFactory.spec.ts
Outdated
Show resolved
Hide resolved
import Request from '@core/domain/request/Request'; | ||
import Response from '@core/domain/response/Response'; | ||
|
||
class UrlRequestExecutor implements RequestExecutor { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
…+ other CR changes
selectDataUseCase: SelectDataUseCase, | ||
logger: LoggerPort, | ||
) { | ||
this.requestExecutors = [new UrlRequestExecutor(fetchDataUseCase, selectDataUseCase, logger)]; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 ;)
There was a problem hiding this comment.
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>; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
private readonly sendResponseToOracleUseCase: SendResponseToOracleUseCase, | ||
private readonly requestRepository: RequestRepositoryPort, | ||
private readonly responseRepository: ResponseRepositoryPort, | ||
private readonly requestExecutorFactory: RequestExecutorStrategy, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming inconsistency: strategy-factory
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.