AVIS – funkce pro využití poolu Integračních služeb

Abychom ulehčili práci vývojářům třetích stran, kteří používají prostředí .Net 4.5 a vyšší, vytvořili jsme metodu, která pomáhá obsloužit volání poolu „Integračních služeb”. Metoda zároveň umožňuje opakovaně volat potřebnou část kódu, díky čemuž překoná bez většího úsilí krátkodobý výpadek dostupnosti Integračních služeb. Lze ji použít namísto dříve doporučované funkce AVIS – pomocná funkce pro překonání výpadku dostupnosti Integračních služeb.

Konfigurace poolu Integračních služeb

  • Vytvořte novou složku Integračních služeb, případně ve stávající pomocí parametru příkazové řádky -install vytvořte dostatečné množství instancí. (Podrobný popis instalace, registrace viz také AVIS – instalace AVIS.)

Services

  • V konfiguračním souboru nastavte RequestQueuingEnabled na False:

    <setting name="RequestQueuingEnabled" serializeAs="String">
        <value>False</value>
      </setting>

  • Nastavte u jednotlivých služeb v poolu v Registry Editor BaseUri končící pořadovým číslem – obecně: http://server/název_instance[číslo_instance]

Registry Editor

Pro přípravu, otestování provozu s Avis poolem je vhodné vytvořit novou složku aplikace AVIS, zkopírovat stávající složku, zaregistrovat a nastavit nové instance z této složky, nastavit vlastní config – zejména co se týče „RequestQueuingEnabled” False. Tím není ohrožen stávající provoz, který je naopak založen na systému fronty požadavků a jedné vyřizující instance.

Konfigurace klienta Integračních služeb

  • V konfiguračním souboru endpointů změňte URL na http://server/název_instance[nejvyšší_číslo_instance]/služba:

    <client>
      <endpoint address="http://10.1.1.1:8001/SECON[5]/Altus.COM.V12.BasicServices.Logistika"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ILogistika"
        contract="LogistikaReference.ILogistika" name="BasicHttpBinding_ILogistika" />
      <endpoint address="http://10.1.1.1:8001/SECON[5]/Altus.COM.V12.BasicServices.Sklad"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISklad"
        contract="SkladReference.ISklad" name="BasicHttpBinding_ISklad" />
      <endpoint address="http://10.1.1.1:8001/SECON[5]/Altus.COM.V12.BasicServices.Doklady"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IDoklady"
        contract="DokladyReference.IDoklady" name="BasicHttpBinding_IDoklady" />
      <endpoint address="http://10.1.1.1:8001/SECON[5]/Altus.COM.V12.BasicServices.Ceniky"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICeniky"
        contract="CenikyReference.ICeniky" name="BasicHttpBinding_ICeniky" />
      <endpoint address="http://10.1.1.1:8001/SECON[5]/Altus.COM.V12.Secon.BaliciLinka"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IBaliciLinka"
        contract="BaliciLinkaReference.IBaliciLinka" name="BasicHttpBinding_IBaliciLinka" />
    </client>

Kód je napsán v jazyku C#. Vývojáři, kteří používají jiné jazyky nebo jiné technologie, si mohou podobnou metodu napsat sami. Kód vzorové metody obsahuje komentáře, podle kterých je možné se zorientovat a funkci si přizpůsobit nebo napsat vlastní.

  • Následující metodu zkopírujte a vložte ji na vhodné místo ve vašem projektu.

//Metoda pro automatické opakované volání Integračních služeb v případě jejich výpadku

//TClient - typ "soapklienta" pro komunikaci se službou, odvozený od System.ServiceModel.ClientBase<>

//clientEndpoint - endpoint "soapklienta" pro komunikaci se službou

//repeatSection - labda výraz nebo delegát, který je potřeba provést

//remainingAdditionalAttempts  - počet opakování volání v případě, že služby nejsou dostupné

//sleepSeconds - délka intervalu v sekundách, po který se čeká mezi jednotlivými pokusy

public static void CallSafeAVIS<TClient>(System.ServiceModel.Description.ServiceEndpoint clientEndpoint, Action<TClient> repeatSection, int remainingAdditionalAttempts = 3, int sleepSeconds = 5)

    where TClient : IDisposable

