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