Skip to content

Commit

Permalink
various improvements for v5 (#283)
Browse files Browse the repository at this point in the history
use IO in transaction handlers
The transaction handlers now have to return Eff<RT, RfcRc>. Any error will be mapped to RfcRc.EXTERNAL_FAILURE as specified by SAP.
fixed code documentations
improved stability of mapper caching
fixed some code qualitity issues
improved samples
reduced traits to HasSAPRfc and HasSAPRfcServer
fixed runIO to pass IConnection reference
  • Loading branch information
fw2568 authored Mar 20, 2024
1 parent 2a1492b commit f53cfd5
Show file tree
Hide file tree
Showing 90 changed files with 1,333 additions and 814 deletions.
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@

# IDE1006: Naming Styles
dotnet_diagnostic.IDE1006.severity = silent

# IDE0290: Use primary constructor
dotnet_diagnostic.IDE0290.severity = silent

# IDE0028: Simplify collection initialization
dotnet_diagnostic.IDE0028.severity = silent
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,11 @@ var serverBuilder = new ServerBuilder(serverSettings)
.WithTransactionalRfc(new MyTransactionRfcHandler())

// MyTransactionRfcHandler has to implement interface
// ITransactionalRfcHandler
// ITransactionalRfcHandler<RT>
public interface ITransactionalRfcHandler
public interface ITransactionalRfcHandler<RT>
{
RfcRc OnCheck(IRfcRuntime rfcRuntime,
Eff<RT,RfcRc> OnCheck(
IRfcHandle rfcHandle, string transactionId);

...
Expand Down
2 changes: 2 additions & 0 deletions YaNco.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=BAPIRET/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Configurer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dbosoft/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=IDOC/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Interopt/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=KPRO/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MATMAS/@EntryIndexedValue">True</s:Boolean>
Expand All @@ -16,4 +17,5 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=sapftp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=saphttp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=SAPI/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=saprfc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sysid/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
106 changes: 106 additions & 0 deletions samples/net6.0/ExportMATMAS/ConsoleRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System.Text;
using Dbosoft.YaNco;
using Dbosoft.YaNco.Live;
using Dbosoft.YaNco.Traits;
using Dbosoft.YaNco.TypeMapping;
using ExportMATMAS.MaterialMaster;
using LanguageExt;
using LanguageExt.Sys.Traits;

namespace ExportMATMAS;

/// <summary>
/// Runtime environment for the console application
/// It provides access to the SAP RFC runtime environment, the console and file IO
/// It is expected that you create your own runtime environment for your application.
/// </summary>
public readonly struct ConsoleRuntime :
HasConsole<ConsoleRuntime>,
HasFile<ConsoleRuntime>,
HasSAPRfc<ConsoleRuntime>,
HasSAPRfcServer<ConsoleRuntime>,
HasMaterialManager<ConsoleRuntime>
{

private readonly SAPRfcRuntimeEnv<SAPServerSettings> _env;

/// <summary>
/// Constructor
/// </summary>
private ConsoleRuntime(SAPRfcRuntimeEnv<SAPServerSettings> env) =>
_env = env;


/// <summary>
/// Configuration environment accessor
/// </summary>
public SAPRfcRuntimeEnv<SAPServerSettings> Env =>
_env ?? throw new InvalidOperationException("Runtime Env not set. Perhaps because of using default(Runtime) or new Runtime() rather than Runtime.New()");

SAPRfcRuntimeEnv<SAPRfcRuntimeSettings> IHasEnvRuntimeSettings.Env =>
Env.ToRuntimeSettings();


/// <summary>
/// Constructor function
/// </summary>
public static ConsoleRuntime New(CancellationTokenSource cancellationTokenSource, SAPServerSettings settings) =>
new(new SAPRfcRuntimeEnv<SAPServerSettings>(cancellationTokenSource, settings));


/// <summary>
/// Create a new Runtime with a fresh cancellation token
/// </summary>
/// <remarks>Used by localCancel to create new cancellation context for its sub-environment</remarks>
/// <returns>New runtime</returns>
public ConsoleRuntime LocalCancel =>
new(new SAPRfcRuntimeEnv<SAPServerSettings>(new CancellationTokenSource(), Env.Settings));

/// <summary>
/// Direct access to cancellation token
/// </summary>
public CancellationToken CancellationToken =>
Env.Token;

/// <summary>
/// Directly access the cancellation token source
/// </summary>
/// <returns>CancellationTokenSource</returns>
public CancellationTokenSource CancellationTokenSource =>
Env.Source;

public Eff<ConsoleRuntime, ConsoleIO> ConsoleEff =>
Prelude.SuccessEff(LanguageExt.Sys.Live.ConsoleIO.Default);

public Encoding Encoding => Encoding.UTF8;

public Eff<ConsoleRuntime, FileIO> FileEff => Prelude.SuccessEff(LanguageExt.Sys.Live.FileIO.Default);

public Option<ILogger> RfcLogger => Env.Settings.Logger == null ? Option<ILogger>.None : Prelude.Some(Env.Settings.Logger);

private SAPRfcDataIO DataIO => Env.Settings.RfcDataIO ?? new LiveSAPRfcDataIO(RfcLogger, Env.Settings.FieldMapper, Env.Settings.Options);
private SAPRfcFunctionIO FunctionIO => Env.Settings.RfcFunctionIO ?? new LiveSAPRfcFunctionIO(RfcLogger, DataIO);
private SAPRfcConnectionIO ConnectionIO => Env.Settings.RfcConnectionIO ?? new LiveSAPRfcConnectionIO(RfcLogger);
private SAPRfcServerIO ServerIO => Env.Settings.RfcServerIO ?? new LiveSAPRfcServerIO(RfcLogger);

public Eff<ConsoleRuntime, Option<ILogger>> RfcLoggerEff => Prelude.Eff<ConsoleRuntime, Option<ILogger>>(rt => rt.RfcLogger);

public Eff<ConsoleRuntime, SAPRfcDataIO> RfcDataEff => Prelude.Eff<ConsoleRuntime, SAPRfcDataIO>(rt => rt.DataIO);


public Eff<ConsoleRuntime, SAPRfcFunctionIO> RfcFunctionsEff =>
Prelude.Eff<ConsoleRuntime, SAPRfcFunctionIO>(rt => rt.FunctionIO);

public Eff<ConsoleRuntime, IFieldMapper> FieldMapperEff =>
Prelude.Eff<ConsoleRuntime, IFieldMapper>(rt => rt.Env.Settings.FieldMapper);

public Eff<ConsoleRuntime, SAPRfcConnectionIO> RfcConnectionEff => Prelude.Eff<ConsoleRuntime, SAPRfcConnectionIO>(
rt => rt.ConnectionIO);

public Eff<ConsoleRuntime, SAPRfcServerIO> RfcServerEff => Prelude.Eff<ConsoleRuntime, SAPRfcServerIO>(
rt => rt.ServerIO);

public Eff<ConsoleRuntime, TransactionManager<MaterialMasterRecord>> MaterialManagerEff =>
Prelude.Eff<ConsoleRuntime, TransactionManager<MaterialMasterRecord>>(rt =>
rt.Env.Settings.TaManager);
}
4 changes: 2 additions & 2 deletions samples/net6.0/ExportMATMAS/ExportMATMAS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LanguageExt.Sys" Version="4.4.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\YaNco.Hosting\YaNco.Hosting.csproj" />
<ProjectReference Include="..\..\..\src\YaNco\YaNco.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions samples/net6.0/ExportMATMAS/HasMaterialManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using ExportMATMAS.MaterialMaster;
using LanguageExt;
using LanguageExt.Effects.Traits;

namespace ExportMATMAS;

public interface HasMaterialManager<RT> : HasCancel<RT>
where RT : struct, HasCancel<RT>
{
Eff<RT, TransactionManager<MaterialMasterRecord>> MaterialManagerEff { get; }
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ExportMATMAS;
namespace ExportMATMAS.MaterialMaster;

public record ClientData(string Unit);
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ExportMATMAS;
namespace ExportMATMAS.MaterialMaster;

public record DescriptionData(string Language, string Description);
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ExportMATMAS;
namespace ExportMATMAS.MaterialMaster;

public record MaterialMasterRecord(string MaterialNo, ClientData ClientData, DescriptionData[] Descriptions, PlantData[] PlantData);
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using Dbosoft.YaNco;
using System.Text.Encodings.Web;
using System.Text.Json;
using LanguageExt;
using LanguageExt.Sys;
using LanguageExt.Sys.Traits;

namespace ExportMATMAS.MaterialMaster;


/// <summary>
/// This is a sample implementation of a transactional RFC handler.
/// </summary>
public class MaterialMasterTransactionalRfcHandler<RT> : ITransactionalRfcHandler<RT>
where RT : struct, HasConsole<RT>, HasMaterialManager<RT>
{

public Eff<RT, RfcRc> OnCheck(IRfcHandle rfcHandle, string transactionId) =>
from uLog in Console<RT>.writeLine($"Checking transaction '{transactionId}'")
from tm in default(RT).MaterialManagerEff
let result = tm.GetTransaction(transactionId).Match(
None: () =>
{
tm.AddTransaction(transactionId);
return RfcRc.RFC_OK;
},
Some: ta =>
{
//check if TA is in valid state, inform sender in case TA is already executed
return ta.State switch
{
TransactionState.Created => RfcRc.RFC_OK,
TransactionState.Executed => RfcRc.RFC_EXECUTED,
TransactionState.Committed => RfcRc.RFC_EXECUTED,
TransactionState.RolledBack => RfcRc.RFC_OK,
_ => throw new ArgumentOutOfRangeException()
};
})
select result;

public Eff<RT, RfcRc> OnCommit(IRfcHandle rfcHandle, string transactionId) =>
from uLog in Console<RT>.writeLine($"Commit transaction '{transactionId}'")
from tm in default(RT).MaterialManagerEff
from ta in tm.GetTransaction(transactionId)
.ToEff(RfcError.Error($"Transaction {transactionId} not found", RfcRc.RFC_EXTERNAL_FAILURE))

from gNotExecuted in Prelude.guard(ta.State == TransactionState.Executed,
RfcError.Error($"Transaction {transactionId} not executed", RfcRc.RFC_EXTERNAL_FAILURE).AsError)
from gHasData in Prelude.guardnot(ta.Data == null, RfcError.Error($"Transaction {transactionId} has no data", RfcRc.RFC_EXTERNAL_FAILURE).AsError)


// where you would normally commit the transaction, but here we just print the data
from printData in PrettyPrintMaterial(ta.Data)
from uPrint in Console<RT>.writeLine(printData)

from rc in Prelude.Eff(() =>
{
ta.State = TransactionState.Committed;
return RfcRc.RFC_OK;
})
select rc;

public Eff<RT, RfcRc> OnRollback(IRfcHandle rfcHandle, string transactionId) =>
from uLog in Console<RT>.writeLine($"Rollback transaction '{transactionId}'")
from tm in default(RT).MaterialManagerEff
let result = tm.GetTransaction(transactionId).Match(
None: () => RfcRc.RFC_EXTERNAL_FAILURE,
Some: ta =>
{
ta.State = TransactionState.RolledBack;
return RfcRc.RFC_OK;
})
select result;

public Eff<RT, RfcRc> OnConfirm(IRfcHandle rfcHandle, string transactionId) =>
from uLog in Console<RT>.writeLine($"Confirm transaction '{transactionId}'")
from tm in default(RT).MaterialManagerEff
from result in Prelude.Eff<RT, RfcRc>(_ =>
{
// cleanup transaction data
tm.RemoveTransaction(transactionId);
return RfcRc.RFC_OK;
}) select result;

private static Eff<string> PrettyPrintMaterial(MaterialMasterRecord record)
{
return Prelude.Eff( () =>JsonSerializer.Serialize(record, Json.JsonOptions));
}

}

internal class Json
{
public static readonly JsonSerializerOptions JsonOptions = new()
{
//json don't has to be valid, so disable text encoding for unicode chars
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using LanguageExt;
// ReSharper disable StringLiteralTypo

namespace ExportMATMAS;
namespace ExportMATMAS.MaterialMaster;

// for a known IDoc type you used fixed segment to type mapping
// a more generic way would be looking up segment names from RFM IDOCTYPE_READ_COMPLETE

public static class MatmasTypes
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace ExportMATMAS;
namespace ExportMATMAS.MaterialMaster;

public record PlantData(string Plant, string PurchasingGroup);
Loading

0 comments on commit f53cfd5

Please sign in to comment.