mirror of https://github.com/EasyAbp/EShop.git
30 changed files with 784 additions and 0 deletions
@ -0,0 +1,58 @@ |
|||||
|
# EShop.Plugins.Inventories.OrleansGrains |
||||
|
|
||||
|
[](https://abp.io) |
||||
|
[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions) |
||||
|
[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions) |
||||
|
[](https://discord.gg/S6QaezrCRq) |
||||
|
[](https://www.github.com/EasyAbp/EShop) |
||||
|
|
||||
|
EShop product-inventory implementation of [Orleans Grains](https://docs.microsoft.com/en-us/dotnet/orleans/grains). |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
1. Install the following NuGet packages. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-nuget-packages)) |
||||
|
|
||||
|
* EasyAbp.EShop.Products.OrleansGrainsInventory.Domain _(install at EasyAbp.EShop.Products.Domain location)_ |
||||
|
* EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo _(install at a host project to run Grains)_ |
||||
|
|
||||
|
2. Add `DependsOn(typeof(EShopXxxModule))` attribute to configure the module dependencies. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-module-dependencies)) |
||||
|
|
||||
|
3. Open `Program.cs` in the host project to create an Orleans Silo. (see Microsoft's [document](https://docs.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/server-configuration) for more information) |
||||
|
|
||||
|
```csharp |
||||
|
builder.Host.AddAppSettingsSecretsJson() |
||||
|
.UseAutofac() |
||||
|
.UseSerilog() |
||||
|
.UseOrleans(c => |
||||
|
{ |
||||
|
c.UseLocalhostClustering() // for test only |
||||
|
.AddMemoryGrainStorage(InventoryGrain.StorageProviderName) // for test only |
||||
|
.Configure<ClusterOptions>(options => |
||||
|
{ |
||||
|
options.ClusterId = "my-first-cluster"; |
||||
|
options.ServiceId = "MyEShopApp"; |
||||
|
}) |
||||
|
.ConfigureApplicationParts( |
||||
|
parts => parts.AddApplicationPart(typeof(InventoryGrain).Assembly).WithReferences()); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
1. Configure the OrleansGrains inventory provider as default. |
||||
|
```csharp |
||||
|
Configure<EShopProductsOptions>(options => |
||||
|
{ |
||||
|
// Configure as the default inventory provider |
||||
|
options.DefaultInventoryProviderName = "OrleansGrains"; |
||||
|
|
||||
|
// Configure as the default inventory provider for MyProductGroup |
||||
|
options.Groups.Configure<MyProductGroup>(group => |
||||
|
{ |
||||
|
group.DefaultInventoryProviderName = "OrleansGrains"; |
||||
|
}); |
||||
|
}); |
||||
|
``` |
||||
|
> Better to use `OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderName` instead of `"OrleansGrains"` as the provider name. |
||||
|
|
||||
|
2. Create a product and set `InventoryProviderName` to `OrleansGrains`. Then the product is specified to use the Orleans Grains inventory provider. |
||||
@ -0,0 +1 @@ |
|||||
|
../../../docs/plugins/inventories/orleans-grains/README.md |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="$(OrleansVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Domain.Shared\EasyAbp.EShop.Products.Domain.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,11 @@ |
|||||
|
using EasyAbp.EShop.Products; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(EShopProductsDomainSharedModule) |
||||
|
)] |
||||
|
public class EShopPluginsInventoriesOrleansGrainsAbstractionsModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
|
||||
|
public interface IInventoryGrain : IGrainWithStringKey |
||||
|
{ |
||||
|
Task<InventoryStateModel> GetInventoryStateAsync(); |
||||
|
|
||||
|
Task IncreaseInventoryAsync(int quantity, bool decreaseSold); |
||||
|
|
||||
|
Task ReduceInventoryAsync(int quantity, bool increaseSold); |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
|
||||
|
public class InventoryStateModel |
||||
|
{ |
||||
|
public int Inventory { get; set; } |
||||
|
|
||||
|
public long Sold { get; set; } |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Inventories.OrleansGrains\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Orleans.Server" Version="$(OrleansVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,14 @@ |
|||||
|
using EasyAbp.EShop.Products; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(EShopPluginsInventoriesOrleansGrainsModule) |
||||
|
)] |
||||
|
public class EShopPluginsInventoriesOrleansGrainsSiloModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="$(OrleansVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(EShopPluginsInventoriesOrleansGrainsAbstractionsModule) |
||||
|
)] |
||||
|
public class EShopPluginsInventoriesOrleansGrainsModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Orleans.Providers; |
||||
|
using Orleans.Runtime; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
|
||||
|
[StorageProvider(ProviderName = StorageProviderName)] |
||||
|
public class InventoryGrain : Grain<InventoryStateModel>, IInventoryGrain |
||||
|
{ |
||||
|
public const string StorageProviderName = "EShopInventoryStorage"; |
||||
|
|
||||
|
public virtual Task<InventoryStateModel> GetInventoryStateAsync() => Task.FromResult(State); |
||||
|
|
||||
|
public virtual async Task IncreaseInventoryAsync(int quantity, bool decreaseSold) |
||||
|
{ |
||||
|
InternalIncreaseInventory(quantity, decreaseSold); |
||||
|
|
||||
|
await WriteStateAsync(); |
||||
|
} |
||||
|
|
||||
|
public async Task ReduceInventoryAsync(int quantity, bool increaseSold) |
||||
|
{ |
||||
|
InternalReduceInventory(quantity, increaseSold); |
||||
|
|
||||
|
await WriteStateAsync(); |
||||
|
} |
||||
|
|
||||
|
protected virtual void InternalIncreaseInventory(int quantity, bool decreaseSold) |
||||
|
{ |
||||
|
if (quantity < 0) |
||||
|
{ |
||||
|
throw new OrleansException("Quantity should not be less than 0."); |
||||
|
} |
||||
|
|
||||
|
if (decreaseSold && State.Sold - quantity < 0) |
||||
|
{ |
||||
|
throw new OrleansException("Target Sold cannot be less than 0."); |
||||
|
} |
||||
|
|
||||
|
State.Inventory = checked(State.Inventory + quantity); |
||||
|
|
||||
|
if (decreaseSold) |
||||
|
{ |
||||
|
State.Sold -= quantity; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual void InternalReduceInventory(int quantity, bool increaseSold) |
||||
|
{ |
||||
|
if (quantity < 0) |
||||
|
{ |
||||
|
throw new OrleansException("Quantity should not be less than 0."); |
||||
|
} |
||||
|
|
||||
|
if (quantity > State.Inventory) |
||||
|
{ |
||||
|
throw new OrleansException("Insufficient inventory."); |
||||
|
} |
||||
|
|
||||
|
if (increaseSold) |
||||
|
{ |
||||
|
State.Sold = checked(State.Sold + quantity); |
||||
|
} |
||||
|
|
||||
|
State.Inventory -= quantity; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Domain\EasyAbp.EShop.Products.Domain.csproj" /> |
||||
|
<ProjectReference Include="..\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Orleans.Client" Version="$(OrleansVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,28 @@ |
|||||
|
using EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
using EasyAbp.EShop.Products.Options; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(EShopProductsDomainModule), |
||||
|
typeof(EShopPluginsInventoriesOrleansGrainsAbstractionsModule) |
||||
|
)] |
||||
|
public class EShopProductsOrleansGrainsInventoryDomainModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<EShopProductsOptions>(options => |
||||
|
{ |
||||
|
options.InventoryProviders.Configure( |
||||
|
OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderName, provider => |
||||
|
{ |
||||
|
provider.DisplayName = |
||||
|
OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderDisplayName; |
||||
|
provider.Description = OrleansGrainsProductInventoryProvider |
||||
|
.OrleansGrainsProductInventoryProviderDescription; |
||||
|
provider.ProviderType = typeof(OrleansGrainsProductInventoryProvider); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory; |
||||
|
|
||||
|
public interface IInventoryGrainProvider |
||||
|
{ |
||||
|
Task<IInventoryGrain> GetAsync(string grainKey); |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
using Orleans; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory; |
||||
|
|
||||
|
public class InventoryGrainProvider : IInventoryGrainProvider, ITransientDependency |
||||
|
{ |
||||
|
private readonly IGrainFactory _grainFactory; |
||||
|
|
||||
|
public InventoryGrainProvider(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
_grainFactory = grainFactory; |
||||
|
} |
||||
|
|
||||
|
public virtual Task<IInventoryGrain> GetAsync(string grainKey) |
||||
|
{ |
||||
|
return Task.FromResult(_grainFactory.GetGrain<IInventoryGrain>(grainKey)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
using EasyAbp.EShop.Products.ProductInventories; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory; |
||||
|
|
||||
|
public class OrleansGrainsProductInventoryProvider : IProductInventoryProvider, ITransientDependency |
||||
|
{ |
||||
|
public static string OrleansGrainsProductInventoryProviderName { get; set; } = "OrleansGrains"; |
||||
|
public static string OrleansGrainsProductInventoryProviderDisplayName { get; set; } = "OrleansGrains"; |
||||
|
public static string OrleansGrainsProductInventoryProviderDescription { get; set; } = "OrleansGrains"; |
||||
|
|
||||
|
public string InventoryProviderName { get; } = OrleansGrainsProductInventoryProviderName; |
||||
|
|
||||
|
private readonly ILogger<OrleansGrainsProductInventoryProvider> _logger; |
||||
|
protected IInventoryGrainProvider InventoryGrainProvider { get; } |
||||
|
|
||||
|
public OrleansGrainsProductInventoryProvider( |
||||
|
IInventoryGrainProvider inventoryGrainProvider, |
||||
|
ILogger<OrleansGrainsProductInventoryProvider> logger) |
||||
|
{ |
||||
|
InventoryGrainProvider = inventoryGrainProvider; |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<InventoryDataModel> GetInventoryDataAsync(InventoryQueryModel model) |
||||
|
{ |
||||
|
var grain = await GetGrainAsync(model); |
||||
|
|
||||
|
var stateModel = await grain.GetInventoryStateAsync(); |
||||
|
|
||||
|
return new InventoryDataModel |
||||
|
{ |
||||
|
Inventory = stateModel.Inventory, |
||||
|
Sold = stateModel.Sold |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<Dictionary<Guid, InventoryDataModel>> GetSkuIdInventoryDataMappingAsync( |
||||
|
IList<InventoryQueryModel> models) |
||||
|
{ |
||||
|
var result = new Dictionary<Guid, InventoryDataModel>(); |
||||
|
|
||||
|
foreach (var model in models) |
||||
|
{ |
||||
|
result.Add(model.ProductSkuId, await GetInventoryDataAsync(model)); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity, |
||||
|
bool decreaseSold) |
||||
|
{ |
||||
|
var grain = await GetGrainAsync(model); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await grain.IncreaseInventoryAsync(quantity, decreaseSold); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
_logger.LogError("Grain threw: {Message}", e.Message); |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold) |
||||
|
{ |
||||
|
var grain = await GetGrainAsync(model); |
||||
|
|
||||
|
var stateModel = await grain.GetInventoryStateAsync(); |
||||
|
|
||||
|
if (stateModel.Inventory < quantity) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await grain.ReduceInventoryAsync(quantity, increaseSold); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
_logger.LogError("Grain threw: {Message}", e.Message); |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<IInventoryGrain> GetGrainAsync(InventoryQueryModel model) |
||||
|
{ |
||||
|
return await InventoryGrainProvider.GetAsync(GetGrainId(model)); |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetGrainId(InventoryQueryModel model) |
||||
|
{ |
||||
|
return $"eshop_inventory_{(model.TenantId.HasValue ? model.TenantId.Value : "host")}_{model.ProductSkuId}"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Authorization; |
||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpAutofacModule), |
||||
|
typeof(AbpTestBaseModule), |
||||
|
typeof(AbpAuthorizationModule), |
||||
|
typeof(EShopProductsOrleansGrainsInventoryDomainModule) |
||||
|
)] |
||||
|
public class EShopProductsOrleansGrainsInventoryDomainTestModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace>EasyAbp.EShop.Products.OrleansGrainsInventory.Domain</RootNamespace> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> |
||||
|
<PackageReference Include="NSubstitute" Version="4.2.2" /> |
||||
|
<PackageReference Include="Shouldly" Version="4.0.1" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> |
||||
|
<PackageReference Include="Volo.Abp.Autofac" Version="$(AbpVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Authorization" Version="$(AbpVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.TestBase" Version="$(AbpVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,43 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain; |
||||
|
|
||||
|
public class FakeInventoryGrain : IInventoryGrain, ITransientDependency |
||||
|
{ |
||||
|
private InventoryStateModel StateModel { get; } = new() |
||||
|
{ |
||||
|
Inventory = 100, |
||||
|
Sold = 0 |
||||
|
}; |
||||
|
|
||||
|
public Task<InventoryStateModel> GetInventoryStateAsync() |
||||
|
{ |
||||
|
return Task.FromResult(StateModel); |
||||
|
} |
||||
|
|
||||
|
public Task IncreaseInventoryAsync(int quantity, bool decreaseSold) |
||||
|
{ |
||||
|
StateModel.Inventory += quantity; |
||||
|
|
||||
|
if (decreaseSold) |
||||
|
{ |
||||
|
StateModel.Sold -= quantity; |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task ReduceInventoryAsync(int quantity, bool increaseSold) |
||||
|
{ |
||||
|
StateModel.Inventory -= quantity; |
||||
|
|
||||
|
if (increaseSold) |
||||
|
{ |
||||
|
StateModel.Sold += quantity; |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Products.ProductInventories; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain; |
||||
|
|
||||
|
public class OrleansGrainsProductInventoryProviderTests : ProductsOrleansGrainsInventoryTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task Should_Get_Inventory() |
||||
|
{ |
||||
|
var inventoryProvider = ServiceProvider.GetRequiredService<OrleansGrainsProductInventoryProvider>(); |
||||
|
|
||||
|
var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel()); |
||||
|
|
||||
|
inventoryDataModel.ShouldNotBeNull(); |
||||
|
inventoryDataModel.Inventory.ShouldBe(100); |
||||
|
inventoryDataModel.Sold.ShouldBe(0); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Change_Inventory() |
||||
|
{ |
||||
|
var inventoryProvider = ServiceProvider.GetRequiredService<OrleansGrainsProductInventoryProvider>(); |
||||
|
|
||||
|
var result = await inventoryProvider.TryReduceInventoryAsync(new InventoryQueryModel(), 2, true); |
||||
|
|
||||
|
result.ShouldBeTrue(); |
||||
|
|
||||
|
var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel()); |
||||
|
|
||||
|
inventoryDataModel.ShouldNotBeNull(); |
||||
|
inventoryDataModel.Inventory.ShouldBe(98); |
||||
|
inventoryDataModel.Sold.ShouldBe(2); |
||||
|
|
||||
|
result = await inventoryProvider.TryIncreaseInventoryAsync(new InventoryQueryModel(), 1, true); |
||||
|
|
||||
|
result.ShouldBeTrue(); |
||||
|
|
||||
|
inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel()); |
||||
|
|
||||
|
inventoryDataModel.ShouldNotBeNull(); |
||||
|
inventoryDataModel.Inventory.ShouldBe(99); |
||||
|
inventoryDataModel.Sold.ShouldBe(1); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Testing; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain |
||||
|
{ |
||||
|
/* All test classes are derived from this class, directly or indirectly. */ |
||||
|
public abstract class |
||||
|
ProductsOrleansGrainsInventoryTestBase : AbpIntegratedTest<EShopProductsOrleansGrainsInventoryDomainTestModule> |
||||
|
{ |
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
|
||||
|
protected virtual Task WithUnitOfWorkAsync(Func<Task> func) |
||||
|
{ |
||||
|
return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func<Task> action) |
||||
|
{ |
||||
|
using (var scope = ServiceProvider.CreateScope()) |
||||
|
{ |
||||
|
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>(); |
||||
|
|
||||
|
using (var uow = uowManager.Begin(options)) |
||||
|
{ |
||||
|
await action(); |
||||
|
|
||||
|
await uow.CompleteAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual Task<TResult> WithUnitOfWorkAsync<TResult>(Func<Task<TResult>> func) |
||||
|
{ |
||||
|
return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<TResult> WithUnitOfWorkAsync<TResult>(AbpUnitOfWorkOptions options, |
||||
|
Func<Task<TResult>> func) |
||||
|
{ |
||||
|
using (var scope = ServiceProvider.CreateScope()) |
||||
|
{ |
||||
|
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>(); |
||||
|
|
||||
|
using (var uow = uowManager.Begin(options)) |
||||
|
{ |
||||
|
var result = await func(); |
||||
|
await uow.CompleteAsync(); |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.EShop.Plugins.Inventories.OrleansGrains; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class TestInventoryGrainProvider : IInventoryGrainProvider, ITransientDependency |
||||
|
{ |
||||
|
private IInventoryGrain Grain { get; set; } |
||||
|
|
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
public TestInventoryGrainProvider(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public Task<IInventoryGrain> GetAsync(string grainKey) |
||||
|
{ |
||||
|
return Task.FromResult(Grain ??= _serviceProvider.GetRequiredService<FakeInventoryGrain>()); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue