-
Notifications
You must be signed in to change notification settings - Fork 1
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
[DO NOT MERGE] POC for getting/updating sessions in different states. #275
base: main
Are you sure you want to change the base?
Conversation
import { BiometricSessionFinished } from "./updateOperations/BiometricSessionFinished"; | ||
import { BiometricTokenIssued } from "./updateOperations/BiometricTokenIssued"; | ||
|
||
describe("Sessions", () => { |
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.
Very clear console.logs produced here, thanks! Wish I had read line 11 a little harder before running the test a few times 😆
if (session.sessionState === SessionState.BIOMETRIC_TOKEN_ISSUED) { | ||
return successResult(session); | ||
} | ||
if (session.sessionState === SessionState.BIOMETRIC_SESSION_FINISHED) { | ||
return successResult(session); | ||
} | ||
if (session.sessionState === SessionState.RESULT_SENT) { | ||
return successResult(session); | ||
} | ||
return successResult(session); |
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 say if it's one of these 3 states return a successResult(session), but if not we still return the same thing. Am I missing a reason why we do this or is this just because it's POC?
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 is needed to appease TypeScript, annoyingly, try removing the conditions and you'll see what I mean. I can't remember the exact reason it happens, but I think essentially if you have a function that returns one (specific) type from several options, every exit point from that function needs to be returning a specific type, rather than returning an object that could be any one of the valid types.
): Promise<Result<Session, ReadSessionError>> { | ||
let output: GetItemCommandOutput; | ||
try { | ||
console.log("Get session attempt"); // replace with proper logging |
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.
In the past we only log by exception (apart from started, completed logs), do you think here there's a good reason to change this?
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.
See the PR description (stray comments) for the reasoning here. tl;dr, yes, I think it's very useful for working with external calls. But not crucial to this POC, definitely something to discuss in more detail elsewhere.
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.
Sorry should have re read that first, thanks for confirming!
backend-api/src/functions/common/session/updateOperations/BiometricSessionFinished.ts
Show resolved
Hide resolved
S: SessionState.BIOMETRIC_SESSION_FINISHED, | ||
}, | ||
":biometricTokenIssued": { S: SessionState.BIOMETRIC_TOKEN_ISSUED }, | ||
} as const; |
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 casting as a const to do with strongly typing here? Or to make it readonly? Or something else entirely?
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.
Honest answer is, I can't remember why we did it on MPT. Doesn't seem to cause any TS issues if it's removed, so I assume it's just to keep it readonly, but I can't see that it's really needed here.
} from "../common/utils/environment"; | ||
import { Result } from "../utils/result"; | ||
|
||
const REQUIRED_ENVIRONMENT_VARIABLES = ["SESSIONS_TABLE"] as const; |
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.
What is the value in asking for the envVars you want like you do here vs having envVars defined for each lambda and just getting all of them?
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 I understand the question - this would be where we define all the env vars for the lambda, it just so happens that this example lambda only has one.
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.
Sorry had misunderstood the getConfigFromEnvironment
. This will actually be a function callable by all lambdas and we just give it the envVars we want. Makes sense
[key in T]: string; | ||
}; | ||
|
||
export const getRequiredEnvironmentVariables = <T extends 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.
Made another comment re envVars, would be good to chat about the general approach here. Looking at this func it feels a bit weird but unsure if it's just because of the generics and typings of stuff look a bit different to me. Not against at all but would be useful for me to chat through
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.
Happy to chat through it any time (and see if it can be improved, too!). The goal was to have a generic function that could take a list of strings for the env vars we want to retrieve, and give back either a type-safe map with all these strings as keys, or an error. This way, the config functions for the individual lambdas only need to specify once which env vars they need. The pursuit of type-safety is why we've had to use all the generics etc - but there may well be a simpler way to do 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.
General Q: What's the thinking behind some filenames being capitalised and some not? If it's intensional that 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.
The thinking was supposed to be - PascalCase for files that export one primary class or interface, with the name matching the name of said class/interface (i.e. BiometricSessionFinished.ts
is where BiometricSessionFinished
comes from). And camelCase for files that export a function (name matching, e.g. exampleHandler
), or for files that export several different entities (e.g. dynamoDBItemToSessionConvertors
).
I don't think I've been consistent here, though - sessionRegistry
should probably be capitalised, for example.
redirectUri?: string; | ||
}; | ||
|
||
export type AuthSessionCreatedSession = CommonSessionKeys & { |
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 pattern is nice
export type BiometricTokenIssuedSession = CommonSessionKeys & { | ||
sessionState: SessionState.BIOMETRIC_TOKEN_ISSUED; | ||
documentType: string; | ||
accessToken: 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.
More for the back of your mind and not related to this spike - we shouldn't save the accessToken in the database - it's a short-lived token and I can't think why we'd ever need 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.
Good point, I hadn't stopped to consider where it would actually be used again. One to update on the TD where it came from I suppose - perhaps as part of a more general review of the session schema?
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! It's on my list to update that TD having spoken to Darren
} | ||
|
||
getDynamoDbConditionExpression(): undefined { | ||
return undefined; |
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 assume this is undefined, given this is just a spike?
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.
Or is this because we've already fetched the session record earlier in the lambda and we know in the lambda itself whether we want to make an update operation or not?
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.
The second one! Saves a redundant read-before-write.
sessionState: SessionState, | ||
): Promise<Result<Session, ReadSessionError>>; | ||
|
||
// Ideally the return value of this would be more dynamo-agnostic, to keep it in this interface |
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 - I agree, and I'd prefer this. This interface would ideally return the AuthSessionCreatedSession I think?
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.
Yep that's what I was thinking
| BiometricSessionFinishedSession | ||
| ResultSentSession; | ||
|
||
export interface IUpdateSessionOperation { |
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'm just mulling (or marinading as Elishka would say) on this one. I like how the lambda decides what implementation of the operations to follow.
Perhaps it's just the location of this interface! It would be good to think about partitioning (even if it's by folder structure) as much of the Dynamo implementation we can from the business logic/interfaces you've written that are so clean.
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.
Definitely happy to move the interface elsewhere.
Something I considered was having the update operations just know about (and be able to tell you about) their valid 'FROM' session states and target session state, as well as what other fields need adding, in a dynamo-agnostic way. I just couldn't think of a neat way to work that list bit in in the time I'd taken on it. But will keep marinading.
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's possible that avenue might add a bit more abstraction than we really need, but yes if you can think of an improvement by all means go for it.
If the lambda directly referenced this interface, I'd be much more hesitant about the solution you've proposed.
this.tableName = tableName; | ||
} | ||
|
||
async getSessionWithState( |
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've tried to play with a mapped type for this but probably hit similar pitfalls to what you've tried historically! If it works, it works.
Rationale
For the async work we'll soon need to work with async sessions in multiple states, for which it will pay to have a pattern that's type safe and easy to extend/modify.
Some objectives:
A lot of this is adapted from patterns used in the STS codebase, where they've seen some success.
Where to start
I'd recommend:
Overview of new code
getSessionWithState
attempts to get a session from dynamo that's expected to be in a particular state.updateSession
performs a particular update operation to a pre-existing sessionStray comments