-
Notifications
You must be signed in to change notification settings - Fork 204
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
refactor!: add agent context #881
refactor!: add agent context #881
Conversation
Codecov Report
@@ Coverage Diff @@
## main #881 +/- ##
==========================================
+ Coverage 87.65% 87.78% +0.13%
==========================================
Files 470 473 +3
Lines 11172 11251 +79
Branches 1868 1905 +37
==========================================
+ Hits 9793 9877 +84
+ Misses 1313 1309 -4
+ Partials 66 65 -1
Continue to review full report at Codecov.
|
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 have reviewed the first 36 files and will continue somewhere tomorrow (it is quite a bit).
But I do have a comment and that is about the function signatures. I believe we can make every function signature way more consistent by having a common pattern.
Seeing that most functions now take an AgentContext
it would nice to have something like this:
const afjFunc = (agentContext: AgentContext, parameters: {...}, options?: {...}) => { ... }
// or
const afjFunc = (parameters: {...}, options?: {...}) => { ... }
With this we will always have named parameters and have a nice division between the parameters of a function and the configuration/options we supply to it (like shouldDeleteCredential
or timeOut
). I am interested to hear what you think about this structure and whether it is something we want to implement in AFJ. I think it has value, but it does require a big breaking change.
I am not too sure if we want to also add this to the public api, might be worth discussing.
this.messageSender = messageSender | ||
this.eventEmitter = eventEmitter | ||
this.logger = agentConfig.logger | ||
this.logger = 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.
I am a bit confused by this. Maybe I am missing something about the context, but could we not get the logger from agentContext.logger
?
Checking a bit through this PR it seems a bit "messy" on how we deal with the logger instance. We get it via the injection but it is also in the agentConfig
. I am not too knowledgeable on dependency injection with tsyringe, but is this really the best way to deal with the logger instance?
Also, do we want each agent to have their instance of a logger or do we want to use a RootContainer
(if I understand your design correctly) logger instance?
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.
Yeah that's something I also wasn't sure about, so good that you're calling it out.
It's in the agent config because we configure it in the agent init config. However my thought was that the logger would be shared between the different tenants. I'm not sure if it would work well if we have a lot of logger instances, and there's ways to different context of the different agents in logs.
As mentioned in the design document we should probably look at a way to separate agent config (global, used by all tenants) and tenant config (specific to that tenant).
What would be your preferred approach here?
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.
Yeah I think a global configuration, maybe a Provider/manager agent aswell, and tenant specific configuration would be a good solution.
Quickly looking through the AgentConfig
I do not see a lot that should be globally defined. That being said, I still think it has value for future configuration.
@@ -1,6 +1,7 @@ | |||
// reflect-metadata used for class-transformer + class-validator | |||
import 'reflect-metadata' | |||
|
|||
export { AgentContext } from './agent/AgentContext' |
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 do we want to expose the AgentContext
publicly?
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.
If we want to inject it (in extension modules for example) we need it. So it's not necessary for the public api for users, but is needed if you want to write extension modules.
@@ -164,7 +166,7 @@ export class DidExchangeProtocol { | |||
) | |||
} | |||
|
|||
const didDocument = await this.extractDidDocument(message) | |||
const didDocument = await this.extractDidDocument(messageContext.agentContext, message) |
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 exactly is the agentContext inside the messageContext? Just curious.
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.
Because each message can have their own context. Handlers, services and repositories are now all stateless. The agent context gives the context to process the message. So if we receive a message we'll find the correct tenant, assign the agent context for that tenant to the message context and then process the message.
We could make it a separate parameter, but it kinda made sense to me to tie the agent context to a specific message context. Would you prefer to make it a separate parameter?
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.
Its a difficult one. I think for consistency a seperate agentContext
might be nicer, but I see your point of containing the agentContext inside the messageContext. I am okay with keeping it like this.
packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts
Outdated
Show resolved
Hide resolved
} | ||
|
||
public async findSingleByQuery(query: { did: string; theirDid: string }) { | ||
return this.connectionRepository.findSingleByQuery(query) | ||
public async findByDids(agentContext: AgentContext, query: { ourDid: string; theirDid: string }) { |
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.
Is there a reason why we call it ourDid
? I believe ACA-Py uses my_did
? I have no preference, more just curious.
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.
Not sure, @jakubkoci added this
packages/core/src/modules/connections/services/ConnectionService.ts
Outdated
Show resolved
Hide resolved
packages/core/src/modules/connections/services/ConnectionService.ts
Outdated
Show resolved
Hide resolved
Not 100% sure what you mean by this. This is a pattern already used by some methods, do you mean we should enforce it for all methods? How would we do something like that? I'm also not sure it will work for all methods. It can be difficult sometimes to differentiate between what parameters and what options are (in the case of the agent.credentials.offerCredential({
connectionId: 'the-connection-id',
autoAcceptCredential: AutoAcceptCredential.Always,
credentialFormats: {
/* formats */
}
}) than this: agent.credentials.offerCredential({
connectionId: 'the-connection-id',
credentialFormats: {
/* formats */
}
}, {
autoAcceptCredential: AutoAcceptCredential.Always
}) Anyway it seems this would be better addressed to me in a separate issue, except if this involves moving the |
No the agentContext as first param is a good choice. My point is to get more consistency within the framework. TBH, I do think that the second method with the object for |
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.
Reviewed this time until file: 91. Will continue later today :)
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.
Great work @TimoGlastra! I do think that we should not mark this as refactor
as there are many many changes to functions and the way we use them.
I left a couple of notes but nothing blocking.
We should also merge it probably after the 0.2.0 release.
2be8776
to
fb26a4d
Compare
Signed-off-by: Timo Glastra <[email protected]>
fb26a4d
to
afca7dc
Compare
Superseded by #920 (which is based on 0.3.0-pre instead of main) |
This is a big pull request, but doesn't change any functionality of the agent. All services, repositories etc.. now use agent context so we can start using them as stateless services for multitenancy (as described here: https://hackmd.io/vGLVlxLvQR6jsEEjzNcL8g#Agent-Context).
This doesn't change the public API of the framework, but because of the large number of changes and also the implications for custom modules I've marked this as a breaking change.
Will add more detailed migration docs later on, but generally you have to update your modules to now inject the agent context object and pass that around to services. See the
DummyModule
for an example: https://github.com/TimoGlastra/aries-framework-javascript/blob/fb26a4dfe656b38da984ed46faf5b5a5f3cda1df/samples/extension-module/dummy/DummyModule.ts