{

    int maxFallback = 1;

    var originalUri = clientEndpoint.Address.Uri.OriginalString;

    if (originalUri.Contains('[') && originalUri.Contains(']'))

    {

        maxFallback = Convert.ToInt32(originalUri.Split('[', ']')[1]);

    }

 

    while (remainingAdditionalAttempts > 0)

    {

        int remainingFallbacks = maxFallback;

        while (remainingFallbacks > 0)

        {

 

            try

            {

                var uri = originalUri.Replace("[" + maxFallback.ToString() + "]", "[" + remainingFallbacks.ToString() + "]");

                var binding = clientEndpoint.Binding;

   

                remainingFallbacks--;

 

                using (TClient client = (TClient)Activator.CreateInstance(typeof(TClient), binding, new EndpointAddress(uri)))

                {

                    repeatSection.Invoke(client);

                }

 

                //dobře to dopadlo, tak konec

                return;

            }

            catch (System.ServiceModel.CommunicationException err)

            {

                Type exceptionContractType = null;

                bool isTerminalFault = true;

                bool isApplicationException = true;

                string exceptionDescription = null;

                string exceptionSource = null;

                int exceptionNumber = 0;

 

                var errType = err.GetType();

                if (!errType.IsGenericType) { }

                else if (!(errType.BaseType == typeof(System.ServiceModel.FaultException))) { }

                else if (errType.GenericTypeArguments.Length != 1) { }

                else if (err.GetType().GenericTypeArguments[0].Name != "ExceptionContract") { }

                else

                {

                    //tady máme chybu, kterou vyhodily Integrační služby,

                    //takže zjistíme, jestli jde o chybu, která vynutila restart služby

                    //v případě, že je vlastnost IsTerminalFault = true, došlo právě k restartu služby

                    exceptionContractType = err.GetType().GenericTypeArguments[0];

                    isTerminalFault = exceptionContractType.GetProperty("IsTerminalFault").GetValue(((dynamic)err).Detail);

                    isApplicationException = exceptionContractType.GetProperty("IsApplicationException").GetValue(((dynamic)err).Detail);

                    exceptionDescription = exceptionContractType.GetProperty("Description").GetValue(((dynamic)err).Detail);

                    exceptionSource = exceptionContractType.GetProperty("Source").GetValue(((dynamic)err).Detail);

                    exceptionNumber = exceptionContractType.GetProperty("Number").GetValue(((dynamic)err).Detail);

                }

 

                if (isTerminalFault && remainingFallbacks + remainingAdditionalAttempts > 1)

                {

                    //služby nejsou dočasně dostupné, zbývají ještě nějaké pokusy

                    //rovnou to zkusit znovu

                }

                else if (isTerminalFault)

                {

                    //služby nejsou dočasně dostupné

                    //došla trpělivost, oznámit chybu

                    throw;

                }

                else if (isApplicationException)

                {

                    //očekávaná aplikační chyba, víme, co se děje, a uživatel ví, co s tím udělat

                    throw new ApplicationException(exceptionDescription);

                }

                else

                {

                    //neočekávaná chyba

                    throw new ApplicationException(

                        string.Format("Chyba číslo {0} v {1}:\r\n{2}",

                        exceptionNumber,

                        exceptionSource,

                        exceptionDescription));

                }

            }

            catch (Exception err)

            {

                throw err;

            }

        }

 

        remainingAdditionalAttempts--;

        if (remainingAdditionalAttempts > 0)

        {

            System.Threading.Thread.Sleep(new TimeSpan(0, 0, sleepSeconds));

        }

    }           

}

 

  • Při volání různých metod v jedné funkci je potřeba, aby každé jedno volání bylo provedeno s pomocí metody CallSafeAVIS:

public Guid ZapsatNovyDoklad(DokladyAVIS.DokladContract doklad)

{

    Guid rg = Guid.Empty;

 

    using (var client = new DokladyAVIS.DokladyClient())

    {

        //vložíme doklad

        CallSafeAVIS<DokladyAVIS.DokladyClient>(client.Endpoint, (x) => rg = x.VlozitNovyDoklad(doklad));

               

        //doklad načteme, získáme hodnoty vyplněné šablonou

        CallSafeAVIS<DokladyAVIS.DokladyClient>(client.Endpoint, (x) => doklad = x.NacistDokladRG(rg));

               

        //provedeme nějaké úpravy a zaktualizujeme

        CallSafeAVIS<DokladyAVIS.DokladyClient>(client.Endpoint, (x) => x.AktualizovatDoklad(doklad));

    }

}