diff --git a/src/Grace.Actors/Extensions/MemoryCache.Extensions.Actor.fs b/src/Grace.Actors/Extensions/MemoryCache.Extensions.Actor.fs new file mode 100644 index 0000000..2ae9048 --- /dev/null +++ b/src/Grace.Actors/Extensions/MemoryCache.Extensions.Actor.fs @@ -0,0 +1,136 @@ +namespace Grace.Actors.Extensions + +open Grace.Shared.Constants +open Grace.Shared.Types +open Microsoft.Extensions.Caching.Memory +open System + +module MemoryCache = + [] + let ownerIdPrefix = "OwI" + [] + let organizationIdPrefix = "OrI" + [] + let repositoryIdPrefix = "ReI" + [] + let branchIdPrefix = "BrI" + [] + let ownerNamePrefix = "OwN" + [] + let organizationNamePrefix = "OrN" + [] + let repositoryNamePrefix = "ReN" + [] + let branchNamePrefix = "BrN" + + type Microsoft.Extensions.Caching.Memory.IMemoryCache with + + /// Create a new entry in MemoryCache with a default expiration time. + member this.CreateWithDefaultExpirationTime (key: string) value = + use newCacheEntry = this.CreateEntry(key, Value = value, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + () + + /// Create a new entry in MemoryCache to confirm that an OwnerId exists. + member this.CreateOwnerIdEntry (ownerId: OwnerId) (value: string) = + this.CreateWithDefaultExpirationTime $"{ownerIdPrefix}:{ownerId}" value + + /// Create a new entry in MemoryCache to confirm that an OrganizationId exists. + member this.CreateOrganizationIdEntry (organizationId: OrganizationId) (value: string) = + this.CreateWithDefaultExpirationTime $"{organizationIdPrefix}:{organizationId}" value + + /// Create a new entry in MemoryCache to confirm that a RepositoryId exists. + member this.CreateRepositoryIdEntry (repositoryId: RepositoryId) (value: string) = + this.CreateWithDefaultExpirationTime $"{repositoryIdPrefix}:{repositoryId}" value + + /// Create a new entry in MemoryCache to confirm that a BranchId exists. + member this.CreateBranchIdEntry (branchId: BranchId) (value: string) = + this.CreateWithDefaultExpirationTime $"{branchIdPrefix}:{branchId}" value + + /// Create a new entry in MemoryCache to link an OwnerName with an OwnerId. + member this.CreateOwnerNameEntry (ownerName: OwnerName) (ownerId: OwnerId) = + this.CreateWithDefaultExpirationTime $"{ownerNamePrefix}:{ownerName}" ownerId + + /// Create a new entry in MemoryCache to link an OrganizationName with an OrganizationId. + member this.CreateOrganizationNameEntry (organizationName: OrganizationName) (organizationId: OrganizationId) = + this.CreateWithDefaultExpirationTime $"{organizationNamePrefix}:{organizationName}" organizationId + + /// Create a new entry in MemoryCache to link a RepositoryName with a RepositoryId. + member this.CreateRepositoryNameEntry(repositoryName: RepositoryName) (repositoryId: RepositoryId) = + this.CreateWithDefaultExpirationTime $"{repositoryNamePrefix}:{repositoryName}" repositoryId + + /// Create a new entry in MemoryCache to link a BranchName with a BranchId. + member this.CreateBranchNameEntry(branchName: string) (branchId: BranchId) = + this.CreateWithDefaultExpirationTime $"{branchNamePrefix}:{branchName}" branchId + + /// Get a value from MemoryCache, if it exists. + member this.GetFromCache<'T> (key: string) = + let mutable value = Unchecked.defaultof<'T> + if this.TryGetValue(key, &value) then + Some value + else + None + + /// Check if we have an entry in MemoryCache for an OwnerId, and return the OwnerId if we have it. + member this.GetOwnerIdEntry(ownerId: OwnerId) = + this.GetFromCache $"{ownerIdPrefix}:{ownerId}" + + /// Check if we have an entry in MemoryCache for an OrganizationId, and return the OrganizationId if we have it. + member this.GetOrganizationIdEntry(organizationId: OrganizationId) = + this.GetFromCache $"{organizationIdPrefix}:{organizationId}" + + /// Check if we have an entry in MemoryCache for a RepositoryId, and return the RepositoryId if we have it. + member this.GetRepositoryIdEntry(repositoryId: RepositoryId) = + this.GetFromCache $"{repositoryIdPrefix}:{repositoryId}" + + /// Check if we have an entry in MemoryCache for a BranchId, and return the BranchId if we have it. + member this.GetBranchIdEntry(branchId: BranchId) = + this.GetFromCache $"{branchIdPrefix}:{branchId}" + + /// Check if we have an entry in MemoryCache for an OwnerName, and return the OwnerId if we have it. + member this.GetOwnerNameEntry(ownerName: string) = + this.GetFromCache $"{ownerNamePrefix}:{ownerName}" + + /// Check if we have an entry in MemoryCache for an OrganizationName, and return the OrganizationId if we have it. + member this.GetOrganizationNameEntry(organizationName: string) = + this.GetFromCache $"{organizationNamePrefix}:{organizationName}" + + /// Check if we have an entry in MemoryCache for a RepositoryName, and return the RepositoryId if we have it. + member this.GetRepositoryNameEntry(repositoryName: string) = + this.GetFromCache $"{repositoryNamePrefix}:{repositoryName}" + + /// Check if we have an entry in MemoryCache for a BranchName, and return the BranchId if we have it. + member this.GetBranchNameEntry(branchName: string) = + this.GetFromCache $"{branchNamePrefix}:{branchName}" + + + /// Remove an entry in MemoryCache for an OwnerId. + member this.RemoveOwnerIdEntry(ownerId: OwnerId) = + this.Remove($"{ownerIdPrefix}:{ownerId}") + + /// Remove an entry in MemoryCache for an OrganizationId. + member this.RemoveOrganizationIdEntry(organizationId: OrganizationId) = + this.Remove($"{organizationIdPrefix}:{organizationId}") + + /// Remove an entry in MemoryCache for a RepositoryId. + member this.RemoveRepositoryIdEntry(repositoryId: RepositoryId) = + this.Remove($"{repositoryIdPrefix}:{repositoryId}") + + /// Remove an entry in MemoryCache for a BranchId. + member this.RemoveBranchIdEntry(branchId: BranchId) = + this.Remove($"{branchIdPrefix}:{branchId}") + + /// Remove an entry in MemoryCache for an OwnerName. + member this.RemoveOwnerNameEntry(ownerName: string) = + this.Remove($"{ownerNamePrefix}:{ownerName}") + + /// Remove an entry in MemoryCache for an OrganizationName. + member this.RemoveOrganizationNameEntry(organizationName: string) = + this.Remove($"{organizationNamePrefix}:{organizationName}") + + /// Remove an entry in MemoryCache for a RepositoryName. + member this.RemoveRepositoryNameEntry(repositoryName: string) = + this.Remove($"{repositoryNamePrefix}:{repositoryName}") + + /// Remove an entry in MemoryCache for a BranchName. + member this.RemoveBranchNameEntry(branchName: string) = + this.Remove($"{branchNamePrefix}:{branchName}") diff --git a/src/Grace.Actors/Grace.Actors.fsproj b/src/Grace.Actors/Grace.Actors.fsproj index 14383d9..ce0484c 100644 --- a/src/Grace.Actors/Grace.Actors.fsproj +++ b/src/Grace.Actors/Grace.Actors.fsproj @@ -15,6 +15,7 @@ + diff --git a/src/Grace.Actors/Owner.Actor.fs b/src/Grace.Actors/Owner.Actor.fs index af14434..53410b4 100644 --- a/src/Grace.Actors/Owner.Actor.fs +++ b/src/Grace.Actors/Owner.Actor.fs @@ -4,8 +4,9 @@ open Dapr.Actors open Dapr.Actors.Runtime open FSharp.Control open Grace.Actors.Commands.Owner -open Grace.Actors.Services open Grace.Actors.Events.Owner +open Grace.Actors.Extensions.MemoryCache +open Grace.Actors.Services open Grace.Actors.Interfaces open Grace.Shared open Grace.Shared.Constants @@ -342,13 +343,12 @@ module Owner = // Clear the OwnerNameActor for the old name. let ownerNameActor = actorProxyFactory.CreateActorProxy(ActorId(ownerDto.OwnerName), ActorName.OwnerName) do! ownerNameActor.ClearOwnerId metadata.CorrelationId - memoryCache.Remove($"OwN:{ownerDto.OwnerName}") + memoryCache.RemoveOwnerNameEntry ownerDto.OwnerName // Set the OwnerNameActor for the new name. let ownerNameActor = actorProxyFactory.CreateActorProxy(ActorId(newName), ActorName.OwnerName) do! ownerNameActor.SetOwnerId ownerDto.OwnerId metadata.CorrelationId - use newCacheEntry = - memoryCache.CreateEntry($"OwN:{newName}", Value = ownerDto.OwnerId, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + memoryCache.CreateOwnerNameEntry newName ownerDto.OwnerId return Ok(OwnerEventType.NameSet newName) | OwnerCommand.SetType ownerType -> return Ok(OwnerEventType.TypeSet ownerType) diff --git a/src/Grace.Actors/Services.Actor.fs b/src/Grace.Actors/Services.Actor.fs index d216aae..14b7f3e 100644 --- a/src/Grace.Actors/Services.Actor.fs +++ b/src/Grace.Actors/Services.Actor.fs @@ -7,6 +7,7 @@ open Dapr.Actors open Dapr.Actors.Client open Dapr.Client open Grace.Actors.Constants +open Grace.Actors.Extensions.MemoryCache open Grace.Actors.Events open Grace.Actors.Interfaces open Grace.Shared @@ -35,6 +36,7 @@ open System.Text open System.Threading.Tasks open System.Threading open System +open Microsoft.Extensions.DependencyInjection module Services = type ServerGraceIndex = Dictionary @@ -78,7 +80,7 @@ module Services = let setActorProxyFactory proxyFactory = actorProxyFactory <- proxyFactory /// Getter for actor proxy factory - let ActorProxyFactory () = actorProxyFactory + let ActorProxyFactory() = actorProxyFactory /// Actor state storage provider instance let mutable actorStateStorageProvider: ActorStateStorageProvider = ActorStateStorageProvider.Unknown @@ -98,8 +100,14 @@ module Services = /// Setter for Cosmos container let setCosmosContainer (container: Container) = cosmosContainer <- container + /// Host services collection + let mutable internal hostServiceProvider: IServiceProvider = null + + /// Setter for services collection + let setHostServiceProvider (hostServices: IServiceProvider) = hostServiceProvider <- hostServices + /// Logger factory instance - let mutable internal loggerFactory: ILoggerFactory = null + let mutable loggerFactory : ILoggerFactory = null //hostServiceProvider.GetService(typeof) :?> ILoggerFactory /// Setter for logger factory let setLoggerFactory (factory: ILoggerFactory) = loggerFactory <- factory @@ -134,6 +142,16 @@ module Services = queryRequestOptions.PopulateIndexMetrics <- true #endif + /// Adds the parameters from the API call to a GraceReturnValue instance. + let addParametersToGraceReturnValue<'T> parameters (graceReturnValue: GraceReturnValue<'T>) = + (getParametersAsDictionary parameters) |> Seq.iter (fun kvp -> graceReturnValue.enhance(kvp.Key, kvp.Value) |> ignore) + graceReturnValue + + /// Adds the parameters from the API call to a GraceError instance. + let addParametersToGraceError parameters (graceError: GraceError) = + (getParametersAsDictionary parameters) |> Seq.iter (fun kvp -> graceError.enhance(kvp.Key, kvp.Value) |> ignore) + graceError + /// Gets a CosmosDB container client for the given container. let getContainerClient (storageAccountName: StorageAccountName) (containerName: StorageContainerName) = task { @@ -146,7 +164,7 @@ module Services = else let blobContainerClient = BlobContainerClient(azureStorageConnectionString, $"{containerName}") let! azureResponse = blobContainerClient.CreateIfNotExistsAsync(publicAccessType = Models.PublicAccessType.None) - let memoryCacheEntryOptions = MemoryCacheEntryOptions(SlidingExpiration = TimeSpan.FromMinutes(15.0)) + let memoryCacheEntryOptions = MemoryCacheEntryOptions(AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15.0)) return memoryCache.Set(key, blobContainerClient, memoryCacheEntryOptions) } @@ -293,11 +311,8 @@ module Services = else // Add this OwnerName and OwnerId to the MemoryCache. ownerGuid <- Guid.Parse(ownerId) - - use newCacheEntry1 = - memoryCache.CreateEntry($"OwN:{ownerName}", Value = ownerGuid, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) - - use newCacheEntry2 = memoryCache.CreateEntry(ownerGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + memoryCache.CreateOwnerNameEntry ownerName ownerGuid + memoryCache.CreateOwnerIdEntry ownerGuid MemoryCache.ExistsValue // Set the OwnerId in the OwnerName actor. do! ownerNameActorProxy.SetOwnerId ownerGuid correlationId @@ -313,14 +328,8 @@ module Services = task { let mutable ownerGuid = Guid.Empty - if not <| String.IsNullOrEmpty(ownerId) && Guid.TryParse(ownerId, &ownerGuid) then - // Check if we have this owner id in MemoryCache. - let exists = memoryCache.Get(ownerGuid) - - match exists with - | MemoryCache.ExistsValue -> return Some ownerId - | MemoryCache.DoesNotExistValue -> return None - | _ -> + let ownerExists ownerId = + task { // Call the Owner actor to check if the owner exists. let ownerActorProxy = actorProxyFactory.CreateActorProxy(ActorId(ownerId), ActorName.Owner) @@ -328,36 +337,42 @@ module Services = if exists then // Add this OwnerId to the MemoryCache. - use newCacheEntry = memoryCache.CreateEntry(ownerGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + memoryCache.CreateOwnerIdEntry ownerGuid MemoryCache.ExistsValue return Some ownerId else return None + } + + if not <| String.IsNullOrEmpty(ownerId) && Guid.TryParse(ownerId, &ownerGuid) then + // Check if we have this owner id in MemoryCache. + match memoryCache.GetOwnerIdEntry ownerGuid with + | Some value -> + match value with + | MemoryCache.ExistsValue -> return Some ownerId + | MemoryCache.DoesNotExistValue -> return None + | _ -> return! ownerExists ownerId + | None -> + return! ownerExists ownerId elif String.IsNullOrEmpty(ownerName) then // We have no OwnerId or OwnerName to resolve. return None else // Check if we have this owner name in MemoryCache. - let cached = memoryCache.TryGetValue($"OwN:{ownerName}", &ownerGuid) - - if ownerGuid.Equals(MemoryCache.EntityDoesNotExist) then - // We have already checked and the owner does not exist. - return None - elif cached then + match memoryCache.GetOwnerNameEntry ownerName with + | Some ownerGuid -> // We have already checked and the owner exists. - return Some (ownerGuid.ToString()) - else + memoryCache.CreateOwnerIdEntry ownerGuid MemoryCache.ExistsValue + return Some $"{ownerGuid}" + | None -> // Check if we have an active OwnerName actor with a cached result. let ownerNameActorId = getOwnerNameActorId ownerName let ownerNameActorProxy = actorProxyFactory.CreateActorProxy(ownerNameActorId, ActorName.OwnerName) match! ownerNameActorProxy.GetOwnerId correlationId with | Some ownerId -> - // Add this OwnerName to the MemoryCache. - use newCacheEntry = - memoryCache.CreateEntry($"OwN:{ownerName}", Value = ownerId, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) - - // Add the OwnerId to the MemoryCache. - use newCacheEntry2 = memoryCache.CreateEntry(ownerGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + // Add this OwnerName and OwnerId to the MemoryCache. + memoryCache.CreateOwnerNameEntry ownerName ownerId + memoryCache.CreateOwnerIdEntry ownerGuid MemoryCache.ExistsValue return Some $"{ownerId}" | None -> @@ -365,8 +380,11 @@ module Services = if nameExists then // We have already checked and the owner exists. - let cached = memoryCache.TryGetValue($"OwN:{ownerName}", &ownerGuid) - return Some $"{ownerGuid}" + match memoryCache.GetOwnerNameEntry ownerName with + | Some ownerGuid -> + return Some $"{ownerGuid}" + | None -> // This should never happen, because we just populated the cache in nameExists. + return None else // The owner name does not exist. return None @@ -377,14 +395,8 @@ module Services = task { let mutable organizationGuid = Guid.Empty - if not <| String.IsNullOrEmpty(organizationId) - && Guid.TryParse(organizationId, &organizationGuid) - then - let exists = memoryCache.Get(organizationGuid) - match exists with - | MemoryCache.ExistsValue -> return Some organizationId - | MemoryCache.DoesNotExistValue -> return None - | _ -> + let organizationExists organizationId = + task { // Call the Organization actor to check if the organization exists. let actorProxy = actorProxyFactory.CreateActorProxy(ActorId(organizationId), ActorName.Organization) @@ -392,24 +404,36 @@ module Services = if exists then // Add this OrganizationId to the MemoryCache. - use newCacheEntry = memoryCache.CreateEntry(organizationGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) - () + memoryCache.CreateOrganizationIdEntry organizationGuid MemoryCache.ExistsValue + return Some organizationId + else + return None + } - if exists then return Some organizationId else return None + if not <| String.IsNullOrEmpty(organizationId) + && Guid.TryParse(organizationId, &organizationGuid) + then + match memoryCache.GetOrganizationIdEntry organizationGuid with + | Some value -> + match value with + | MemoryCache.ExistsValue -> return Some organizationId + | MemoryCache.DoesNotExistValue -> return None + | _ -> return! organizationExists organizationId + | None -> return! organizationExists organizationId elif String.IsNullOrEmpty(organizationName) then // We have no OrganizationId or OrganizationName to resolve. return None else // Check if we have this organization name in MemoryCache. - let cached = memoryCache.TryGetValue($"OrN:{organizationName}", &organizationGuid) - - if organizationGuid.Equals(Constants.MemoryCache.EntityDoesNotExist) then - // We have already checked and the organization does not exist. - return None - elif cached then - // We have already checked and the organization exists. - return Some $"{organizationGuid}" - else + match memoryCache.GetOrganizationNameEntry organizationName with + | Some organizationGuid -> + if organizationGuid.Equals(MemoryCache.EntityDoesNotExistGuid) then + // We have already checked and the organization does not exist. + return None + else + memoryCache.CreateOrganizationIdEntry organizationGuid MemoryCache.ExistsValue + return Some $"{organizationGuid}" + | None -> // Check if we have an active OrganizationName actor with a cached result. let organizationNameActorProxy = actorProxyFactory.CreateActorProxy( @@ -418,7 +442,11 @@ module Services = ) match! organizationNameActorProxy.GetOrganizationId correlationId with - | Some ownerId -> return Some $"{ownerId}" + | Some organizationId -> + // Add this OrganizationName and OrganizationId to the MemoryCache. + memoryCache.CreateOrganizationNameEntry organizationName organizationId + memoryCache.CreateOrganizationIdEntry organizationId MemoryCache.ExistsValue + return Some $"{organizationId}" | None -> // We have to call into Actor storage to get the OrganizationId. match actorStateStorageProvider with @@ -444,27 +472,13 @@ module Services = if String.IsNullOrEmpty(organizationId) then // We didn't find the OrganizationId, so add this OrganizationName to the MemoryCache and indicate that we have already checked. - use newCacheEntry = - memoryCache.CreateEntry( - $"OrN:{organizationName}", - Value = MemoryCache.EntityDoesNotExist, - AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime - ) - + memoryCache.CreateOrganizationNameEntry organizationName MemoryCache.EntityDoesNotExistGuid return None else // Add this OrganizationName and OrganizationId to the MemoryCache. organizationGuid <- Guid.Parse(organizationId) - - use newCacheEntry1 = - memoryCache.CreateEntry( - $"OrN:{organizationName}", - Value = organizationGuid, - AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime - ) - - use newCacheEntry2 = - memoryCache.CreateEntry(organizationGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + memoryCache.CreateOrganizationNameEntry organizationName organizationGuid + memoryCache.CreateOrganizationIdEntry organizationGuid MemoryCache.ExistsValue do! organizationNameActorProxy.SetOrganizationId organizationId correlationId return Some organizationId @@ -486,16 +500,8 @@ module Services = task { let mutable repositoryGuid = Guid.Empty - if not <| String.IsNullOrEmpty(repositoryId) - && Guid.TryParse(repositoryId, &repositoryGuid) - then - let exists = memoryCache.Get(repositoryGuid) - //logToConsole $"In resolveRepositoryId: correlationId: {correlationId}; repositoryId: {repositoryGuid}; exists: {exists}." - - match exists with - | MemoryCache.ExistsValue -> return Some repositoryId - | MemoryCache.DoesNotExistValue -> return None - | _ -> + let repositoryExists repositoryId = + task { // Call the Repository actor to check if the repository exists. let actorProxy = actorProxyFactory.CreateActorProxy(ActorId(repositoryId), ActorName.Repository) @@ -503,24 +509,36 @@ module Services = if exists then // Add this RepositoryId to the MemoryCache. - //logToConsole $"In resolveRepositoryId: creating cache entry; correlationId: {correlationId}; repositoryId: {repositoryGuid}; exists: {MemoryCache.ExistsValue}." - use newCacheEntry = memoryCache.CreateEntry(repositoryGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + memoryCache.CreateRepositoryIdEntry repositoryGuid MemoryCache.ExistsValue return Some repositoryId else return None + } + + if not <| String.IsNullOrEmpty(repositoryId) + && Guid.TryParse(repositoryId, &repositoryGuid) + then + match memoryCache.GetRepositoryIdEntry repositoryGuid with + | Some value -> + match value with + | MemoryCache.ExistsValue -> return Some repositoryId + | MemoryCache.DoesNotExistValue -> return None + | _ -> return! repositoryExists repositoryId + | None -> return! repositoryExists repositoryId elif String.IsNullOrEmpty(repositoryName) then // We don't have a RepositoryId or RepositoryName, so we can't resolve the RepositoryId. return None else - let cached = memoryCache.TryGetValue($"ReN:{repositoryName}", &repositoryGuid) - - if repositoryGuid.Equals(Constants.MemoryCache.EntityDoesNotExist) then - // We have already checked and the repository does not exist. - return None - elif cached then - // We have already checked and the repository exists. - return Some(repositoryGuid.ToString()) - else + match memoryCache.GetRepositoryNameEntry repositoryName with + | Some repositoryGuid -> + if repositoryGuid.Equals(Constants.MemoryCache.EntityDoesNotExist) then + // We have already checked and the repository does not exist. + return None + else + // We have already checked and the repository exists. + memoryCache.CreateRepositoryIdEntry repositoryGuid MemoryCache.ExistsValue + return Some $"{repositoryGuid}" + | None -> // Check if we have an active RepositoryName actor with a cached result. let repositoryNameActorProxy = actorProxyFactory.CreateActorProxy( @@ -529,7 +547,11 @@ module Services = ) match! repositoryNameActorProxy.GetRepositoryId correlationId with - | Some repositoryId -> return Some repositoryId + | Some repositoryId -> + repositoryGuid <- Guid.Parse(repositoryId) + memoryCache.CreateRepositoryNameEntry repositoryName repositoryGuid + memoryCache.CreateRepositoryIdEntry repositoryGuid MemoryCache.ExistsValue + return Some repositoryId | None -> // We have to call into Actor storage to get the RepositoryId. match actorStateStorageProvider with @@ -553,27 +575,14 @@ module Services = if String.IsNullOrEmpty(repositoryId) then // We didn't find the RepositoryId, so add this RepositoryName to the MemoryCache and indicate that we have already checked. - use newCacheEntry = - memoryCache.CreateEntry( - $"ReN:{repositoryName}", - Value = MemoryCache.EntityDoesNotExist, - AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime - ) - + memoryCache.CreateRepositoryNameEntry repositoryName MemoryCache.EntityDoesNotExistGuid return None else // Add this RepositoryName and RepositoryId to the MemoryCache. repositoryGuid <- Guid.Parse(repositoryId) + memoryCache.CreateRepositoryNameEntry repositoryName repositoryGuid + memoryCache.CreateRepositoryIdEntry repositoryGuid MemoryCache.ExistsValue - use newCacheEntry1 = - memoryCache.CreateEntry( - $"ReN:{repositoryName}", - Value = repositoryGuid, - AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime - ) - - use newCacheEntry2 = - memoryCache.CreateEntry(repositoryGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) // Set the RepositoryId in the RepositoryName actor. do! repositoryNameActorProxy.SetRepositoryId repositoryId correlationId return Some repositoryId @@ -587,50 +596,51 @@ module Services = task { let mutable branchGuid = Guid.Empty - if not <| String.IsNullOrEmpty(branchId) && Guid.TryParse(branchId, &branchGuid) then - let exists = memoryCache.Get(branchGuid) - - match exists with - | MemoryCache.ExistsValue -> return Some branchId - | MemoryCache.DoesNotExistValue -> return None - | _ -> + let branchExists branchId = + task { // Call the Branch actor to check if the branch exists. let branchActorProxy = actorProxyFactory.CreateActorProxy(ActorId(branchId), ActorName.Branch) let! exists = branchActorProxy.Exists correlationId if exists then // Add this BranchId to the MemoryCache. - use newCacheEntry = memoryCache.CreateEntry(branchGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + memoryCache.CreateBranchIdEntry branchGuid MemoryCache.ExistsValue return Some branchId else return None + } + + if not <| String.IsNullOrEmpty(branchId) && Guid.TryParse(branchId, &branchGuid) then + match memoryCache.GetBranchIdEntry branchGuid with + | Some value -> + match value with + | MemoryCache.ExistsValue -> return Some branchId + | MemoryCache.DoesNotExistValue -> return None + | _ -> return! branchExists branchId + | None -> return! branchExists branchId elif String.IsNullOrEmpty(branchName) then // We don't have a BranchId or BranchName, so we can't resolve the BranchId. return None else // Check if we have an active BranchName actor with a cached result. - let cached = memoryCache.TryGetValue($"BrN:{branchName}", &branchGuid) - - if branchGuid.Equals(Constants.MemoryCache.EntityDoesNotExist) then - // We have already checked and the branch does not exist. - logToConsole $"We have already checked and the branch does not exist. BranchName: {branchName}; BranchId: none." - return None - elif cached then - // We have already checked and the branch exists. - logToConsole $"We have already checked and the branch exists. BranchId: {branchGuid}." - return Some $"{branchGuid}" - else + match memoryCache.GetBranchNameEntry branchName with + | Some branchGuid -> + if branchGuid.Equals(Constants.MemoryCache.EntityDoesNotExist) then + // We have already checked and the branch does not exist. + return None + else + // We have already checked and the branch exists. + return Some $"{branchGuid}" + | None -> // Check if we have an active BranchName actor with a cached result. let branchNameActorProxy = actorProxyFactory.CreateActorProxy(getBranchNameActorId repositoryId branchName, ActorName.BranchName) match! branchNameActorProxy.GetBranchId correlationId with | Some branchId -> - // Add this BranchName to the MemoryCache. - use newCacheEntry = - memoryCache.CreateEntry($"BrN:{branchName}", Value = branchId, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) - - logToConsole $"BranchName actor was already active. BranchId: {branchId}." + // Add this BranchName and BranchId to the MemoryCache. + memoryCache.CreateBranchNameEntry branchName branchId + memoryCache.CreateBranchIdEntry branchId MemoryCache.ExistsValue return Some $"{branchId}" | None -> // We have to call into Actor storage to get the BranchId. @@ -665,15 +675,9 @@ module Services = // Add this BranchName and BranchId to the MemoryCache. branchGuid <- Guid.Parse(branchId) - use newCacheEntry1 = - memoryCache.CreateEntry( - $"BrN:{branchName}", - Value = branchGuid, - AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime - ) - - use newCacheEntry2 = - memoryCache.CreateEntry(branchGuid, Value = MemoryCache.ExistsValue, AbsoluteExpirationRelativeToNow = MemoryCache.DefaultExpirationTime) + // Add this BranchName and BranchId to the MemoryCache. + memoryCache.CreateBranchNameEntry branchName branchGuid + memoryCache.CreateBranchIdEntry branchGuid MemoryCache.ExistsValue // Set the BranchId in the BranchName actor. do! branchNameActorProxy.SetBranchId branchGuid correlationId diff --git a/src/Grace.CLI/Command/Branch.CLI.fs b/src/Grace.CLI/Command/Branch.CLI.fs index 461daa1..6ecd9f8 100644 --- a/src/Grace.CLI/Command/Branch.CLI.fs +++ b/src/Grace.CLI/Command/Branch.CLI.fs @@ -1986,13 +1986,14 @@ module Branch = let! uploadDirectoryVersions = Directory.SaveDirectoryVersions saveParameters - if true then + match! Directory.SaveDirectoryVersions saveParameters with + | Ok returnValue -> t |> setProgressTaskValue showOutput 100.0 return Ok(showOutput, parseResult, parameters, currentBranch, $"Save created prior to branch switch.") - else + | Error error -> t |> setProgressTaskValue showOutput 50.0 - return Error GraceError.Default + return Error error else t |> setProgressTaskValue showOutput 100.0 diff --git a/src/Grace.CLI/Command/Owner.CLI.fs b/src/Grace.CLI/Command/Owner.CLI.fs index ba53acb..a6dbd78 100644 --- a/src/Grace.CLI/Command/Owner.CLI.fs +++ b/src/Grace.CLI/Command/Owner.CLI.fs @@ -287,7 +287,7 @@ module Owner = | Ok returnValue -> // Update the Grace configuration file with the new Owner name. let newConfig = Current() - newConfig.OwnerName <- returnValue.Properties[setNameParameters.NewName] + newConfig.OwnerName <- setNameParameters.NewName updateConfiguration newConfig | Error _ -> () t0.Increment(100.0) diff --git a/src/Grace.CLI/Command/Services.CLI.fs b/src/Grace.CLI/Command/Services.CLI.fs index 9dd76bd..a38bcf4 100644 --- a/src/Grace.CLI/Command/Services.CLI.fs +++ b/src/Grace.CLI/Command/Services.CLI.fs @@ -670,7 +670,7 @@ module Services = let graceError = GraceError.Create (StorageError.getErrorMessage StorageError.FailedUploadingFilesToObjectStorage) correlationId - return Error graceError |> enhance ("Errors", errorMessage) + return Error graceError |> enhance "Errors" errorMessage | Error error -> return Error error else return Ok(GraceReturnValue.Create true correlationId) diff --git a/src/Grace.SDK/Common.SDK.fs b/src/Grace.SDK/Common.SDK.fs index edfc48b..30c5206 100644 --- a/src/Grace.SDK/Common.SDK.fs +++ b/src/Grace.SDK/Common.SDK.fs @@ -105,13 +105,13 @@ module Common = return Ok graceReturn - |> enhance ("ServerResponseTime", $"{(endTime - startTime).TotalMilliseconds:F3} ms") + |> enhance "ServerResponseTime" $"{(endTime - startTime).TotalMilliseconds:F3} ms" else let! graceError = response.Content.ReadFromJsonAsync(Constants.JsonSerializerOptions) return Error graceError - |> enhance ("ServerResponseTime", $"{(endTime - startTime).TotalMilliseconds:F3} ms") + |> enhance "ServerResponseTime" $"{(endTime - startTime).TotalMilliseconds:F3} ms" with ex -> let exceptionResponse = Utilities.createExceptionResponse ex return Error(GraceError.Create (serialize exceptionResponse) parameters.CorrelationId) @@ -142,7 +142,7 @@ module Common = //let graceReturnValue = JsonSerializer.Deserialize>(blah, Constants.JsonSerializerOptions) return Ok graceReturnValue - |> enhance ("ServerResponseTime", $"{(endTime - startTime).TotalMilliseconds:F3} ms") + |> enhance "ServerResponseTime" $"{(endTime - startTime).TotalMilliseconds:F3} ms" else if response.StatusCode = HttpStatusCode.NotFound then return Error(GraceError.Create $"Server endpoint {route} not found." parameters.CorrelationId) elif response.StatusCode = HttpStatusCode.BadRequest then @@ -150,8 +150,8 @@ module Common = return Error (GraceError.Create $"{errorMessage}" parameters.CorrelationId) - |> enhance ("ServerResponseTime", $"{(endTime - startTime).TotalMilliseconds:F3} ms") - |> enhance ("StatusCode", $"{response.StatusCode}") + |> enhance "ServerResponseTime" $"{(endTime - startTime).TotalMilliseconds:F3} ms" + |> enhance "StatusCode" $"{response.StatusCode}" else let! responseAsString = response.Content.ReadAsStringAsync() @@ -160,13 +160,13 @@ module Common = return Error graceError - |> enhance ("ServerResponseTime", $"{(endTime - startTime).TotalMilliseconds:F3} ms") - |> enhance ("StatusCode", $"{response.StatusCode}") + |> enhance "ServerResponseTime" $"{(endTime - startTime).TotalMilliseconds:F3} ms" + |> enhance "StatusCode" $"{response.StatusCode}" with ex -> return Error(GraceError.Create $"{responseAsString}" parameters.CorrelationId) - |> enhance ("ServerResponseTime", $"{(endTime - startTime).TotalMilliseconds:F3} ms") - |> enhance ("StatusCode", $"{response.StatusCode}") + |> enhance "ServerResponseTime" $"{(endTime - startTime).TotalMilliseconds:F3} ms" + |> enhance "StatusCode" $"{response.StatusCode}" with ex -> let exceptionResponse = Utilities.createExceptionResponse ex return Error (GraceError.Create ($"{exceptionResponse}") parameters.CorrelationId) diff --git a/src/Grace.SDK/Storage.SDK.fs b/src/Grace.SDK/Storage.SDK.fs index 9f1e2a3..312e3e1 100644 --- a/src/Grace.SDK/Storage.SDK.fs +++ b/src/Grace.SDK/Storage.SDK.fs @@ -123,7 +123,7 @@ module Storage = for fileVersion in fileVersions do fileVersionList.Append($"{fileVersion.RelativePath}; ") |> ignore - return Error graceError |> enhance ("fileVersions", fileVersionList.ToString()) + return Error graceError |> enhance "fileVersions" $"{fileVersionList}" | AWSS3 -> return Error(GraceError.Create (StorageError.getErrorMessage NotImplemented) correlationId) | GoogleCloudStorage -> return Error(GraceError.Create (StorageError.getErrorMessage NotImplemented) correlationId) | ObjectStorageProvider.Unknown -> return Error(GraceError.Create (StorageError.getErrorMessage NotImplemented) correlationId) diff --git a/src/Grace.Server/ApplicationContext.Server.fs b/src/Grace.Server/ApplicationContext.Server.fs index ea61577..56e2162 100644 --- a/src/Grace.Server/ApplicationContext.Server.fs +++ b/src/Grace.Server/ApplicationContext.Server.fs @@ -6,6 +6,7 @@ open CosmosJsonSerializer open Dapr.Client open Dapr.Actors.Client open Grace.Actors.Constants +open Grace.Actors.Services open Grace.Shared open Grace.Shared.Types open Grace.Shared.Utilities @@ -31,11 +32,16 @@ module ApplicationContext = let mutable private configuration: IConfiguration = null let Configuration () : IConfiguration = configuration + /// Dapr actor proxy factory instance let mutable actorProxyFactory: IActorProxyFactory = null + /// Dapr actor state storage provider instance let mutable actorStateStorageProvider: ActorStateStorageProvider = ActorStateStorageProvider.Unknown + /// Logger factory instance let mutable loggerFactory: ILoggerFactory = null + + /// Grace Server's universal .NET memory cache let mutable memoryCache: IMemoryCache = null /// Sets the Application global configuration. @@ -47,17 +53,17 @@ module ApplicationContext = /// Sets the ActorProxyFactory for the application. let setActorProxyFactory proxyFactory = actorProxyFactory <- proxyFactory - Grace.Actors.Services.setActorProxyFactory proxyFactory + setActorProxyFactory proxyFactory /// Sets the ActorStateStorageProvider for the application. let setActorStateStorageProvider actorStateStorage = actorStateStorageProvider <- actorStateStorage - Grace.Actors.Services.setActorStateStorageProvider actorStateStorageProvider + setActorStateStorageProvider actorStateStorageProvider /// Sets the ILoggerFactory for the application. let setLoggerFactory logFactory = loggerFactory <- logFactory - Grace.Actors.Services.setLoggerFactory loggerFactory + setLoggerFactory loggerFactory /// Holds information about each Azure Storage Account used by the application. type StorageAccount = { StorageAccountName: string; StorageAccountConnectionString: string } @@ -175,9 +181,9 @@ module ApplicationContext = //memoryCacheOptions.SizeLimit <- 100L * 1024L * 1024L memoryCache <- new MemoryCache(memoryCacheOptions, loggerFactory) - // Inject the CosmosClient, CosmosContainer, and MemoryCache into Actor Services. - Grace.Actors.Services.setCosmosClient cosmosClient - Grace.Actors.Services.setCosmosContainer cosmosContainer - Grace.Actors.Services.setMemoryCache memoryCache + // Inject things into Actor Services. + setCosmosClient cosmosClient + setCosmosContainer cosmosContainer + setMemoryCache memoryCache } :> Task diff --git a/src/Grace.Server/Branch.Server.fs b/src/Grace.Server/Branch.Server.fs index cc18d49..6ebbed3 100644 --- a/src/Grace.Server/Branch.Server.fs +++ b/src/Grace.Server/Branch.Server.fs @@ -45,35 +45,50 @@ module Branch = let processCommand<'T when 'T :> BranchParameters> (context: HttpContext) (validations: Validations<'T>) (command: 'T -> ValueTask) = task { + let graceIds = getGraceIds context + try let commandName = context.Items["Command"] :?> string - let graceIds = context.Items[nameof (GraceIds)] :?> GraceIds use activity = activitySource.StartActivity("processCommand", ActivityKind.Server) let! parameters = context |> parse<'T> + // We know these Id's from ValidateIds.Middleware, so let's set them so we never have to resolve them again. + parameters.OwnerId <- graceIds.OwnerId + parameters.OrganizationId <- graceIds.OrganizationId + parameters.RepositoryId <- graceIds.RepositoryId + parameters.BranchId <- graceIds.BranchId + let handleCommand branchId cmd = task { let actorProxy = getBranchActorProxy branchId match! actorProxy.Handle cmd (createMetadata context) with | Ok graceReturnValue -> - let graceIds = getGraceIds context - graceReturnValue.Properties[nameof (OwnerId)] <- graceIds.OwnerId - graceReturnValue.Properties[nameof (OrganizationId)] <- graceIds.OrganizationId - graceReturnValue.Properties[nameof (RepositoryId)] <- graceIds.RepositoryId - graceReturnValue.Properties[nameof (BranchId)] <- graceIds.BranchId + (graceReturnValue |> addParametersToGraceReturnValue parameters) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance(nameof(BranchId), graceIds.BranchId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) |> ignore return! context |> result200Ok graceReturnValue | Error graceError -> + (graceError |> addParametersToGraceError parameters) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance(nameof(BranchId), graceIds.BranchId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) |> ignore + log.LogError( "{currentInstant}: In Branch.Server.handleCommand: error from actorProxy.Handle: {error}", getCurrentInstantExtended (), (graceError.ToString()) ) - return! - context - |> result400BadRequest { graceError with Properties = getParametersAsDictionary parameters } + return! context |> result400BadRequest graceError } let validationResults = validations parameters @@ -88,45 +103,37 @@ module Branch = if validationsPassed then let! cmd = command parameters - - let! branchId = resolveBranchId graceIds.RepositoryId parameters.BranchId parameters.BranchName parameters.CorrelationId - - match branchId, commandName = nameof (Create) with - | Some branchId, _ -> - // If Id is Some, then we know we have a valid Id. - if String.IsNullOrEmpty(parameters.BranchId) then - parameters.BranchId <- branchId - - return! handleCommand branchId cmd - | None, true -> - // If it's None, but this is a Create command, still valid, just use the Id from the parameters. - return! handleCommand parameters.BranchId cmd - | None, false -> - // If it's None, and this is not a Create command, then we have a bad request. - log.LogDebug( - "{currentInstant}: In Branch.Server.processCommand: resolveBranchId failed. Branch does not exist. repositoryId: {repositoryId}; repositoryName: {repositoryName}.", - getCurrentInstantExtended (), - parameters.RepositoryId, - parameters.RepositoryName - ) - - return! - context - |> result400BadRequest (GraceError.Create (BranchError.getErrorMessage RepositoryDoesNotExist) (getCorrelationId context)) + return! handleCommand graceIds.BranchId cmd else let! error = validationResults |> getFirstError let errorMessage = BranchError.getErrorMessage error log.LogDebug("{currentInstant}: error: {error}", getCurrentInstantExtended (), errorMessage) - let graceError = GraceError.CreateWithMetadata errorMessage (getCorrelationId context) (getParametersAsDictionary parameters) + let graceError = + (GraceError.CreateWithMetadata errorMessage (getCorrelationId context) (getParametersAsDictionary parameters)) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance(nameof(BranchId), graceIds.BranchId) + .enhance("Path", context.Request.Path) - graceError.Properties.Add("Path", context.Request.Path) - graceError.Properties.Add("Error", errorMessage) return! context |> result400BadRequest graceError with ex -> - let graceError = GraceError.Create $"{Utilities.createExceptionResponse ex}" (getCorrelationId context) + log.LogError( + ex, + "{currentInstant}: Exception in Organization.Server.processCommand. CorrelationId: {correlationId}.", + getCurrentInstantExtended (), + (getCorrelationId context) + ) + + let graceError = + (GraceError.Create $"{Utilities.createExceptionResponse ex}" (getCorrelationId context)) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance(nameof(BranchId), graceIds.BranchId) + .enhance("Path", context.Request.Path) - graceError.Properties.Add("Path", context.Request.Path) return! context |> result500ServerError graceError } @@ -139,9 +146,9 @@ module Branch = = task { use activity = activitySource.StartActivity("processQuery", ActivityKind.Server) + let graceIds = getGraceIds context try - let graceIds = getGraceIds context let validationResults = validations parameters let! validationsPassed = validationResults |> allPass @@ -155,29 +162,36 @@ module Branch = // Wrap the query result in a GraceReturnValue. let graceReturnValue = - (GraceReturnValue.Create queryResult (getCorrelationId context)) + (GraceReturnValue.CreateWithMetadata queryResult (getCorrelationId context) (getParametersAsDictionary parameters)) .enhance(nameof (OwnerId), graceIds.OwnerId) .enhance(nameof (OrganizationId), graceIds.OrganizationId) .enhance(nameof (RepositoryId), graceIds.RepositoryId) .enhance(nameof (BranchId), graceIds.BranchId) + .enhance("Path", context.Request.Path) return! context |> result200Ok graceReturnValue else let! error = validationResults |> getFirstError let graceError = - (GraceError.Create (BranchError.getErrorMessage error) (getCorrelationId context)) + (GraceError.CreateWithMetadata (BranchError.getErrorMessage error) (getCorrelationId context) (getParametersAsDictionary parameters)) .enhance(nameof (OwnerId), graceIds.OwnerId) .enhance(nameof (OrganizationId), graceIds.OrganizationId) .enhance(nameof (RepositoryId), graceIds.RepositoryId) .enhance(nameof (BranchId), graceIds.BranchId) - - graceError.Properties.Add("Path", context.Request.Path) + .enhance("Path", context.Request.Path) + return! context |> result400BadRequest graceError with ex -> - return! - context - |> result500ServerError (GraceError.Create $"{Utilities.createExceptionResponse ex}" (getCorrelationId context)) + let graceError = + (GraceError.CreateWithMetadata $"{Utilities.createExceptionResponse ex}" (getCorrelationId context) (getParametersAsDictionary parameters)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance(nameof (OrganizationId), graceIds.OrganizationId) + .enhance(nameof (RepositoryId), graceIds.RepositoryId) + .enhance(nameof (BranchId), graceIds.BranchId) + .enhance("Path", context.Request.Path) + + return! context |> result500ServerError graceError } /// Creates a new branch. diff --git a/src/Grace.Server/Organization.Server.fs b/src/Grace.Server/Organization.Server.fs index 9deb582..0345aa3 100644 --- a/src/Grace.Server/Organization.Server.fs +++ b/src/Grace.Server/Organization.Server.fs @@ -43,13 +43,14 @@ module Organization = (command: 'T -> ValueTask) = task { + let graceIds = getGraceIds context + try use activity = activitySource.StartActivity("processCommand", ActivityKind.Server) let commandName = context.Items["Command"] :?> string - let graceIds = getGraceIds context let! parameters = context |> parse<'T> - // We know these Id's from ValidateIdsMiddleware, so let's be sure they're set, so we never have to resolve them again. + // We know these Id's from ValidateIds.Middleware, so let's set them so we never have to resolve them again. parameters.OwnerId <- graceIds.OwnerId parameters.OrganizationId <- graceIds.OrganizationId @@ -59,21 +60,27 @@ module Organization = match! actorProxy.Handle cmd (createMetadata context) with | Ok graceReturnValue -> - let graceIds = getGraceIds context - graceReturnValue.Properties[nameof (OwnerId)] <- graceIds.OwnerId - graceReturnValue.Properties[nameof (OrganizationId)] <- graceIds.OrganizationId + (graceReturnValue |> addParametersToGraceReturnValue parameters) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) |> ignore return! context |> result200Ok graceReturnValue | Error graceError -> + (graceError |> addParametersToGraceError parameters) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) |> ignore + log.LogDebug( "{currentInstant}: In Branch.Server.handleCommand: error from actorProxy.Handle: {error}", getCurrentInstantExtended (), (graceError.ToString()) ) - return! - context - |> result400BadRequest { graceError with Properties = getParametersAsDictionary parameters } + return! context |> result400BadRequest graceError } let validationResults = validations parameters @@ -87,51 +94,20 @@ module Organization = if validationsPassed then let! cmd = command parameters - - let! organizationId = - resolveOrganizationId - parameters.OwnerId - parameters.OwnerName - parameters.OrganizationId - parameters.OrganizationName - parameters.CorrelationId - - match organizationId, commandName = nameof (Create) with - | Some organizationId, _ -> - // If Id is Some, then we know we have a valid Id. - if String.IsNullOrEmpty(parameters.OrganizationId) then - parameters.OrganizationId <- organizationId - - return! handleCommand organizationId cmd - | None, true -> - // If it's None, but this is a Create command, still valid, just use the Id from the parameters. - return! handleCommand parameters.OrganizationId cmd - | None, false -> - // If it's None, and this is not a Create command, then we have a bad request. - log.LogDebug( - "{currentInstant}: In Organization.Server.processCommand: resolveOrganizationId failed. Organization does not exist. organizationId: {organizationId}; organizationName: {organizationName}.", - getCurrentInstantExtended (), - parameters.OrganizationId, - parameters.OrganizationName - ) - - return! - context - |> result400BadRequest ( - GraceError.CreateWithMetadata - (OrganizationError.getErrorMessage OrganizationDoesNotExist) - (getCorrelationId context) - (getParametersAsDictionary parameters) - ) + return! handleCommand graceIds.OrganizationId cmd else let! error = validationResults |> getFirstError let errorMessage = OrganizationError.getErrorMessage error log.LogDebug("{currentInstant}: error: {error}", getCurrentInstantExtended (), errorMessage) - let graceError = GraceError.CreateWithMetadata errorMessage (getCorrelationId context) (getParametersAsDictionary parameters) + let graceError = + (GraceError.CreateWithMetadata errorMessage (getCorrelationId context) (getParametersAsDictionary parameters)) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) + .enhance("Error", errorMessage) - graceError.Properties.Add("Path", context.Request.Path) - graceError.Properties.Add("Error", errorMessage) return! context |> result400BadRequest graceError with ex -> log.LogError( @@ -141,9 +117,13 @@ module Organization = (getCorrelationId context) ) - return! - context - |> result500ServerError (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) + let graceError = + (GraceError.Create $"{Utilities.createExceptionResponse ex}" (getCorrelationId context)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance(nameof (OrganizationId), graceIds.OrganizationId) + .enhance("Path", context.Request.Path) + + return! context |> result500ServerError graceError } /// Generic processor for all Organization queries. @@ -156,9 +136,9 @@ module Organization = = task { use activity = activitySource.StartActivity("processQuery", ActivityKind.Server) + let graceIds = getGraceIds context try - let graceIds = getGraceIds context let validationResults = validations parameters let! validationsPassed = validationResults |> allPass @@ -170,26 +150,31 @@ module Organization = let! queryResult = query context maxCount actorProxy // Wrap the result in a GraceReturnValue. - let graceReturnValue = GraceReturnValue.Create queryResult (getCorrelationId context) - graceReturnValue.enhance(nameof (OwnerId), graceIds.OwnerId) - .enhance(nameof (OrganizationId), graceIds.OrganizationId) - .enhance("Path", context.Request.Path) - |> ignore + let graceReturnValue = + (GraceReturnValue.CreateWithMetadata queryResult (getCorrelationId context) (getParametersAsDictionary parameters)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance(nameof (OrganizationId), graceIds.OrganizationId) + .enhance("Path", context.Request.Path) return! context |> result200Ok graceReturnValue else let! error = validationResults |> getFirstError - let graceError = GraceError.Create (OrganizationError.getErrorMessage error) (getCorrelationId context) - graceError.enhance(nameof (OwnerId), graceIds.OwnerId) - .enhance(nameof (OrganizationId), graceIds.OrganizationId) - .enhance("Path", context.Request.Path) - |> ignore + let graceError = + (GraceError.CreateWithMetadata (OrganizationError.getErrorMessage error) (getCorrelationId context) (getParametersAsDictionary parameters)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance(nameof (OrganizationId), graceIds.OrganizationId) + .enhance("Path", context.Request.Path) + return! context |> result400BadRequest graceError with ex -> - return! - context - |> result500ServerError (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) + let graceError = + (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance(nameof (OrganizationId), graceIds.OrganizationId) + .enhance("Path", context.Request.Path) + + return! context |> result500ServerError graceError } /// Create an organization. diff --git a/src/Grace.Server/Owner.Server.fs b/src/Grace.Server/Owner.Server.fs index e843bd8..c1c72c7 100644 --- a/src/Grace.Server/Owner.Server.fs +++ b/src/Grace.Server/Owner.Server.fs @@ -52,24 +52,24 @@ module Owner = use activity = activitySource.StartActivity("processCommand", ActivityKind.Server) let! parameters = context |> parse<'T> + // We know these Id's from ValidateIds.Middleware, so let's set them so we never have to resolve them again. + parameters.OwnerId <- graceIds.OwnerId + let handleCommand ownerId cmd = task { let actorProxy = getActorProxy ownerId match! actorProxy.Handle cmd (createMetadata context) with | Ok graceReturnValue -> - (getParametersAsDictionary parameters) |> Seq.iter (fun kvp -> graceReturnValue.enhance(kvp.Key, kvp.Value) |> ignore) - - graceReturnValue + (graceReturnValue |> addParametersToGraceReturnValue parameters) .enhance(nameof(OwnerId), graceIds.OwnerId) .enhance("Command", commandName) .enhance("Path", context.Request.Path) |> ignore return! context |> result200Ok graceReturnValue | Error graceError -> - graceError + (graceError |> addParametersToGraceError parameters) .enhance(nameof(OwnerId), graceIds.OwnerId) - .enhance(nameof(Parameters), serialize parameters) .enhance("Command", commandName) .enhance("Path", context.Request.Path) |> ignore @@ -116,9 +116,12 @@ module Owner = (getCorrelationId context) ) - return! - context - |> result500ServerError (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) + let graceError = + (GraceError.Create $"{Utilities.createExceptionResponse ex}" (getCorrelationId context)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance("Path", context.Request.Path) + + return! context |> result500ServerError graceError } /// Generic processor for all Owner queries. @@ -158,18 +161,15 @@ module Owner = let graceError = (GraceError.Create (OwnerError.getErrorMessage error) (getCorrelationId context)) .enhance(nameof (OwnerId), graceIds.OwnerId) - .enhance(nameof(Parameters), serialize parameters) .enhance("Path", context.Request.Path) return! context |> result400BadRequest graceError with ex -> let graceError = (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) .enhance(nameof (OwnerId), graceIds.OwnerId) - .enhance(nameof(Parameters), serialize parameters) .enhance("Path", context.Request.Path) - return! - context - |> result500ServerError graceError + + return! context |> result500ServerError graceError } /// Create an owner. diff --git a/src/Grace.Server/Program.Server.fs b/src/Grace.Server/Program.Server.fs index 8cffef5..c0ee224 100644 --- a/src/Grace.Server/Program.Server.fs +++ b/src/Grace.Server/Program.Server.fs @@ -70,8 +70,9 @@ module Program = // Console.WriteLine($"{kvp.Key}: {kvp.Value}"); // Just placing some much-used services into ApplicationContext where they're easy to find. - let loggerFactory = host.Services.GetService(typeof) :?> ILoggerFactory + Grace.Actors.Services.setHostServiceProvider host.Services + let loggerFactory = host.Services.GetService(typeof) :?> ILoggerFactory ApplicationContext.setLoggerFactory (loggerFactory) host.Run() diff --git a/src/Grace.Server/Repository.Server.fs b/src/Grace.Server/Repository.Server.fs index 4420698..865d97a 100644 --- a/src/Grace.Server/Repository.Server.fs +++ b/src/Grace.Server/Repository.Server.fs @@ -50,34 +50,47 @@ module Repository = let processCommand<'T when 'T :> RepositoryParameters> (context: HttpContext) (validations: Validations<'T>) (command: 'T -> ValueTask) = task { + use activity = activitySource.StartActivity("processCommand", ActivityKind.Server) + let graceIds = getGraceIds context + try - use activity = activitySource.StartActivity("processCommand", ActivityKind.Server) let commandName = context.Items["Command"] :?> string - let graceIds = context.Items["GraceIds"] :?> GraceIds let! parameters = context |> parse<'T> + // We know these Id's from ValidateIds.Middleware, so let's set them so we never have to resolve them again. + parameters.OwnerId <- graceIds.OwnerId + parameters.OrganizationId <- graceIds.OrganizationId + parameters.RepositoryId <- graceIds.RepositoryId + let handleCommand repositoryId cmd = task { let actorProxy = getActorProxy repositoryId match! actorProxy.Handle cmd (createMetadata context) with | Ok graceReturnValue -> - let graceIds = getGraceIds context - graceReturnValue.Properties[nameof (OwnerId)] <- graceIds.OwnerId - graceReturnValue.Properties[nameof (OrganizationId)] <- graceIds.OrganizationId - graceReturnValue.Properties[nameof (RepositoryId)] <- graceIds.RepositoryId + (graceReturnValue |> addParametersToGraceReturnValue parameters) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) |> ignore return! context |> result200Ok graceReturnValue | Error graceError -> + (graceError |> addParametersToGraceError parameters) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) |> ignore + log.LogDebug( "{currentInstant}: In Branch.Server.handleCommand: error from actorProxy.Handle: {error}", getCurrentInstantExtended (), (graceError.ToString()) ) - return! - context - |> result400BadRequest { graceError with Properties = getParametersAsDictionary parameters } + return! context |> result400BadRequest graceError } let validationResults = validations parameters @@ -94,55 +107,21 @@ module Repository = if validationsPassed then let! cmd = command parameters - - let! repositoryId = - resolveRepositoryId - parameters.OwnerId - parameters.OwnerName - parameters.OrganizationId - parameters.OrganizationName - parameters.RepositoryId - parameters.RepositoryName - parameters.CorrelationId - - match repositoryId, commandName = nameof (Create) with - | Some repositoryId, _ -> - // If Id is Some, then we know we have a valid Id. - if String.IsNullOrEmpty(parameters.RepositoryId) then - parameters.RepositoryId <- repositoryId - - return! handleCommand repositoryId cmd - | None, true -> - // If it's None, but this is a Create command, still valid, just use the Id from the parameters. - return! handleCommand parameters.RepositoryId cmd - | None, false -> - // If it's None, and this is not a Create command, then we have a bad request. - log.LogDebug( - "{currentInstant}: In Repository.Server.processCommand: resolveRepositoryId failed. Repository does not exist. repositoryId: {repositoryId}; repositoryName: {repositoryName}; Path: {path}; CorrelationId: {correlationId}.", - getCurrentInstantExtended (), - parameters.RepositoryId, - parameters.RepositoryName, - context.Request.Path, - (getCorrelationId context) - ) - - return! - context - |> result400BadRequest ( - GraceError.CreateWithMetadata - (RepositoryError.getErrorMessage RepositoryDoesNotExist) - (getCorrelationId context) - (getParametersAsDictionary parameters) - ) + return! handleCommand graceIds.RepositoryId cmd else let! error = validationResults |> getFirstError let errorMessage = RepositoryError.getErrorMessage error log.LogDebug("{currentInstant}: error: {error}", getCurrentInstantExtended (), errorMessage) - let graceError = GraceError.CreateWithMetadata errorMessage (getCorrelationId context) (getParametersAsDictionary parameters) + let graceError = + (GraceError.CreateWithMetadata errorMessage (getCorrelationId context) (getParametersAsDictionary parameters)) + .enhance(nameof(OwnerId), graceIds.OwnerId) + .enhance(nameof(OrganizationId), graceIds.OrganizationId) + .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance("Command", commandName) + .enhance("Path", context.Request.Path) + .enhance("Error", errorMessage) - graceError.Properties.Add("Path", context.Request.Path) - graceError.Properties.Add("Error", errorMessage) return! context |> result400BadRequest graceError with ex -> log.LogError( @@ -153,9 +132,14 @@ module Repository = (getCorrelationId context) ) - return! - context - |> result500ServerError (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) + let graceError = + (GraceError.Create $"{Utilities.createExceptionResponse ex}" (getCorrelationId context)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance(nameof (OrganizationId), graceIds.OrganizationId) + .enhance(nameof (RepositoryId), graceIds.RepositoryId) + .enhance("Path", context.Request.Path) + + return! context |> result500ServerError graceError } let processQuery<'T, 'U when 'T :> RepositoryParameters> @@ -166,11 +150,11 @@ module Repository = (query: QueryResult) = task { + use activity = activitySource.StartActivity("processQuery", ActivityKind.Server) + let graceIds = getGraceIds context + try - use activity = activitySource.StartActivity("processQuery", ActivityKind.Server) - let graceIds = getGraceIds context let validationResults = validations parameters - let! validationsPassed = validationResults |> allPass if validationsPassed then @@ -186,6 +170,7 @@ module Repository = .enhance(nameof(OwnerId), graceIds.OwnerId) .enhance(nameof(OrganizationId), graceIds.OrganizationId) .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance("Path", context.Request.Path) return! context |> result200Ok graceReturnValue else @@ -196,8 +181,8 @@ module Repository = .enhance(nameof(OwnerId), graceIds.OwnerId) .enhance(nameof(OrganizationId), graceIds.OrganizationId) .enhance(nameof(RepositoryId), graceIds.RepositoryId) + .enhance("Path", context.Request.Path) - graceError.Properties.Add("Path", context.Request.Path) return! context |> result400BadRequest graceError with ex -> log.LogError( @@ -208,9 +193,14 @@ module Repository = (getCorrelationId context) ) - return! - context - |> result500ServerError (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) + let graceError = + (GraceError.Create $"{createExceptionResponse ex}" (getCorrelationId context)) + .enhance(nameof (OwnerId), graceIds.OwnerId) + .enhance(nameof (OrganizationId), graceIds.OrganizationId) + .enhance(nameof (RepositoryId), graceIds.RepositoryId) + .enhance("Path", context.Request.Path) + + return! context |> result500ServerError graceError } /// Create a new repository. diff --git a/src/Grace.Server/Startup.Server.fs b/src/Grace.Server/Startup.Server.fs index 31947eb..cde3cdc 100644 --- a/src/Grace.Server/Startup.Server.fs +++ b/src/Grace.Server/Startup.Server.fs @@ -320,9 +320,6 @@ module Application = globalOpenTelemetryAttributes.Add("process.runtime.version", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription) - // Set up the ActorProxyFactory for the application. - let actorProxyOptions = ActorProxyOptions() // DaprApiToken = Environment.GetEnvironmentVariable("DAPR_API_TOKEN")) (when we actually implement auth) - //let environmentVariables = Environment.GetEnvironmentVariables() //let sortedKeys = SortedSet() @@ -334,12 +331,13 @@ module Application = // let value = Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.Process) // logToConsole $"{key}: {value}" + // Set up the ActorProxyFactory for the application. + let actorProxyOptions = ActorProxyOptions() // DaprApiToken = Environment.GetEnvironmentVariable("DAPR_API_TOKEN")) (when we actually implement auth) actorProxyOptions.HttpEndpoint <- $"{Environment.GetEnvironmentVariable(Constants.EnvironmentVariables.DaprServerUri)}:{Environment.GetEnvironmentVariable(Constants.EnvironmentVariables.DaprHttpPort)}" - actorProxyOptions.JsonSerializerOptions <- Constants.JsonSerializerOptions actorProxyOptions.RequestTimeout <- TimeSpan.FromSeconds(60.0) - //logToConsole $"actorProxyOptions.HttpEndpoint: {actorProxyOptions.HttpEndpoint}" + let actorProxyFactory = new ActorProxyFactory(actorProxyOptions) ApplicationContext.setActorProxyFactory actorProxyFactory ApplicationContext.setActorStateStorageProvider ActorStateStorageProvider.AzureCosmosDb @@ -352,7 +350,7 @@ module Application = openApiInfo.Version <- "v0.1" openApiInfo.Contact <- new OpenApiContact() openApiInfo.Contact.Name <- "Scott Arbeit" - openApiInfo.Contact.Email <- "scott@scottarbeit.com" + openApiInfo.Contact.Email <- "scott.arbeit@outlook.com" openApiInfo.Contact.Url <- Uri("https://gracevcs.com") // Telemetry configuration diff --git a/src/Grace.Shared/Constants.Shared.fs b/src/Grace.Shared/Constants.Shared.fs index ba8880e..4828baf 100644 --- a/src/Grace.Shared/Constants.Shared.fs +++ b/src/Grace.Shared/Constants.Shared.fs @@ -238,7 +238,10 @@ module Constants = /// Values used with Grace's MemoryCache. module MemoryCache = /// A special Guid value that means "false" or "we know there's no value here". Used in places where the cache entry value is a Guid. - let EntityDoesNotExist = box (Guid("27F21D8A-DA1D-4C73-8773-4AA5A5712612")) // There's nothing special about this Guid. I just generated it one day. + let EntityDoesNotExistGuid = Guid("27F21D8A-DA1D-4C73-8773-4AA5A5712612") + + /// A special Guid value that means "false" or "we know there's no value here". Used in places where the cache entry value is a Guid. + let EntityDoesNotExist = box EntityDoesNotExistGuid // There's nothing special about this Guid. I just generated it one day. /// The default value to store in Grace's MemoryCache when an entity is known to exist. This is a one-character string because MemoryCache values are Objects; although a bool would make more sense, using a constant string avoids boxing. [] diff --git a/src/Grace.Shared/Services.Shared.fs b/src/Grace.Shared/Services.Shared.fs index 426d1d6..59d7856 100644 --- a/src/Grace.Shared/Services.Shared.fs +++ b/src/Grace.Shared/Services.Shared.fs @@ -12,19 +12,19 @@ open System.IO open System.Linq open System.Security.Cryptography open System.Text -open Microsoft.FSharp.NativeInterop module Services = /// Adds a property to a GraceResult instance. - let enhance<'T> (key: string, value: string) (result: GraceResult<'T>) = + let enhance<'T> key value (result: GraceResult<'T>) = if not <| String.IsNullOrEmpty(key) then + let safeValue = if String.IsNullOrEmpty(value) then String.Empty else value match result with | Ok result -> - result.Properties[key] <- value + result.Properties[key] <- safeValue Ok result | Error error -> - error.Properties[key] <- value + error.Properties[key] <- safeValue Error error else result diff --git a/src/Grace.Shared/Types.Shared.fs b/src/Grace.Shared/Types.Shared.fs index f159bb1..5047a6b 100644 --- a/src/Grace.Shared/Types.Shared.fs +++ b/src/Grace.Shared/Types.Shared.fs @@ -421,6 +421,13 @@ module Types = CorrelationId: string Properties: Dictionary } + /// Adds a property to a GraceResult instance. + //static member enhance<'T> (key, value) (result: GraceReturnValue<'T>) = + // if not <| String.IsNullOrEmpty(key) then + // result.Properties[key] <- value + + // result + static member Create<'T> (returnValue: 'T) (correlationId: string) = { ReturnValue = returnValue; EventTime = getCurrentInstant (); CorrelationId = correlationId; Properties = new Dictionary() } @@ -429,7 +436,13 @@ module Types = /// Adds a key-value pair to GraceReturnValue's Properties dictionary. member this.enhance(key, value) = - this.Properties[key] <- value + match String.IsNullOrEmpty(key), String.IsNullOrEmpty(value) with + | false, false -> + this.Properties[key] <- value + | false, true -> + this.Properties[key] <- String.Empty + | true, _ -> () + this override this.ToString() = @@ -456,7 +469,13 @@ module Types = /// Adds a key-value pair to GraceError's Properties dictionary. member this.enhance(key, value) = - this.Properties[key] <- value + match String.IsNullOrEmpty(key), String.IsNullOrEmpty(value) with + | false, false -> + this.Properties[key] <- value + | false, true -> + this.Properties[key] <- String.Empty + | true, _ -> () + this override this.ToString() = diff --git a/src/Grace.Shared/Utilities.Shared.fs b/src/Grace.Shared/Utilities.Shared.fs index cfdcb2e..1eeb17a 100644 --- a/src/Grace.Shared/Utilities.Shared.fs +++ b/src/Grace.Shared/Utilities.Shared.fs @@ -20,6 +20,7 @@ open System.Net.Security open System.Net open System open System.Reflection +open System.Collections.Concurrent #nowarn "9" @@ -444,14 +445,14 @@ module Utilities = let p = NativePtr.stackalloc<'a> length |> NativePtr.toVoidPtr Span<'a>(p, length) - let propertyLookupByType = Dictionary() + let propertyLookupByType = ConcurrentDictionary() /// Creates a dictionary from the properties of an object. let getParametersAsDictionary<'T> (obj: 'T) = let mutable properties = Array.Empty() if not <| propertyLookupByType.TryGetValue(typeof<'T>, &properties) then properties <- typeof<'T>.GetProperties(BindingFlags.Instance ||| BindingFlags.Public) - propertyLookupByType.Add(typeof<'T>, properties) + propertyLookupByType.TryAdd(typeof<'T>, properties) |> ignore let dict = Dictionary()