-
Notifications
You must be signed in to change notification settings - Fork 162
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 needed in typesystem resolver #1527
Comments
Here are some thoughts I wanted to share:
Given the points above, my suggestion is simple. We drop the // pkg/server/server.go
package server
func NewServerWithOpts(...) {
s := &Server{...}
...
s.typesystemResolver, s.typesystemResolverStop = s.serverTypesystemResolver()
}
...
// serverTypesystemResolver constructs a [[typesystem.TypesystemResolverFunc]] that
// resolves the underlying TypeSystem given the storeID and modelID and
// it sets some Server response metadata based on the model resolution.
func (s *Server) serverTypesystemResolver() (typesystem.TypesystemResolverFunc, func()) {
memoizedResolver, typesystemResolverStop := typesystem.MemoizedTypesystemResolverFunc(s.datastore)
return func(ctx context.Context, storeID, modelID string) (*typesystem.TypeSystem, error) {
ctx, span := tracer.Start(ctx, "resolveTypesystem")
defer span.End()
typesys, err := memoizedResolver(ctx, storeID, modelID)
if err != nil {
if errors.Is(err, typesystem.ErrModelNotFound) {
if modelID == "" {
return nil, serverErrors.LatestAuthorizationModelNotFound(storeID)
}
return nil, serverErrors.AuthorizationModelNotFound(modelID)
}
if errors.Is(err, typesystem.ErrInvalidModel) {
return nil, serverErrors.ValidationError(err)
}
return nil, serverErrors.HandleError("", err)
}
resolvedModelID := typesys.GetAuthorizationModelID()
span.SetAttributes(attribute.KeyValue{Key: authorizationModelIDKey, Value: attribute.StringValue(resolvedModelID)})
grpc_ctxtags.Extract(ctx).Set(authorizationModelIDKey, resolvedModelID)
s.transport.SetHeader(ctx, AuthorizationModelIDHeader, resolvedModelID)
return typesys, nil
}, typesystemResolverStop
} This way everywhere in our code in the server when we use |
@openfga/backend any thoughts on this? Would appreciate more inputs :) @jon-whit Your suggestion is a good N+1 because it solves the problem I mentioned. The suggestion I made was for N+2 😄
I disagree with this. Packages are meant to put related code close together. The reusability/composabilty aspect is achieved via other means, including but not limited to design patterns such as command.
This is true, but my whole point is that there is nothing preventing us from changing this pattern of "1 api <-> 1 command" to "1 api -> 1 command" :)
We've never explicitly defined what our standards were in this regard. And yes, what I am suggesting is a deviation from all the other commands, in that we wouldn't expose it as an API. But! it would be a very trivial change to do so if we wanted (#1382)
We don't need this split - the only caller of
|
The solution I proposed puts related code closer together in the same defining package. I see no difference. The question is - does this usage pattern/need warrant needing a completely separate abstraction known as a command? You don't need to construct a command every time you need to resolve a model, you just need a function that can be shared across code modules that can resolve the typesystem. That extends well beyond the scope of needing to create a new object/struct in memory every single time you need to resolve a typesystem. You wouldn't create a Server every time you needed to resolve an RPC, so why would we create a Furthermore, if everything that implements business logic is a
I understand where you are coming from. You want to achieve more consistency in the code base, and I think that's a good goal, but the way of achieving that with What we should be prioritizing here is consistent code re-use through shared components. If we're not sharing components then creation of a new object on every request for no shared value is wasteful. Sharing code that resolves a typesystem is good, but that doesn't warrant an independent structure per request to do so.
Because it's not the typesystem's responsibility to understand the Server transport layer. That would leak transport level details in the wrong direction. We wouldn't return HTTP status codes to communicate errors from the storage layer. Why would the typesytem be treated any differently? The typesystem resolves types - it has nothing to do with the Server level transport.
The shared TypesystemResolver pattern described in my previous comment achieves the goal of removing duplicate cache of models as well. Why is a command needed over a shared function? What value does introducing another struct into the mix have over a shared function? |
We have a minor design concern in our code base.
We should never use
server.typesystemResolver
directly within the server handlers, we should always useserver.resolveTypesystem
because that is the one that returns actual server errors (with correct http status codes).I.e. with
typesystemResolver
versus with
resolveTypesystem
The way the code is structured today makes it very easy to make this mistake, as seen in #1516 (comment).
IMO, the typesystem resolver code is better written as a command (e.g. https://github.com/openfga/openfga/pull/1258/files#diff-679325381eda2c29f2d4794339a1efa278a4f65cdebc7514b9f45341c5f59ec8)
The text was updated successfully, but these errors were encountered: