go to post Eduard Lebedyuk · Dec 7, 2020 It depends. Essentially Interoperability Productions take care of: Parallelization Queues / Async Error management, mitigation, and recovery After-error investigation (Visual Trace / Audit) Unified overview of integration flows For each integration or part of an integration you need to decide if you need these features and usually you do. In that case all you need to do is to develop one or more Business Hosts containing the business logic and as long as they conform to Interoperability Production structure you would automatically get all the above mentioned benefits. You pay for the convenience with the overhead for messages and queues. In the cases where some (most) of these conditions are true: external system (whatever it is) is reliable - downtime is a scheduled rarity external system does not throw errors much response time is stable and in the sync realm interaction with the system is one simple flow integration is extremely highload You can choose to interface more directly. Furthermore it's not Interoperability/no Interoperability, but rather a scale of how much you expose as Interoperability Hosts. In your example maybe having only a BO is enough and you can forego the BP?
go to post Eduard Lebedyuk · Dec 7, 2020 What error are you getting? Also compatibility should only be used on up, not build.
go to post Eduard Lebedyuk · Dec 7, 2020 Also note that it uses docker-compose 3.6 so it should be started in compatibility mode. This way you don't have to provision a swarm cluster.
go to post Eduard Lebedyuk · Dec 3, 2020 I run tests programmatically like this: ClassMethod runtest() As %Status { set ^UnitTestRoot = basedir set sc = ##class(%UnitTest.Manager).RunTest(testdir, "/nodelete") quit sc } ClassMethod isLastTestOk() As %Boolean { set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result) for i=1:1:in.TestSuites.Count() { #dim suite As %UnitTest.Result.TestSuite set suite = in.TestSuites.GetAt(i) return:suite.Status=0 $$$NO } quit $$$YES }
go to post Eduard Lebedyuk · Nov 30, 2020 The format is stable, use XML tools for data import/export into persistent classes.
go to post Eduard Lebedyuk · Nov 28, 2020 To be honest despite about my 7+ years of experience in exposing class methods as sql procedures I've yet to write a name of a resulting sql procedure correctly. The trick I use is to open the list of procedures in SMP: In this list I search for an already existing procedure with the same nesting level, copy it and change identifiers to my values. That's how I wrote your query. First I copied: %Atelier_v1_Utils.Extension_AfterUserAction Then replaced %Atelier_v1_Utils with com_xyz_utils Finally replaced Extension_AfterUserAction with Users_getRole
go to post Eduard Lebedyuk · Nov 27, 2020 Try SELECT id, com_xyz_utils.Users_getRole(id) FROM users.users Note that package is underscored.
go to post Eduard Lebedyuk · Nov 27, 2020 Can you share some technical details? One of the projects: 1 Mirrored Data server 4 Non-mirrored App Servers They run Apache web servers.
go to post Eduard Lebedyuk · Nov 27, 2020 I recommend InterSystems IRIS REST API backend with any js framework frontend. 10 000 simulatenous clients are easily achievable. I know of InterSystems IRIS based applications in productions with even higher number of concurrent users.
go to post Eduard Lebedyuk · Nov 24, 2020 I encounter this issue fairly often, but I need not a complete documentaiton but rather Interoperability production documentation. As all the class info is also available as a %Dictionary package I just query it and generate XLSX. Here are some queries to get started (but they usually need to be adjusted on per-project basis). Also queries should be rewritten to use SubclassOf proc instead of the current matching. Also I'm not sure why I don't pass filestream directly. That also needs to be fixed. Queries /// Generate docs Class Utils.Doc { /// Generate Docs Tables /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"defaultSettings")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionClasses")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionSystemClasses")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionSystemClassesSettings")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionClassesSettings")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionItemsSettingsCall")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionItems")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"modelClasses")) /// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"modelPropeties")) ClassMethod generate(filename As %String = {##class(%File).TempFilename("xlsx")}, query As %String, args...) As %Status { #dim sc As %Status = $$$OK #dim stream As %Stream.TmpCharacter = ##class(%Stream.TmpCharacter).%New() set sql = ..getSQL(query) set sc = ##class(util.XLSX).generateStreamFromSQL(.stream, sql, args...) quit:$$$ISERR(sc) sc set file = ##class(%FileCharacterStream).%New() set:$p(filename,".",*)'="xlsx" filename = filename _".xlsx" set file.Filename = filename set sc = file.CopyFrom(.stream) quit:$$$ISERR(sc) set sc = file.%Save() quit sc } ClassMethod getSQL(name) As %String { #dim sc As %Status = $$$OK set class = $case($l(name, ":"), 2:$p(name, ":"), :$classname()) set queryName = $p(name, ":", *) if ##class(%Dictionary.QueryDefinition).IDKEYExists(class, queryName) { set query = ##class(%Dictionary.QueryDefinition).IDKEYOpen(class, queryName,,.sc) throw:$$$ISERR(sc) ##class(%Exception.StatusException).CreateFromStatus(sc) set sql = query.SqlQuery } elseif ##class(%Dictionary.XDataDefinition).IDKEYExists(class, queryName) { #dim stream As %Stream.Object = ##class(isc.util.XmlUtils).getClassXData(class, queryName) set sql = stream.Read($$$MaxLocalLength) } else { throw ##class(%Exception.StatusException).CreateFromStatus($$$ERROR($$$GeneralError, $$$FormatText("Class %1 does not have Query or XData with name %2", class, queryName))) } set:(removeNL = $$$YES) sql = $replace(sql, $$$NL, " ") return sql } /// Generic settings for all Interoperability BH Query defaultSettings() As %Query { SELECT prop.name Setting, TRIM('"' FROM MAX(prop.InitialExpression)) "Default", LIST(prop.parent) Classes, MAX(prop.Description) Descrition FROM %Dictionary.PropertyDefinition prop JOIN %Dictionary.CompiledParameter par ON par.parent = prop.parent AND par.Name = 'SETTINGS' JOIN %Dictionary.CompiledClass cls ON prop.parent = cls.Name WHERE (cls.Super LIKE '%Ens.Host%' OR cls.Name = 'Ens.Host' OR cls.Name = 'Ens.BusinessProcessBPL') AND par."_Default" LIKE '%' || prop.Name || '%' GROUP BY prop.Name ORDER BY 1 } /// Production BHs Query productionClasses() As %Query { SELECT Name Class, Description Descrition FROM %Dictionary.CompiledClass cls WHERE Name LIKE 'production.%' AND Super In ('Ens.BusinessProcessBPL', 'Ens.BusinessService', 'Ens.BusinessOperation', 'Ens.BusinessProcess') ORDER BY 1 } /// System BHs Query productionSystemClasses() As %Query { SELECT Name Class, Description Descrition FROM %Dictionary.CompiledClass cls WHERE Name In ('util.SQLInboundAdapter', 'EnsLib.SQL.InboundAdapter', 'EnsLib.JavaGateway.Service', 'EnsLib.Workflow.Operation', 'Ens.InboundAdapter') ORDER BY 1 } /// System settings for BHs Query productionSystemClassesSettings() As %Query { SELECT prop.parent Class, prop.name Setting, TRIM('"' FROM prop.InitialExpression) "Default", prop.Description Descrition FROM %Dictionary.PropertyDefinition prop JOIN %Dictionary.CompiledParameter par ON par.parent = prop.parent AND par.Name = 'SETTINGS' JOIN %Dictionary.CompiledClass cls ON prop.parent = cls.Name WHERE cls.Name IN ('util.SQLInboundAdapter', 'EnsLib.SQL.InboundAdapter', 'EnsLib.JavaGateway.Service', 'EnsLib.Workflow.Operation', 'Ens.InboundAdapter') AND par."_Default" LIKE '%' || prop.Name || '%' ORDER BY 1,2 } /// BHs Settings Query productionClassesSettings(production) As %Query { SELECT prop.parent Class, prop.name Setting, TRIM('"' FROM prop.InitialExpression) "Default", prop.Description Descrition FROM %Dictionary.PropertyDefinition prop JOIN %Dictionary.CompiledParameter par ON par.parent = prop.parent AND par.Name = 'SETTINGS' JOIN %Dictionary.CompiledClass cls ON prop.parent = cls.Name WHERE cls.Name LIKE :production || '.%' and par."_Default" LIKE '%' || prop.Name || '%' ORDER BY 1,2 } /// Production BH Query productionItems(production) As %Query { SELECT Name Элемент, ClassName Class /*Comment Comment*/ FROM Ens_Config.Item WHERE Production = :production } /// Get settings for production Query productionItemsSettingsCall(production) As %Query { SELECT * FROM util.productionItemsSettings(:production) } /// Get settings for production Query productionItemsSettings(production As %String) As %Query(CONTAINID = 0, ROWSPEC = "Элемент:%String,Setting:%String,Цель:%String,Значение:%String") [ SqlName = productionItemsSettings, SqlProc ] { } ClassMethod productionItemsSettingsExecute(ByRef qHandle As %Binary, production As %String) As %Status { set obj = ##class(Ens.Config.Production).%OpenId(production,,.sc) quit:$$$ISERR(sc) sc do ..clearProductionItems(.obj) set qHandle("production") = obj set qHandle("item") = 1 set qHandle("itemCount") = qHandle("production").Items.Count() set qHandle("setting") = 1 set qHandle("settingCount") = qHandle("production").Items.GetAt(qHandle("item")).Settings.Count() quit sc } ClassMethod productionItemsSettingsFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = productionItemsSettingsExecute ] { #dim sc As %Status = $$$OK #dim item As Ens.Config.Item = qHandle("production").Items.GetAt(qHandle("item")) #dim setting As Ens.Config.Setting = item.Settings.GetAt(qHandle("setting")) set AtEnd = 0 set Row = $lb(item.Name, setting.Name, setting.Target, setting.Value) if qHandle("setting")<qHandle("settingCount") { set qHandle("setting") = qHandle("setting") + 1 } else { if qHandle("item")<qHandle("itemCount") { set qHandle("item") = qHandle("item") + 1 set qHandle("setting") = 1 set qHandle("settingCount") = qHandle("production").Items.GetAt(qHandle("item")).Settings.Count() } else { set AtEnd = 1 } } quit sc } /// Remove setting-less hosts ClassMethod clearProductionItems(ByRef production As Ens.Config.Production) { #dim item As Ens.Config.Item for i = production.Items.Count():-1:1 { set item = production.Items.GetAt(i) do:item.Settings.Count()=0 production.Items.RemoveAt(i) } } ClassMethod productionItemsSettingsClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = productionItemsSettingsFetch ] { kill qHandle quit $$$OK } /// Data model Query modelClasses() As %Query { SELECT Name Class, Description Descrition FROM %Dictionary.ClassDefinition WHERE Name LIKE 'model.%' AND GeneratedBy IS NULL -- AND Name NOT LIKE 'model.ref.%' } /// Data model properties Query modelPropeties() As %Query { SELECT cls.Name Class, prop.Name Свойство, prop.Type Тип, prop.Description Descrition FROM %Dictionary.ClassDefinition cls JOIN %Dictionary.PropertyDefinition prop ON cls.Name = prop.parent WHERE cls.Name LIKE 'model.%' AND cls.Name NOT LIKE 'model.ref.%' } }
go to post Eduard Lebedyuk · Nov 24, 2020 $zf functions (3-6 to be precise) cannot call arbitrary libraries but only InterSystems IRIS callout libraries. You need to write a C library which is an InterSystems IRIS callout library and which calls the dll you need. Here's documentation on that. And here's a sample callout library. Another approach would be using the CNA community project to call your library. CNA provides an interface for using native C-compatible shared libraries.
go to post Eduard Lebedyuk · Nov 17, 2020 Moved to Kitty from Putty a few years ago and it's way better for me.
go to post Eduard Lebedyuk · Nov 17, 2020 Check: Using the FTP Inbound Adapter Using the File Inbound Adapter
go to post Eduard Lebedyuk · Nov 15, 2020 Looks like a user permissions issue. question: which user does the Cache-terminal login as, is this different to the win10-services cache.exe settings. Yes, the terminal works under your OS user, Cache (and InterSystems IRIS) background jobs work under system account user (you can check services - Cache to see the user). You need to give permissions to access the share to system account user.
go to post Eduard Lebedyuk · Nov 13, 2020 As I said use object access instead. I have written a code sample which can be used with Native API for Python (although the first part with directory creation should probably just be called from PYTHON as is if you're on a same machine). Locals are not supported by Native API for Python.