Limpiar filtro
Artículo
Kurro Lopez · 22 oct, 2019
¡Hola a tod@s!
En este artículo voy a hablar sobre los Globals, esas espadas mágicas para almacenar datos, que han estado con nosotros desde hace tiempo, pero no mucha gente las utiliza de forma eficiente o realmente conoce esta súper herramienta.
Si se utilizan globals para realizar las tareas en donde realmente brillan, los resultados pueden ser sorprendentes, ya sea en términos de un mayor rendimiento o en una simplificación drástica de la solución en general (1, 2).
Globals ofrecen una forma especial de almacenar y procesar datos, la cual es completamente diferente de las tablas SQL. Se introdujeron por primera vez en 1966 con el lenguaje de programación M(UMPS), donde inicialmente se utilizaron en las bases de datos médicas. Todavía se usan de la misma manera, pero también fueron adoptados por otras industrias donde la confiabilidad y el alto rendimiento son la máxima prioridad (como en las finanzas, las operaciones comerciales, etc.)
Posteriormente, el M(UMPS) evolucionó hasta convertirse en Caché ObjectScript (COS), el cual se desarrolló por InterSystems como un superconjunto de M. El lenguaje original aún es aceptado por la Comunidad de Desarrolladores y persiste en algunas implementaciones. Existen varias señales de que todavía tiene actividad en la web: Grupos de Google sobre MUMPS, Grupo de usuarios de Mumps, Normas ISO vigentes, etc.
Los globals que se basan en el moderno Sistema de Gestión de Bases de Datos (DBMS) son compatibles con transacciones, registros, replicaciones y particiones. Esto significa que pueden utilizarse para desarrollar sistemas de distribución que sean modernos, confiables y rápidos.
Los globals no lo restringirán a las limitaciones del modelo relacional. Más bien, le darán libertad para crear estructuras de datos optimizadas con el fin de realizar tareas particulares. Para muchas aplicaciones, el uso razonable de los globals puede ser una verdadera solución inmediata, la cual ofrece velocidades que los desarrolladores de aplicaciones relacionales convencionales solamente pueden soñar.
Los globals funcionan como un método para almacenar datos que puede utilizarse con muchos lenguajes de programación modernos, ya sean de nivel superior o inferior. Por lo tanto, este artículo se centrará específicamente en los globals y no en el lenguaje del que proceden.
2. Cómo funcionan los globals
Primero, entenderemos cómo funcionan los globals y cuáles son las ventajas de utilizarlos. Los globals pueden verse desde diferentes perspectivas. En esta parte del artículo los analizaremos como árboles o como almacenes jerárquicos de datos.
Dicho de forma sencilla, un global es un conjunto persistente. Un conjunto que se guarda automáticamente en el disco.
Es difícil imaginar algo más sencillo para almacenar datos. En el código del programa (escrito con el lenguaje COS/M), la única diferencia que existe respecto a un conjunto asociativo normal es el símbolo ^ que se encuentra antes de sus nombres.
No es necesario tener conocimientos previos de SQL para guardar datos en un global, ya que todos los comandos necesarios son realmente sencillos y pueden aprenderse en una hora.
Comencemos con el ejemplo más sencillo, un árbol de un solo nivel que tiene dos ramas. Los ejemplos están escritos en COS.
Set ^a("+7926X") = "John Sidorov"
Set ^a("+7916Y") = "Sergey Smith"
Cuando los datos se insertan en un global (mediante el comando Set), automáticamente suceden 3 cosas:
Se guardan los datos en el disco.
Se crean índices. Lo que está entre paréntesis es un subíndice, y lo que está a la derecha del signo igual es el valor del nodo "а".
Se ordenan. Los datos se ordenan mediante un elemento. El próximo recorrido colocará a «Sergey Smith» en la primera posición, seguido por «John Sidorov». Cuando se obtiene una lista de usuarios desde un global, la base de datos no dedica tiempo a ordenarla. De hecho, puede solicitar que la lista comience a ordenarse desde cualquier elemento, incluso cuando no exista uno (la salida comenzará a partir del primer elemento verdadero y seguirá después de este).
Todas estas operaciones se realizan a una velocidad increíble. En mi equipo doméstico (que cuenta con las siguientes especificaciones: i5-3340, 16GB, HDD WD 1TB Blue) logré alcanzar las 1,050,000 inserciones/segundo en un solo proceso. En equipos con procesadores multinúcleo, las velocidades pueden alcanzar docenas de milliones de inserciones/segundo.
Desde luego, la velocidad de inserción del registro no proporciona mucha información. Por ejemplo, podemos escribir los datos en archivos de texto, según los rumores, así es como funciona el procesamiento de los datos en Visa. Sin embargo, con los globals, obtenemos un almacenamiento estructurado e indexado que permite trabajar mientras se disfruta de su alta velocidad y facilidad de uso.
La mayor fortaleza de los globals es la velocidad con que es posible insertar nuevos nodos dentro de ellos
Los datos siempre se indexan en un global. Los recorridos en los árboles con un solo nivel y profundidad siempre son muy rápidos
Vamos a añadir algunas ramas de segundo y tercer nivel en el global.
Set ^a("+7926X", "city") = "Moscow"
Set ^a("+7926X", "city", "street") = "Req Square"
Set ^a("+7926X", "age") = 25
Set ^a("+7916Y", "city") = "London"
Set ^a("+7916Y", "city", "street") = "Baker Street"
Set ^a("+7916Y", "age") = 36
Aparentemente, puede construir árboles de varios niveles utilizando globals. El acceso a cualquier nodo es casi instantáneo gracias a la indexación automática que se realiza después de cada inserción. Las ramas de los árboles de cualquier nivel se ordenan mediante un elemento.
Como puede ver, tanto los datos como los valores pueden almacenarse en elementos. La longitud combinada de un elemento (la suma de las longitudes de todos los índices) puede alcanza 511 bytes, y los valores pueden alcanzar hasta 3.6 MB de tamaño en el Caché. El número de niveles de un árbol (número de dimensiones) se limita a 31.
Una última cosa que también es genial: puede construir un árbol sin definir los valores de los nodos de nivel superior.
Set ^b("a", "b", "c", "d") = 1
Set ^b("a", "b", "c", "e") = 2
Set ^b("a", "b", "f", "g") = 3
Los círculos vacíos son nodos que no tienen ningún valor.
Para entender mejor los globals, los compararemos con otros árboles: los árboles en los jardines y los árboles de nombres en el sistema de archivos.
Compararemos los globales con las estructuras jerárquicas más comunes: los árboles que generalmente crecen en los jardines y campos, al igual que los sistemas de archivos.
Como podemos ver, las hojas y los frutos solamente crecen en los extremos de las ramas de los árboles comunes.Sistemas de archivos – La información también se almacena en los extremos de las ramas, también conocida como nombres completos de los archivos.
Aquí puede ver la estructura de los datos en un global.
Las diferencias son:
Nodos internos: en un global la información puede almacenarse en cada nodo, no solo en los extremos de las ramas
Nodos externos: los globals deben tener extremos definidos en sus ramas (extremos con valores), aunque esto no es obligatorio para el sistema de archivos y los árboles en los jardines
En cuanto a los nodos internos, podemos referirnos a la estructura del global como un superconjunto que contiene los árboles con nombres de los sistemas de archivos y las estructuras que conforman los árboles en un jardín. De modo que la estructura del global es más flexible.
En general, un global es un árbol estructurado que es compatible con el almacenamiento de datos en cada nodo.
Para entender mejor cómo funcionan los globals, imaginemos: ¿qué pasaría si los creadores de un sistema de archivos utilizaran un enfoque idéntico al de los globals para almacenar información?
Si el último archivo de una carpeta se eliminara, también se eliminaría la misma carpeta, así como todas las carpetas de nivel superior que solamente contenían la carpeta que se eliminó.
No habría necesidad de utilizar carpetas. Tendríamos archivos con subarchivos y archivos sin subarchivos. Si compara esto con un árbol normal, cada rama se convertiría en una fruta.
Probablemente ya no se necesitarían cosas como README.txt. Todo lo que quisiera decir sobre el contenido de una carpeta podría escribirse en el propio archivo dentro la carpeta. Generalmente, los nombres de los archivos no se distinguen de los nombres de las carpetas (por ejemplo, /etc/readme puede ser cualquiera de los dos, el archivo o la carpeta), lo cual significa que no tendríamos ningún problema si solo manipulamos los archivos.
Las carpetas con subcarpetas y archivos podrían eliminarse mucho más rápido. Existen artículos en la red que relatan historias sobre lo difícil y laborioso que es eliminar millones de archivos pequeños (1, 2, 3). Sin embargo, si crea un sistema de pseudo archivos basado en un global, esto solo llevará segundos o incluso fracciones de segundo. Cuando probé la eliminación de subárboles en el equipo PC que tengo en mi casa, logré eliminar desde 96 hasta 341 millones de nodos de un árbol con dos niveles en un HDD (no en un SDD). Es importante mencionar que nos referimos a la eliminación de una sección en un árbol global, no a la eliminación de un archivo completo que contenga un global.
La eliminación de subárboles es otra de las ventajas de los globals: no necesita la recurrencia para realizar esto. Es increíblemente rápido.
En nuestro árbol, esto puede realizarse mediante el comando Kill.
Kill ^a("+7926X")
A continuación se muestra una pequeña tabla que le permitirá comprender mejor las acciones que puede realizar en un global.
Comandos y funciones clave relacionados con los globales en COS
Set
Configura (inicializa) las ramificaciones hasta uno de los nodos (si no se definieron) y el valor del nodo
Merge
Realiza la copia de un subárbol
Kill
Elimina un subárbol
ZKill
Elimina el valor de un nodo específico. No afecta al subárbol que se deriva del nodo
$Query
Muestra el recorrido y la profundidad completa del árbol
$Order
Devuelve el siguiente subíndice que está en el mismo nivel
$Data
Comprueba si un nodo está definido
$Increment
Muestra el incremento atómico del valor de un nodo para evitar que ACAD (ACID, por sus siglas en inglés) lo lea y escriba. Por último, se recomienda utilizar $Sequence en su lugar.
Gracias por leer mi artículo, estaré encantado de responder vuestras preguntas.
Descargo de responsabilidad: Este artículo refleja la opinión personal del autor y no tiene ninguna relación con la opinión oficial de InterSystems.
Artículo
Yuri Marx · 15 ene, 2021
¡Hola Comunidad!
En este artículo, comparo las características de los principales líderes en Operational Database Management Systems (ODBMS) del cuadrante mágico de Gartner (2019). La lista está clasificada por número de características existentes.
InterSystems IRIS 2020.3 - 58 características (https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls)
Oracle Database 21c - 54 características (https://docs.oracle.com/en/database/oracle/oracle-database/index.html)
Microsoft SQL Server - 45 características (https://docs.microsoft.com/en-us/sql/sql-server/?view=sql-server-ver15)
AWS Aurora - PostgreSQL - 34 características (https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_AuroraOverview.html)
Solo comparo características, no rendimiento (ver el informe ESG para esa comparación). Para realizar este artículo, utilicé los enlaces a la documentación oficial de cada producto, mostrados más arriba.
Esta es la tabla con la comparación:
Características
InterSystemsIRIS 2020.3
OracleDatabase 21c
MicrosoftSQL Server 2020
AWS Aurora -PostgreSQL
Failover Cluster
Sí
Sí
Sí
Sí
Mirroring/Data Replication
Sí
Sí
Sí
Sí
Distributed Cache/In Memory support
Sí
Sí
Sí
Sí
Backup/Restore - Incremental and Full
Sí
Sí
Sí
Sí
Vertical Scaling
Sí
Sí
Sí
Sí
Horizontal Scaling for Insert, Update and Delete
Sí
Sí
No
No
Horizontal Scaling for Select
Sí
Sí
Sí
Sí
Sharded Cluster
Sí
Sí
No
Sí
Cloud Support and Cloud Manager
Sí
Sí
Sí
Sí
Kubernetes Support and Kubernetes Manager
Sí
Sí
Sí
Sí
Docker support
Sí
Sí
Sí
No
AWS Hosting
Sí
Sí
Sí
Sí
Azure Hosting
Sí
Sí
Sí
No
Google Cloud Hosting
Sí
Sí
Sí
No
Managed Cloud
Sí
Sí
Sí
Sí
On-premises support
Sí
Sí
Sí
No
Multimodel - OO
Sí
No
No
No
Multimodel - Document - JSON
Sí
Sí
Sí
Sí
Multimodel - XML
Sí
Sí
Sí
Sí
Multimodel - Key/Value
Sí
No
No
No
Multimodel - SQL
Sí
Sí
Sí
Sí
Multimodel - Spatial
No
Sí
Sí
Sí
Multimodel - Graph
No
Sí
Sí
No
Multimodel - OLAP Cubes
Sí
Sí
Sí
No
GIS platform
No
Sí
No
No
Native OO programming language
Sí
Sí
No
No
Java, .Net, Python, C/C++ and PHP support
Sí
Sí
Sí
Sí
Node.js support
Sí
Sí
Sí
Sí
ODBC/JDBC support
Sí
Sí
Sí
Sí
Backend Application Development
Sí
Sí
No
No
Frontend Application Development
Sí
Sí
No
No
Low Code Web Application Development
No
Sí
Sí
No
Database Application Development
Sí
Sí
Sí
Sí
OData support
No
Sí
Sí
No
REST Services
Sí
Sí
Sí
Sí
SOAP Services
Sí
No
No
No
Terminal tools
Sí
Sí
Sí
Sí
IDE Support
Sí
Sí
Sí
Sí
Web Admin/IDE Support
Sí
Sí
Sí
Sí
Embedded NLP
Sí
No
No
No
Embedded AutoML
Sí
No
No
No
R/Machine Learning support
Sí
Sí
Sí
Sí
PMML
Sí
No
No
No
Business Report Server/Development
Sí
Sí
Sí
No
Autonomous AI operation
No
Sí
No
No
Unstructured Text Annotations/Apache UIMA Like
Sí
Sí
No
No
Spark support
Sí
Sí
Sí
Sí
BI tool
Sí
No
Sí
No
MDX Support
Sí
Sí
Sí
No
Interoperability Connectors
Sí
No
No
No
BPEL/Integration Orchestration/Workflows
Sí
No
No
No
ETL - Extract, Transform and Load data
Sí
No
No
No
IoT/MQTT support
Sí
No
No
No
EDI support
Sí
No
No
No
ESB
Sí
No
No
No
CDC - Change Data Capture
Sí
Sí
Sí
Sí
RBAC model
Sí
Sí
Sí
Sí
LDAP support
Sí
Sí
Sí
Sí
Authorization/Authentication with two factor support
Sí
Sí
Sí
Sí
Cryptography
Sí
Sí
Sí
Sí
Labeling
Sí
Sí
Sí
No
Audit and trace
Sí
Sí
Sí
Sí
SAM
Sí
Sí
Sí
Sí
Multi OS support
Sí
Sí
Sí
Sí
SAML/Oauth/OpenID support
Sí
Sí
Sí
Sí
Performance Tunning IDE/Pack
No
Sí
No
No
Previleged User Access Management
No
Sí
No
No
Total de Características
58
54
45
34
Artículo
Muhammad Waseem · 28 jul, 2022
![ObjectScript Kernel Logo][ObjectScript Kernel Logo]
[Jupyter Notebook](https://jupyter.org/) es un entorno interactivo formado por celdas que permiten ejecutar código en un gran número de lenguajes de marcado y programación diferentes.
Para hacer esto, Jupyter debe conectarse a un kernel apropiado. No había un Kernel ObjectScript, por lo que decidí crear uno.
Puedes probarlo [aquí](objectscriptkernel.eastus.cloudapp.azure.com).
Este es un adelanto de los resultados:

## Jupyter Kernels 101
Hay varias formas de crear un [Jupyter Kernel](https://jupyter-client.readthedocs.io/en/stable/kernels.html). Decidí hacer un kernel contenedor de Python.
Tenemos que crear una subclase de ```ipykernel.kernelbase.Kernel``` e implementar el método ```do_execute``` que recibe un código para ser ejecutado en un idioma en particular.
Entonces, la idea general es obtener un fragmento de código ObjectScript, ejecutarlo de alguna manera y devolver los resultados a nuestro cuaderno.
Pero, ¿cómo hacemos eso exactamente? Vamos a tratar de analizarlo un poco más.
## Envío de código ObjectScript a IRIS
Para empezar, tenemos que enviar nuestro fragmento de código a IRIS. Aquí es donde [la API Nativa de IRIS para Python](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=PAGE_PYTHON_NATIVE) es necesaria.
Todo lo que tenemos que hacer es importar el paquete ```irisnative```, luego establecer una conexión:
```
def get_iris_object():
# Crear conexión con InterSystems IRIS
connection = irisnative.createConnection('iris', 51773, 'IRISAPP', '_SYSTEM', 'SYS')
# Crear un objeto de iris
return irisnative.createIris(connection)
```
Después de eso, podemos usar la conexión para llamar a las clases que están almacenadas en la base de datos de IRIS.
```
def execute_code(self, code):
class_name = "JupyterKernel.CodeExecutor"
return self.iris.classMethodValue(class_name, "CodeResult", code)
```
¿Para qué se utilizan esta clase ```Codes Executor``` y el método ```CodeResult```?
Vamos a verlo.
## Ejecutar código ObjectScript
El propósito de esta clase es ejecutar una línea de código ObjectScript y devolver un objeto JSON con los resultados de la ejecución. Pasamos nuestro código a ```CodeResult``` en una variable ```vstrCommand```.
Comenzamos redirigiendo IO a la rutina actual, luego ejecutamos el código pasado a través del comando [XECUTE](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_cxecute), redirigimos IO de vuelta al original y devolvemos los resultados.
```
Include %sySystem
Class JupyterKernel.CodeExecutor
{
ClassMethod CodeResult(vstrCommand As %String) As %String [ ProcedureBlock = 0 ]
{
set tOldIORedirected = ##class(%Device).ReDirectIO()
set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
set tOldIO = $io
try {
set str=""
set status = 1
//Redirigir IO a la rutina actual: utiliza las etiquetas definidas a continuación
use $io::("^"_$ZNAME)
//Habilitar redirección
do ##class(%Device).ReDirectIO(1)
XECUTE (vstrCommand)
} catch ex {
set str = ex.DisplayString()
set status = 0
}
//Vuelva a la configuración original de redirección/rutina mnemotécnica
if (tOldMnemonic '= "") {
use tOldIO::("^"_tOldMnemonic)
} else {
use tOldIO
}
do ##class(%Device).ReDirectIO(tOldIORedirected)
quit {"status":(status), "out":(str)}.%ToJSON()
rchr(c)
quit
rstr(sz,to)
quit
wchr(s)
do output($char(s))
quit
wff()
do output($char(12))
quit
wnl()
do output($char(13,10))
quit
wstr(s)
do output(s)
quit
wtab(s)
do output($char(9))
quit
output(s)
set str = str _ s
quit
}
}
```
## Mostrar los resultados
Hemos ejecutado una parte del código ObjectScript, ¿y ahora qué? Bueno, tenemos que mostrar los resultados.
Si no hubo excepciones, simplemente mostramos los resultados línea por línea.
Sin embargo, si nuestro fragmento de código aprobado generó una excepción, detenemos la ejecución, mostramos el número de la línea fallida, a sí misma y la excepción generada.

## Lanzamiento de la aplicación
Puedes probar este kernel por ti mismo y aquí te mostramos cómo.
## Requisitos previos
Asegúrate de tener [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) y [Docker](https://www.docker.com/products/docker-desktop) instalado.
Clonar/extraer el repositorio en cualquier directorio local, por ejemplo, como se muestra a continuación:
```
$ git clone https://github.com/Vekkby/objectsriptkernel.git
```
Abre el terminal en este directorio y ejecuta:
```
$ docker-compose up -d --build
```
## Cómo trabajar con él
Puedes acceder al servidor del notebook desde el navegador usando
```
localhost:8888
```
Hay un notebook de muestra llamado 'hello.ipynb' en el directorio 'work'.

[ObjectScript Kernel Logo]: https://i.imgur.com/L4pi1da.jpg
Artículo
Mathew Lambert · 15 feb, 2021
## Introducción y motivación {#RobustErrorHandlingandCleanupinObjectScript-IntroductionandMotivation}
Una unidad de código en ObjectScript (pongamos, un ClassMethod) puede producir una gran variedad de efectos secundarios inesperados cuando interactúa con partes del sistema que están fuera de su propio alcance y no han sido depuradas adecuadamente. En forma de lista parcial, se incluyen:
* Transacciones
* Locks
* Dispositivos E/S
* Cursores SQL
* Flags de systema y configuración
* $Namespace
* Archivos temporales
Utilizar estas importantes funciones del lenguaje sin hacer cleanup adecuadamente y con desarrollo defensivo podría ocasionar que una aplicación, que normalmente funciona correctamente, falle de forma inesperada y sea difícil de debugar. Es fundamental que el código de cleanup funcione correctamente en todos los posibles casos de error, en especial porque es probable que en pruebas superficiales se ignoren los casos de error. En este artículo se detallan varios problemas conocidos, y se explican dos patrones para lograr que los errores se gestionen y eliminen de manera eficiente.
_Injerto importante que está parcialmente relacionado: ¿quieres asegurarte de que estás probando todos tus casos hasta el límite? Echa un vistazo a la ¡[Herramienta de cobertura para pruebas en Open Exchange](https://openexchange.intersystems.com/package/Test-Coverage-Tool)!_
## Trampas a evitar {#RobustErrorHandlingandCleanupinObjectScript-PitfallstoAvoid}
### Transacciones {#RobustErrorHandlingandCleanupinObjectScript-Transactions}
Un enfoque natural y simplista de las transacciones consiste en envolver la transacción en un bloque try/catch, con un TRollback en catch, de la siguiente manera:
Try {
TSTART
// ... do stuff with data ...
TCOMMIT
} Catch e {
TROLLBACK
// e.AsStatus(), e.Log(), etc.
}
Estando aislados, este código siempre que lo escrito entre TStart y TCommit arroje excepciones en vez de producir Quit cuando se produzca un error es perfectamente válido. Sin embargo, es arriesgado por dos razones:
* Si otro desarrollador agrega un “Quit” dentro del bloque Try, una transacción se quedará abierta. Sería sencillo saltarse ese cambio durante la revisión del código, especialmente si no es obvio que haya transacciones involucradas en el contexto actual.
* Si se llama al método con este bloque desde una transacción externa, TRollback restaurará todos los niveles de la transacción.
Un mejor enfoque consiste en rastrear el nivel de la transacción al principio del método y retroceder hasta ese nivel de la transacción al final. Por ejemplo:
Set tInitTLevel = $TLevel
Try {
TSTART
// ... do stuff with data ...
// The following is fine now; tStatus does not need to be thrown as an exception.
If $$$ISERR(tStatus) {
Quit
}
// ... do more stuff with data ...
TCOMMIT
} Catch e {
// e.AsStatus(), e.Log(), etc.
}
While $TLevel > tInitTLevel {
// Just roll back one transaction level at a time.
TROLLBACK 1
}
### Locks {#RobustErrorHandlingandCleanupinObjectScript-Locks}
Cualquier código que utilice locks progresivos también debe garantizar que disminuyan los locks en el código de depuración cuando ya no sean necesarios; de lo contrario, dichos locks se mantendrán hasta que el proceso termine. Los locks no deben filtrarse fuera de un método, a menos que la obtención de dicho lock sea un efecto secundario documentado del método.
### Dispositivos de E/S {#RobustErrorHandlingandCleanupinObjectScript-IODevices}
De forma similar, los cambios en el dispositivo de E/S actual (la variable especial $io) tampoco deberían filtrarse fuera de un método, a menos que el propósito del método sea cambiar el dispositivo actual (por ejemplo, habilitar la redirección de E/S). Cuando se trabaja con archivos, es preferible utilizar el paquete %Stream en vez del E/S para archivos secuenciales directos por medio de OPEN / USE / READ / CLOSE. En otros casos, donde es necesario utilizar dispositivos de E/S, debe restablecerse el dispositivo original cuando finalice el método. Por ejemplo, el siguiente patrón de código es arriesgado:
Method ReadFromDevice(pSomeOtherDevice As %String)
{
Open pSomeOtherDevice:10
Use pSomeOtherDevice
Read x
// ... do complicated things with X ...
Close pSomeOtherDevice
}
Si se lanza una excepción antes de que pSomeOtherDevice esté cerrado, entonces $io se quedará como pSomeOtherDevice; esto probablemente ocasionará errores en cascada. Además, cuando el dispositivo se cierra, $io se restablece al dispositivo predeterminado del proceso, el cual probablemente no sea el mismo dispositivo que se utilizó antes de que se llamara el método.
### Cursores SQL {#RobustErrorHandlingandCleanupinObjectScript-SQLCursors}
Cuando se utiliza SQL basado en cursores, el cursor debe estar cerrado en caso de ocurra cualquier error. Cuando el cursor no se cierra, pueden producirse filtraciones en los recursos (según la documentación de apoyo). Además, en algunos casos, si se ejecuta el código de nuevo y se intenta abrir el cursor, se obtendrá un error “already open” (SQLCODE -101).
### Flags de sistema y configuración {#RobustErrorHandlingandCleanupinObjectScript-SystemFlagsandSettings}
Excepcionalmente, el código de la aplicación puede necesitar que se modifiquen flags a nivel de proceso o del sistema; por ejemplo, muchos están definidos en %SYSTEM.Process y %SYSTEM.SQL . En todos esos casos, debe tenerse cuidado de almacenar el valor inicial y restablecerlo al final del método.
### $Namespace {#RobustErrorHandlingandCleanupinObjectScript-Namespace}
El código que modifica el namespace siempre debe ser New $Namespace al principio, para garantizar que los cambios en el namespace no se filtran fuera del alcance del método.
### Archivos temporales {#RobustErrorHandlingandCleanupinObjectScript-TemporaryFiles}
El código de la aplicación encargado de crear archivos temporales, como %Library.File:TempFilename (que, en InterSystems IRIS, realmente crea el archivo), debería eliminar también los archivos temporales cuando ya no se necesiten.
## Patrón recomendado: Try-Catch (-Finally) {#RobustErrorHandlingandCleanupinObjectScript-TryCatchFinally}
Muchos lenguajes tienen una función en la que una estructura de tipo try/catch también puede tener un bloque “finally” que se ejecuta cuando se ha completado try/catch, tanto si se produjo una excepción como si no. ObjectScript no lo hace, pero puede aproximarse. Un patrón general para esto, que demuestra muchos de los posibles casos problemáticos, es el siguiente:
ClassMethod MyRobustMethod(pFile As %String = "C:\foo\bar.txt") As %Status
{
Set tSC = $$$OK
Set tInitialTLevel = $TLevel
Set tMyGlobalLocked = 0
Set tDevice = $io
Set tFileOpen = 0
Set tCursorOpen = 0
Set tOldSystemFlagValue = ""
Try {
// Lock a global, provided a lock can be obtained within 5 seconds.
Lock +^MyGlobal(42):5
If '$Test {
$$$ThrowStatus($$$ERROR($$$GeneralError,"Couldn't lock ^MyGlobal(42)."))
}
Set tMyGlobalLocked = 1
// Open a file
Open pFile:"WNS":10
If '$Test {
$$$ThrowStatus($$$ERROR($$$GeneralError,"Couldn't open file "_pFile))
}
Set tFileOpen = 1
// [ cursor MyCursor declared ]
&;SQL(OPEN MyCursor)
Set tCursorOpen = 1
// Set a system flag for this process.
Set tOldSystemFlagValue = $System.Process.SetZEOF(1)
// Do the important things...
Use tFile
TSTART
// [ ... lots of important and complicated code that changes data here ... ]
// All done!
TCOMMIT
} Catch e {
Set tSC = e.AsStatus()
}
// Finally {
// Cleanup: system flag
If (tOldSystemFlagValue '= "") {
Do $System.Process.SetZEOF(tOldSystemFlagValue)
}
// Cleanup: device
If tFileOpen {
Close pFile
// If pFile is the current device, the CLOSE command switches $io back to the process's default device,
// which might not be the same as the value of $io was when the method was called.
// To be extra sure:
Use tDevice
}
// Cleanup: locks
If tMyGlobalLocked {
Lock -^MyGlobal(42)
}
// Cleanup: transactions
// Roll back one level at a time up to our starting transaction level.
While $TLevel > tInitialTLevel {
TROLLBACK 1
}
// } // end "finally"
Quit tSC
}
Nota: en este enfoque, es fundamental que se utilice “Quit” y no “Return” en el bloque “Try” ...; “Return” haría un bypass del cleanup.
## Patrón recomendado: Objetos Registrados y Destructores {#RobustErrorHandlingandCleanupinObjectScript-RecommendedPattern:RegisteredObjectsandDestructors}
A veces, el código de depuración puede complicarse. En estos casos, quizás tenga sentido facilitar la reutilización del código de depuración integrándolo en un registered object. El estado del sistema es inicializado cuando se inicializa el objeto o cuando se llama a los métodos del objeto que cambian el estado, y regresa a su valor original cuando el objeto ya no está al alcance. Mira este sencillo ejemplo, que administra las transacciones, el namespace actual y el estado de $System.Process.SetZEOF:
/// When an instance of this class goes out of scope, the namespace, transaction level, and value of $System.Process.SetZEOF() that were present when it was created are restored.
Class DC.Demo.ScopeManager Extends %RegisteredObject
{
Property InitialNamespace As %String [ InitialExpression = {$Namespace} ];
Property InitialTransactionLevel As %String [ InitialExpression = {$TLevel} ];
Property ZEOFSetting As %Boolean [ InitialExpression = {$System.Process.SetZEOF()} ];
Method SetZEOF(pValue As %Boolean)
{
Set ..ZEOFSetting = $System.Process.SetZEOF(.pValue)
}
Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
{
Set tSC = $$$OK
Try {
Set $Namespace = ..InitialNamespace
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Try {
Do $System.Process.SetZEOF(..ZEOFSetting)
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Try {
While $TLevel > ..InitialTransactionLevel {
TROLLBACK 1
}
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Quit tSC
}
}
La siguiente clase demuestra cómo la clase registrada anteriormente podría utilizarse para simplificar la depuración cuando finalice el método:
Class DC.Demo.Driver
{
ClassMethod Run()
{
For tArgument = "good","bad" {
Do ..LogState(tArgument,"before")
Do ..DemoRobustMethod(tArgument)
Do ..LogState(tArgument,"after")
}
}
ClassMethod LogState(pArgument As %String, pWhen As %String)
{
Write !,pWhen," calling DemoRobustMethod("_$$$QUOTE(pArgument)_"):"
Write !,$c(9),"$Namespace=",$Namespace
Write !,$c(9),"$TLevel=",$TLevel
Write !,$c(9),"$System.Process.SetZEOF()=",$System.Process.SetZEOF()
}
ClassMethod DemoRobustMethod(pArgument As %String)
{
Set tScopeManager = ##class(DC.Demo.ScopeManager).%New()
Set $Namespace = "%SYS"
TSTART
Do tScopeManager.SetZEOF(1)
If (pArgument = "bad") {
// Normally, this would be a big problem. In this case, because of tScopeManager, it isn't.
Quit
}
TCOMMIT
}
}
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
(Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Kurro Lopez · 3 jun, 2019
"Telegram" es un popular programa de mensajería instantánea, que proporciona una API para la creación de bots. Las características de esta API le permiten crear bots con una amplia gama de funciones, incluida la recepción de pagos. Con la ayuda del bot de Telegram, resolví una tarea sencilla: enviar alertas desde Ensemble hacia Telegram.Ventajas: Cuando las alertas lleguen a su teléfono móvil, aparecerá una notificación, por lo que no es necesario que instale ninguna aplicación adicional (en contraste con la solución https://community.intersystems.com/post/sending-alerts-mobile-phone-using-pushover- httpoutboundadapter).El bot podrá hacer más cosas al añadir nuevos comandos, por ejemplo, para administrar su Productividad o cuando desee resolver otras tareas en Iris, Ensemble o Cache. Si quieres, puedes empezar a echar un vistazo al código - github projectEn primer lugar, considere algunas de las características de Telegram cuando desee crear un bot :Telegram es un mensajero muy popular debido a la seguridad que brinda. Por ejemplo, cuando un usuario comienza a chatear con un robot, éste no conoce el número de teléfono del usuario. Esto es bueno para el usuario, ya nadie podrá recopilar los números de teléfono. Además, es imposible enviar un mensaje con solo conocer el número de teléfono. Telegram es compatible con 2 maneras de enviar actualizaciones (aquellos mensajes que los usuarios escriben a los robots) hacia su servidor:Long polling. Para recibir actualizaciones de entrada, llame al método de la API después de cumplir con los intervalos de tiempo necesarios.Webhook. Registre su URL, y siempre que haya una actualización para el bot, Telegram enviará una petición HTTPS POST a la URL que se especificó.Ejecuté ambas opciones. Cómo funciona este robot y qué se hace en EnsembleCualquier interacción comienza con el envío del comando /start. La respuesta: será un mensaje de texto con un saludo y una lista de los comandos disponibles El usuario envía el comando /subscribe. La respuesta contiene un botón especial que le permite solicitar el número de teléfono del usuario. Esto es necesario para identificar al usuario y determinar cuáles son los mensajes que se le deben enviar. Al hacer clic en este botón el usuario confirma la autorización para enviar su número de teléfono. Ensemble verifica si este número se encuentra en el registro de empleados. Si es así, se guarda el número de identificación del chat. Cuando se produce un error en alguno de los componentes de producción de Ensemble, este automáticamente crea un mensaje de AlertRequest y lo envía a la operación empresarial Ens.Alert. Con la operación empresarial Ens.Alert busca a todos los empleados que tienen un número de identificación en el chat y les envía un mensaje sobre el problema a cada uno de ellos. El comando /alert (que envía la prueba Alert) también se proporciona para que lo pruebe. FuncionesPara almacenar el vínculo del número de teléfono del empleado y el número de identificación del chat en Ensemble, elegí la siguiente tabla. Las claves son los números de teléfono, los valores son los números de identificación del chat de Telegram. Cuando un usuario se registra para recibir notificaciones, el número de identificación del chat se actualiza.Para recibir las actualizaciones, primero se implementó el mecanismo Long poll. Hacer esto es muy sencillo en Ensemble, basta con añadir un servicio (Business Service) y especificar el intervalo de llamadas. Después de especificar el intervalo, Ensemble accederá a la API de Telegram para obtener las actualizaciones. La desventaja de este enfoque, es que el usuario puede notar un retraso entre el envío del comando y la respuesta del bot. Un punto a favor, es que no necesita proporcionar acceso al servidor de Ensemble desde Internet. La implementación de webhook no es un problema, ya que la manera de crear un servicio REST JSON en Cache se ha descrito muchas veces y en detalle. Pero al implementar webhook, debe tener en cuenta que Telegram sólo envía mensajes a través de HTTPS, por lo que debe configurar SSL en su servidor web (utilicé un certificado firmado por mi mismo) .La secuencia de pasos para añadir una función específica en su producciónCree un bot con la ayuda de otro robot especial "BotFather"https://telegram.me/BotFather cuando lo haga recibirá un token Es necesario que cree una configuración de cliente SSL en Ensemble, ya que el mensaje se envíará a través de HTTPS. Importe las clases desde https://github.com/intersystems-community/TelegramAlerts (un paquete de Telegram):API.cls - métodos de implementación de las clases para trabajar con la API.TelegramOutboundAdapter.cls - adaptador de salidaTelegramOperation.cls - operación (Business Operation) para enviar mensajes a Telegram TelegramInboundAdapter.cls - adaptador de entradaTelegramService.cls - servicio (Business Service)Msg.TextRequest.cls, Msg.ButtonRequest.cls - clases de mensajesAlertOperation.cls - operación (Business operation) para enviar alertasRESTBroker.cls - REST-broker para webhook Genere una tabla de búsqueda en el portal(https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=ECONFIG_other#ECONFIG_other_lookup_tables) y añada una o más filas en él, además utilice un número de teléfono (sin +) como clave, con los números de teléfono de aquellos que necesiten recibir notificaciones. Añada y configure la operación empresarial TelegramOperation.cls a la producción. En su configuración, debe especificar TelegramToken y SSLConfigurationPara recibir notificaciones, debe realizar los pasos 6 ó 7. Si utiliza long polling, añada y configure el servicio (Business Service) de TelegramService a su producción. Para ello debe especificar a TelegramToken, SSLConfiguration y TableName (busque el nombre de la tabla). Configure el intervalo de llamadas. Si utiliza webhook:Cree una aplicación web y especifique la clase Telegram.RESTBroker.cls como un bróker.Configure SSL en el servidor web.Registre webhook, para esto necesita llamar al método SetWebhook de la clase Telegram.APIAñada el servicio (Business Service) TelegramService a su producción, especifique el parámetro TableName. Especifique a poolsize = 0. Añada AlertOperation.cls con el nombre Ens.Alert y configure los parámetros: TelegramToken, SSLConfiguration, TableName. ¡Listo!
Artículo
Nancy Martínez · 3 jul, 2020
El objetivo de esta "Guía para solucionar problemas en DeepSee" es ayudar a localizar y solucionar los problemas en un proyecto en DeepSee.
Si el problema no puede solucionarse siguiendo estas recomendaciones, al menos tendrás suficiente información para reportar el problema al Centro de Soporte Internacional (WRC) y proporcionarnos todas la información, para que podamos continuar la investigación juntos y resolverlo más rápido!
Nota.- Si no se está familiarizado con las consecuencias de una determinada acción o comando, no se deben ejecutar, ya que esto podría tener algún efecto en el rendimiento del sistema. En este caso, lo aconsejable es ponerse en contacto con el soporte técnico de DeepSee para obtener más ayuda.
La forma más sencilla de seguir esta guía es comenzar por el lado izquierdo y llegar hasta la columna de Soluciones que se encuentra a la derecha.
.deepsee-solutions td,
.deepsee-solutions th {
padding: 5px;
vertical-align: top;
text-align: left;
font-size: 12px;
}
Tipo de problema
¿Qué ocurrió?
Análisis
Solución
Problemas con la compilación
Errores durante la compilación
Comprobar el mensaje de errorEjecutar $System.OBJ.DisplayError() si la compilación no mostró erroresRevisar ^DeepSee.BuildErrors/ run ##class(%DeepSee.Utils).%PrintBuildErrors(pCube)
Corregir los errores que aparecen en el mensaje
Se crearon menos registros que filas en la tabla de origen
Verificar el modelo analítico para crear restricciones
Eliminar o aceptar las restricciones de compilación que se generen
Comprobar si se utilizaron maxfacts
Eliminar maxfacts
Comprobar ^DeepSee.BuildErrors/ run ##class(%DeepSee.Utils).%PrintBuildErrors(pCube)
Corregir los errores de compilación que se produzcan
Revisar los índices en la clase de origen
Reconstruir los índices en la clase de origen
Faltan datos
Los registros del origen no están disponibles en DeepSee
Consultar las sección anterior para problemas / restricciones durante la compilación
Eliminar las restricciones
Verificar "%OnProcessFact" en la definición del modelo analítico
Corregir el método, si es necesario
Comprobar si hay errores en la compilación
Reparar los errores que se produzcan
Depurar los métodos en sourceExpression
Reparar los métodos
Revisar los registros de la tabla Fact
Encontrar por qué no coinciden y resolver el problema
Comparar la tabla Fact con la tabla de origen
Resultados incorrectos
Miembros duplicados en el nivel
Verificar la dimensión para jerarquía válida
Cambiar el nivel según este artículo
La consulta muestra resultados inesperados
Verificar la sección anterior si faltan datosComprobar si hay problemas de almacenamiento en caché: - do $System.DeepSee.Shell() - desactive caché
Corregir los errores en la compilacióndo $System.DeepSee.Reset()kill ^DeepSee.Cache
Dividir la consulta en segmentos más pequeños (por ejemplo, cada eje por separado)Encontrar los segmentos problemáticos
Reparar los problemas en el segmento
Comprobar ^DeepSee.AgentLog
Ejecutar do ##class(%DeepSee.TaskMaster).%Reset()
Las listas están vacías
Comprobar que las listas tengan el formato habitual
Otorgar permisos para seleccionar desde la tabla de origen
Comprobar si las listas de SQL están personalizadas
Depurar los condicionales WHEREComprobar ^DeepSee.SQLErrorComprobar ^DeepSee.QueryLog
Caídas del software / Eventos inesperados
DeepSee no responde como se espera
Verificar el estado del sistema
Caídas del software
Comprobar el estado del agente
Revisar el uso de la licencia
Ampliar la licencia
Fallo en las operaciones del sistema
Comprobar ^DeepSee.AgentLog
Ejecutar do ##class(%DeepSee.TaskMaster).%Reset()
Ejecute do ##class(%DeepSee.TaskMaster).%PrintLog()
Comprobar ^DeepSee.LastLogErrorComprobar ^DeepSee.PivotError()
Reparar los errores que se encontraron
Problemas con el rendimiento
Bajo rendimiento en la compilación
Ejecutar do ##class(%DeepSee.TaskMaster).%PrintLog()
Verificar el número de agentes disponibles
Ejecutar do ##class(%DeepSee.TaskMaster).%Reset()
Verificar el uso de memoria y CPU
Ejecutar la compilación en el modo de carga baja
Comprobar expresiones de origen
Evitar las expresiones de origen, si es posible
Verifique las estadísticas generales del Cubo:
Eliminar las dimensiones/niveles que no sean necesarios
- Ejecutar do ##class(%DeepSee.Utils).%Analyze("Holefoods")
Mantener los cubos simples y pequeños
La actividad del registro es muy alta
Al utilizar un diseño para el almacenamiento de datos o un namespace separado para DeepSee, se puede desactivar el registro en este namespace
Bajo rendimiento en las consultas
Ejecute el informe: do ##class(%DeepSee.Diagnostic.MDXUtils).%Run(<query>)
Las consultas que posiblemente se realicen a largo plazo deberán simplificarse mediante algún método alternativo
Verifique el uso de memoria y CPU
Liberar los recursos
Comprobar la configuración del buffer
Incrementar los buffers, si es posible
Realizar un análisis general sobre el rendimiento del sistema
En problemas relacionados con el rendimiento durante el tiempo de ejecución, solicite ayuda al WRC
Problemas más frecuentes
El Namespace no aparece en el menú DS
Compruebe que DeepSee esté habilitado en la configuración de la aplicación web /csp/<namespace>
Habilitar "DeepSee" en la aplicación web
La opción Architect aparece en gris
Revisar la licencia
obtener la licencia habilitada para DeepSee
Verificar los roles del usuario
Agregar el recurso %DeepSee_Architect / %DeepSee_ArchitectEdit Use
Comprobar que su navegador no sea Internet Explorer 8
Utilizar un navegador compatible
Estos son los diagramas para cada tipo de problemas:
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
(Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Joel Espinoza · 28 oct, 2019
¡Hola Comunidad!
Una función útil de nuestra estructura REST es la capacidad que tienen las clases de Dispatch para identificar los prefijos de una solicitud y redireccionarlos a otra clase de Dispatch. Este enfoque permite mejorar el orden y la lectura del código, permite mantener separadas las versiones de una interfaz fácilmente y ofrece una forma de proteger llamadas a APIs a las que solo ciertos usuarios podrán acceder.
Resumen
Para configurar un servicio REST en su instancia de Caché o IRIS Database, necesita definir una aplicación CSP dedicada y crear una clase de Dispatch asociada que gestione las solicitudes que entran. La clase de Dispatch se extiende desde %CSP.REST e incluirá un bloque XData que contiene su mapa URL. Esto indica al sistema qué método deberá llamar cuando reciba una solicitud en particular.
Por ejemplo:
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/orders" Method="GET" Call="GetOrders"/>
<Route Url="/orders" Method="POST" Call="NewOrder"/>
</Routes>
}
Los elementos <Route> determinan las diferentes solicitudes que administrará el servicio. Una solicitud GET para el recurso "/orders" llamará al método de clase "GetOrders". Una solicitud POST que se realiza al mismo recurso llamará, en su lugar, al método "NewOrder".
Es importante tener en cuenta que el nombre de la aplicación CSP no se considera parte del nombre del recurso solicitado en nuestro mapa URL. Analice una solicitud realizada en la dirección:
http://localhost:57772/csp/demo/orders
Si nuestra aplicación CSP se llama "/csp/demo", entonces el único segmento de la solicitud que es administrado por la clase de Dispatch es el que se encuentra después del nombre de la aplicación. En este caso es "/orders".
Reenvío de solicitudes
En lugar de llamar a un método desde dentro de la clase de Dispatch, la otra opción para su mapa URL es reenviar todas las solicitudes que coincidan con un prefijo en particular hacia una clase de Dispatch diferente.
Esto se realiza mediante el elemento <Map> que se encuentra en la sección UrlMap. El elemento contiene dos atributos, Prefix y Forward. Si la solicitud de la URL coincide con alguno de los prefijos, entonces, enviamos la solicitud a la clase de Dispatch especificada, para que se procese posteriormente.
Por ejemplo:
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Map Prefix="/shipping" Forward="Demo.Service.Shipping"/>
<Route Url="/orders" Method="GET" Call="GetOrders"/>
<Route Url="/orders" Method="POST" Call="NewOrder"/>
</Routes>
}
Una solicitud GET o POST para "/orders" será administrada directamente por esta clase. Sin embargo, las solicitudes que coincidan con el prefijo "/shipping" se redireccionarán a la clase de Dispatch Demo.Service.Shipping, que tiene su propio mapa URL:
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/track/:id" Method="GET" Call="TrackShipment"/>
</Routes>
}
Fallo en el enrutamiento de la URL
Para demostrar cómo influye cada componente solicitado por la URL en el método que se llama al final, analizaremos cada elemento de una solicitud para la siguiente dirección:
http://localhost:57772/csp/demo/shipping/track/123
http://
Es el protocolo que se usó para la solicitud.
localhost:57772 (52773 en IRIS)
Es el servidor al que nos conectamos.
/csp/demo/shipping/track/123
Es el recurso que se está solicitando.
/csp/demo
Es el nombre de la aplicación CSP.Una clase de Dispatch se define para la aplicación, y enruta la solicitud hacia allí.
/shipping/track/123
Es el segmento del recurso que se enviará a la primera clase de Dispatch.
/shipping
Es el prefijo que coincide con el elemento <Map> en el mapa URL.Lo redireccionará a la clase Demo.Service.Shipping.
/track/123
Es el segmento del recurso que se envió a la segunda clase de Dispatch.Coincide con la ruta "/track/:id".Llama al método TrackShipment(123).
Resultados
Control del código fuente — Separar su API REST en varias clases reducirá el tamaño total de cada clase, lo cual le ayudará a mantener el historial del código fuente bajo control, conciso y legible.
Versiones — Una manera sencilla para lograr que varias versiones de una API sean compatibles simultáneamente es utilizar el reenvío. Una sola clase de Dispatch podría reenviar solicitudes que coincidan con los prefijos /v1 o /v2 a una clase de Dispatch que implemente esa versión de la API. La API REST, que es un elemento central de Atelier, nuestro nuevo IDE, utiliza el mismo esquema de versiones.
Seguridad — Si su API necesita tener rutas que estén restringidas para ciertos usuarios - por ejemplo, para permitir que únicamente los administradores realicen ciertos tipos de solicitudes- tiene sentido aislar estas rutas en su propia clase y después reenviar las solicitudes mediante un prefijo en particular. Si la segunda clase de Dispatch define un método OnPreDispatch, su código se ejecutará antes del procesamiento de cada solicitud. El servicio puede utilizar esto para comprobar los privilegios de un usuario y decidir si continúa con el procesamiento o cancela su solicitud.
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
(Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Mathew Lambert · 15 jul, 2020
¡Hola desarrolladores!
Aquí podéis ver el anuncio del proyecto isc-tar de @Dmitriy Maslennikov. En ocasiones, la historia de porque se ha llegado a un resultado es igual o más interesante que el resultado: cómo se construyó, cómo funciona y qué sucede en torno al proyecto. Esta es la historia:
Cómo desarrollar este proyecto
Cómo probarlo
Cómo lanzar nuevas versiones para publicar
Cómo automatizar todo lo anterior
Integración continua
Os hablaré de todo eso.
Desarrollo
Últimamente, mis herramientas favoritas son Docker y VSCode. Uso Docker para empezar cualquiera de mis proyectos en su propio entorno y VSCode es sencillamente el mejor editor existente, el cual uso junto con la extensión vscode-objectscript. En cualquier nuevo proyecto, uso por lo menos esas dos herramientas. También soy usuario de macOS, por lo que todos los siguientes pasos fueron probados y deberían funcionar bien en macOS y quizás también en Linux, pero podría ser distinto para Windows.
Antes de nada, clonamos nuestro proyecto en algún sitio
$ git clone git@github.com:daimor/isc-tar.git
Yo tengo el código de comando en mi intérprete, para poder abrir fácilmente cualquier carpeta desde mi terminal con el código. Si quieres esta funcionalidad, puedes configurarla desde la consola de comandos. Para que quede disponible el comando, tendrás que actualizar tu intérprete
Abre el editor solo con el repositorio clonado
$ code isc-tar
VSCode tiene la opción de un terminal integrado. Ahora puedes abrirlo sin perder tu terminal, que estará muy cerca siempre. Podemos usar el terminal para configurar el entorno del proyecto con los dos comandos siguientes:
$ docker-compose build --no-cache
$ docker-compose up -d
esto puede tardar un poco.
Y ya puedes escribir código. Este repositorio contiene la configuración para VSCode, por lo que ya está preconfigurado para usar este IRIS
Pruebas
El proyecto tiene un par de pruebas que puedes realizar:
en el terminal, ve a la sesión de IRIS
$ docker-compose exec iris iris session iris
USER>do ##class(%UnitTest.Manager).RunTest()
Por cierto, ¿has notado los 3 "iris" en el comando? A mí no me gusta tener que repetir las cosas, y hay que escribir "iris" tres veces para lanzar el intérprete de IRIS. La primera vez es para el nombre del servicio en docker-compose, la segunda es el comando iris dentro del contenedor (que sustituye a ccontrol que se usaba en Caché), y la tercera es para el nombre de la instancia. Me gustaría que InterSystems añadiera un comando session más corto, para eliminar esta innecesaria repetición de la palabra "iris". Debería ser algo como esto
docker-compose exec iris session
Publicación
Como se comentó en el anuncio, esta herramienta también podría funcionar en versiones anteriores de Caché y Ensemble. Pero no todas esas versiones soportan UDL como formato de importación. Por lo tanto, necesitamos XML, que se puede usar siempre, tanto en IRIS como en Caché/Ensemble. Afortunadamente, tengo una sola clase y puedo hacer una exportación simple.
do $system.OBJ.Export("%zUtils.FileBinaryTar.cls", "/opt/zUtils.FileBinaryTar.xml", "/diffexport")
Y lamentablemente, por algún motivo desconocido, no puedo usar un útil qualifier /exportversion=2010.1
USER>do $system.OBJ.Export("%zUtils.FileBinaryTar.cls", "/opt/zUtils.FileBinaryTar.xml", "/diffexport/exportversion=2010.1")
Exporting to XML started on 03/16/2019 09:18:35
Exporting class: %zUtils.FileBinaryTar
ERROR #5126: XML export version '2010.1' not supported, supports 2010.1 and onwards.
Errors detected during export.
En el XML exportado, necesito sustituir el encabezado Export para que también pueda reconocerlo Caché.
<Export generator="IRIS" version="26">
Y para eso uso el comando sed.
sed -i.bak 's/^<Export generator="IRIS" .*$/<Export generator="Cache" version="25">/g' /opt/zUtils.FileBinaryTar.xml
Ahora se puede usar tanto en Caché como en IRIS.
Automatización
Para ejecutar pruebas y compilar versiones, es necesario recordar e invocar comandos largos, por lo que sería bueno simplificarlo lo máximo posible. Y Makefile es perfectamente adecuado para ello.
APP_NAME = isc-tar
IMAGE ?= daimor/$(APP_NAME)
SHELL := /bin/bash
.PHONY: help build test release
help: ## This help.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
.DEFAULT_GOAL := help
build: ## Build the image
docker build -t $(IMAGE) .
test: build ## Run UnitTests
docker run --rm -i -v `pwd`/tests:/opt/tests -w /opt --entrypoint /tests_entrypoint.sh $(IMAGE)
release: clean build ## Export as XML
docker run --rm -i -v `pwd`/out:/opt/out -w /opt --entrypoint /build_artifacts.sh $(IMAGE)
clean:
-rm -rf out
Mi Makefile es bastante simple. Tengo dos recetas principales
make test - ejecuta Unit Tests
make release - exporta el proyecto como XML
Estas dos recetas dependen de build, que compila la imagen de IRIS con el proyecto dentro.
Integración continua
Por supuesto, el artículo no estaría completo sin la integración continua, en breve se publicará la segunda parte, y estoy seguro de que os sorprenderá cómo se hice.
Artículo
Alberto Fuentes · 22 abr, 2022
Durante una actualización a una versión principal (major) es aconsejable recompilar las clases y rutinas de todos tus namespaces (ver Tareas tras la instalación de una versión major).
do $system.OBJ.CompileAllNamespaces("u")
do ##Class(%Routine).CompileAllNamespaces()
Para automatizar esta tarea de administración y mantener un registro de cualquier error, os muestro un ejemplo de una clase para importar y compilar en el namespace USER, que puedes usar después de cada actualización: admin.utils.cls
Class
Class admin.utils.cls
Class admin.utils
{
ClassMethod upgrade(verbose As %Boolean = 0) As %Status
{
set ns=$Namespace
// Stopping all productions
do ..stopAllProductions(verbose)
zn "%sys"
kill ^[ns]upgradeLog
// UpgradeAll
set ^[ns]upgradeLog("UpgradeAll")=$zdt($now(),3,,6)
set start=$zh
do $system.OBJ.UpgradeAll(,.errorLogUpgrade)
do ##class(%SYSTEM.OBJ).UpgradeAll()
set ^[ns]upgradeLog("UpgradeAll","duration")=$zh-start
merge ^[ns]upgradeLog("UpgradeAll","errors")=errorLogUpgrade
// Compile Classes in All Namespaces
set ^[ns]upgradeLog("classes")=$zdt($now(),3,,6)
set start=$zh
do $system.OBJ.CompileAllNamespaces("cbk",.errorLogClasses
set ^[ns]upgradeLog("classes","duration")=$zh-start
merge ^[ns]upgradeLog("classes","errors")=errorLogClasses
// Compile Routines in All Namespaces
set ^[ns]upgradeLog("routines")=$zdt($now(),3,,6)
set start=$zh
do ##Class(%Library.Routine).CompileAllNamespaces(,,.count,.errorLogRoutines)
set ^[ns]upgradeLog("routines","duration")=$zh-start
merge ^[ns]upgradeLog("routines","errors")=errorLogRoutines
merge ^[ns]upgradeLog("routines","count")=coun
// Starting all productions
do ..startAllProductions(verbose)
zn ns
return $$$OK
}
ClassMethod startAllProductions(verbose As %Boolean = 0) As %Status
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=2 {
write:verbose " starting production "_prodname,!
set sc=##class(Ens.Director).StartProduction()
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
ClassMethod stopAllProductions(verbose As %Boolean = 0) As %Status
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=1 {
write:verbose " stopping production "_prodname,!
set sc=##class(Ens.Director).StopProduction(0,1)
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
ClassMethod restartAllProductions(verbose As %Boolean = 0) As %Statu
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=1 {
write:verbose " restarting production "_prodname,!
set sc=##class(Ens.Director).RestartProduction(0,1)
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
ClassMethod cleanAllProductions(verbose As %Boolean = 0) As %Status
{
set sc=$$$OK
set ns=$Namespace
set sc=##class(%SYS.Namespace).ListAll(.namespacesList)
set namespace=$ORDER(namespacesList(""))
while (namespace'="") {
if (##class(%EnsembleMgr).IsEnsembleNamespace(namespace))&&(namespace'["^^") {
write:verbose namespace
zn namespace
kill prodname,status
set sc=##class(Ens.Director).GetProductionStatus(.prodname,.status)
if status=3 {
write:verbose " cleaning production "_prodname,!
set sc=##class(Ens.Director).CleanProduction(1)
}
write:verbose !
zn ns
}
set namespace=$ORDER(namespacesList(namespace))
}
return sc
}
}
Después de la actualización, simplemente ejecuta el método admin.utils.upgrade desde una sesión de terminal de IRIS:
USER>do ##class(admin.utils).upgrade()
Y puedes ver los resultados desde el portal de administración a través del explorador System > Globals > upgradeLog Este artículo está etiquetado como "Mejores prácticas" ("Best practices").
Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.
Artículo
Muhammad Waseem · 6 abr, 2022
¡Hola Comunidad!
Esta publicación es una introducción a mi aplicación iris-globals-graphDB en Open Exchange.
En este artículo, mostraré cómo guardar y recuperar Graph Data en InterSystems Globals con la ayuda del framework Python Flask Web y la librería PYVIS Interactive network visualizations.
Recomendación
Leer la documentación relacionada: Using Globals
Introducción al SDK nativo
PYVIS Librería de visualización interactiva de redes
Paso 1: Establecer conexión con IRIS Globals mediante el SDK nativo de Python
#create and establish connection
if not self.iris_connection:
self.iris_connection = irisnative.createConnection("localhost", 1972, "USER", "superuser", "SYS")
# Create an iris object
self.iris_native = irisnative.createIris(self.iris_connection)
return self.iris_native
Paso 2: Guardar datos en globals mediante la función iris_native.set( )
#import nodes data from csv file
isdefined = self.iris_native.isDefined("^g1nodes")
if isdefined == 0:
with open("/opt/irisapp/misc/g1nodes.csv", newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
self.iris_native.set(row["name"], "^g1nodes", row["id"])
#import edges data from csv file
isdefined = self.iris_native.isDefined("^g1edges")
if isdefined == 0:
with open("/opt/irisapp/misc/g1edges.csv", newline='') as csvfile:
reader = csv.DictReader(csvfile)
counter = 0
for row in reader:
counter = counter + 1
#Save data to globals
self.iris_native.set(row["source"]+'-'+row["target"], "^g1edges", counter)
Paso 3: Transferir datos de nodos y bordes a PYVIS desde globals mediante la función iris_native.get()
#Get nodes data for basic graph
def get_g1nodes(self):
iris = self.get_iris_native()
leverl1_subscript_iter = iris.iterator("^g1nodes")
result = []
# Iterate over all nodes forwards
for level1_subscript, level1_value in leverl1_subscript_iter:
#Get data from globals
val = iris.get("^g1nodes",level1_subscript)
element = {"id": level1_subscript, "label": val, "shape":"circle"}
result.append(element)
return result
#Get edges data for basic graph
def get_g1edges(self):
iris = self.get_iris_native()
leverl1_subscript_iter = iris.iterator("^g1edges")
result = []
# Iterate over all nodes forwards
for level1_subscript, level1_value in leverl1_subscript_iter:
#Get data from globals
val = iris.get("^g1edges",level1_subscript)
element = {"from": int(val.rpartition('-')[0]), "to": int(val.rpartition('-')[2])}
result.append(element)
return result
Paso 4: Usar PYVIS Javascript para generar datos gráficos
<script type="text/javascript">
// initialize global variables.
var edges;
var nodes;
var network;
var container;
var options, data;
// This method is responsible for drawing the graph, returns the drawn network
function drawGraph() {
var container = document.getElementById('mynetwork');
let node = JSON.parse('{{ nodes | tojson }}');
let edge = JSON.parse('{{ edges | tojson }}');
// parsing and collecting nodes and edges from the python
nodes = new vis.DataSet(node);
edges = new vis.DataSet(edge);
// adding nodes and edges to the graph
data = {nodes: nodes, edges: edges};
var options = {
"configure": {
"enabled": true,
"filter": [
"physics","nodes"
]
},
"nodes": {
"color": {
"border": "rgba(233,180,56,1)",
"background": "rgba(252,175,41,1)",
"highlight": {
"border": "rgba(38,137,233,1)",
"background": "rgba(40,138,255,1)"
},
"hover": {
"border": "rgba(42,127,233,1)",
"background": "rgba(42,126,255,1)"
}
},
"font": {
"color": "rgba(255,255,255,1)"
}
},
"edges": {
"color": {
"inherit": true
},
"smooth": {
"enabled": false,
"type": "continuous"
}
},
"interaction": {
"dragNodes": true,
"hideEdgesOnDrag": false,
"hideNodesOnDrag": false,
"navigationButtons": true,
"hover": true
},
"physics": {
"barnesHut": {
"avoidOverlap": 0,
"centralGravity": 0.3,
"damping": 0.09,
"gravitationalConstant": -80000,
"springConstant": 0.001,
"springLength": 250
},
"enabled": true,
"stabilization": {
"enabled": true,
"fit": true,
"iterations": 1000,
"onlyDynamicEdges": false,
"updateInterval": 50
}
}
}
// if this network requires displaying the configure window,
// put it in its div
options.configure["container"] = document.getElementById("config");
network = new vis.Network(container, data, options);
return network;
}
drawGraph();
</script>
Paso 5: Llamar a los códigos anteriores desde el archivo principal app.py
#Mian route. (index)
@app.route("/")
def index():
#Establish connection and import data to globals
irisglobal = IRISGLOBAL()
irisglobal.import_g1_nodes_edges()
irisglobal.import_g2_nodes_edges()
#getting nodes data from globals
nodes = irisglobal.get_g1nodes()
#getting edges data from globals
edges = irisglobal.get_g1edges()
#To display graph with configuration
pyvis = True
return render_template('index.html', nodes = nodes,edges=edges,pyvis=pyvis)
Espero que os resulte útil.
Artículo
Jose-Tomas Salvador · 1 abr, 2025
La interfaz de usuario de Interoperabilidad ahora incluye experiencias modernizadas para las aplicaciones DTL Editor y Production Configuration, las cuales están disponibles para su activación en todos los productos de interoperabilidad. Podéis alternar entre las vistas moderna y tradicional. Todas las demás pantallas de interoperabilidad permanecen en la interfaz de usuario estándar. Tenéis que tener en cuenta que los cambios se limitan a estas dos aplicaciones, y a continuación se identifica la funcionalidad que está disponible actualmente.
Para probar las nuevas pantallas antes de la actualización, podéis descargar la versión 2025.1 desde nuestra página web del kit de la comunidad aquí: https://evaluation.intersystems.com/Eval/.
Configuración de Producción - Introducción a las Tareas de Configuración
Configuración de Producción: Soporte en esta versión de la Configuración de Producción:
Creación/Edición/Copia/Eliminación de Hosts
Detener/Iniciar Hosts
Edición de Configuración de Producción
Detener/Iniciar Producciones
Integración con Control de Versiones: El soporte para la integración con control de versiones para la funcionalidad de configuración mencionada está disponible.
Vista de Panel Dividido: Los usuarios pueden abrir directamente el Editor de Reglas y el Editor DTL desde la pantalla de Configuración de Producción para ver y editar reglas y transformaciones incluidas en la producción en una vista de panel dividido.
Filtrado Mejorado: Un cuadro de búsqueda en la parte superior permite buscar y filtrar en todos los componentes de negocio, incluyendo múltiples categorías, DTLs y subtransformaciones. Usad la barra lateral izquierda para buscar independientemente del panel principal y ver los resultados de búsqueda a través de hosts y categorías.
Edición Masiva de Categorías de Hosts: Podéis añadir una nueva categoría o editar una existente para una producción añadiendo hosts desde la configuración de producción.
Routers Expandibles: Los routers pueden ser expandidos para ver todas las reglas, transformaciones y conexiones en línea.
Conexiones de Hosts Reformuladas: Las conexiones directas e indirectas ahora se muestran cuando se selecciona un host de negocio, permitiendo ver el camino completo que un mensaje puede tomar. Colocad el cursor sobre cualquier host de salida o entrada para diferenciar mejor las conexiones. El interruptor Mostrar solo Hosts Conectados filtrará solo los hosts seleccionados y sus conexiones.
DTL Editor - Introducción a las Herramientas DTL
Integración con Control de Versiones: El soporte para la integración con control de versiones está disponible.
Integración con VS Code: Los usuarios pueden ver esta versión del Editor DTL en su IDE de VS Code.
Soporte de Python Embebido: El soporte de Python embebido se extiende a esta versión del Editor DTL.
Pruebas de DTL: La utilidad de prueba DTL está disponible en esta versión del Editor DTL.
Cambio de Diseño del Panel: El editor DTL soporta un diseño de lado a lado y de arriba a abajo. Haced clic en el botón de diseño en la cinta superior para experimentar con esto.
Deshacer/Volver a Hacer: Los usuarios pueden deshacer y rehacer todas las acciones con los botones de deshacer/rehacer que aún no se han guardado en el código.
Generar Parámetro de Segmentos Vacíos: El parámetro GENERATEEMPTYSEGMENTS está disponible para generar segmentos vacíos para los campos faltantes.
Visualización de Subtransformaciones: Los usuarios pueden ver subtransformaciones haciendo clic en el ícono del ojo para abrir el DTL de la subtransformación en una nueva pestaña.
Desplazamiento:
Desplazamiento Independiente: Las secciones izquierda y derecha (origen y destino) del DTL pueden desplazarse de manera independiente colocando el cursor sobre una de las secciones y utilizando la rueda de desplazamiento o el trackpad para mover los segmentos verticalmente.
Desplazamiento Conjunto: Ambas secciones, origen y destino, pueden desplazarse juntas colocando el cursor en el medio del diagrama.
Autocompletado de Campos: El autocompletado está disponible para los campos: 'origen', 'destino' y 'condición', así como para la Clase de Origen, Tipo de Documento de Origen, Clase de Destino y Tipo de Documento de Destino.
Numeración Ordinal: El editor visual permite activar y desactivar la visualización de los números ordinales y la expresión del camino completo para cada segmento.
Referencias Fáciles: Cuando un campo en el Editor de Acción está enfocado, al hacer doble clic en un segmento en el Editor Gráfico se inserta la referencia del segmento correspondiente en la posición actual del cursor en el Editor de Acción.
Sincronización: Al hacer clic en un elemento en el editor visual, se resalta la fila correspondiente en el editor de acción.
📣 LLAMADA A LA ACCIÓN 📣
Si tenéis comentarios, por favor proporcionadlos a través de los siguientes canales:
✨ Nuevas Características en toda la Interoperabilidad: Introducid una idea en el portal de ideas o participad en otras ideas en el Portal de Ideas de InterSystems. Para nuevas ideas, añadid la etiqueta "Interoperabilidad" en vuestro post o votad positivamente por las características ya propuestas en la lista.
💻 Comentarios Generales sobre la Experiencia del Usuario en toda la Interoperabilidad: Comentad vuestros comentarios o participad en otros comentarios a continuación.
🗒 Sugerencias/Comentarios sobre Aplicaciones Modernizadas (como se describe arriba): Comentad vuestros comentarios o participad en otros comentarios a continuación.
¡Considerad completar la oportunidad de Global Master's para interactuar con el equipo en una sesión de retroalimentación guiada privada y ganar puntos! ¡Inscribíos en estas sesiones a través de Global Masters >> aquí!
Si queréis proporcionar comentarios adicionales de manera privada, por favor enviad vuestros pensamientos o preguntas a: ux@intersystems.com.
Artículo
Ricardo Paiva · 3 mayo, 2022
Todo el código fuente del artículo está disponible en: https://github.com/antonum/ha-iris-k8s
En el [artículo anterior](https://es.community.intersystems.com/post/implementaci%C3%B3n-de-iris-con-alta-disponibilidad-en-kubernetes-sin-mirroring), comentamos cómo configurar IRIS en el clúster k8s con alta disponibilidad, basado en el almacenamiento distribuido, en vez del *mirroring* tradicional. Como ejemplo, ese artículo utilizaba el clúster Azure AKS. En esta ocasión, seguiremos explorando las configuraciones de alta disponibilidad en los k8s. Esta vez, basado en Amazon EKS (servicio administrado para ejecutar Kubernetes en AWS) e incluiría una opción para hacer copias de seguridad y restauraciones de la base de datos, basado en Kubernetes Snapshot.
## Instalación
Vamos a lo más importante. Primero: necesitas una cuenta de AWS, y las herramientas [AWS CLI,](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) [kubectl](https://kubernetes.io/docs/tasks/tools/) y [eksctl](https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html) instaladas. Para crear el nuevo clúster, ejecuta el siguiente comando:
eksctl create cluster \
--name my-cluster \
--node-type m5.2xlarge \
--nodes 3 \
--node-volume-size 500 \
--region us-east-1
Este comando tarda unos 15 minutos, implementa el clúster EKS y lo convierte en un clúster predeterminado para tu herramienta kubectl. Puedes verificar la implementación ejecutando:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-192-168-19-7.ca-central-1.compute.internal Ready <none> 18d v1.18.9-eks-d1db3c
ip-192-168-37-96.ca-central-1.compute.internal Ready <none> 18d v1.18.9-eks-d1db3c
ip-192-168-76-18.ca-central-1.compute.internal Ready <none> 18d v1.18.9-eks-d1db3c
El siguiente paso es instalar el motor de almacenamiento distribuido Longhorn.
kubectl create namespace longhorn-system
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.0/deploy/iscsi/longhorn-iscsi-installation.yaml --namespace longhorn-system
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml --namespace longhorn-system
Y, por último, el propio IRIS:
kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml
En este momento, tendrás un clúster EKS completamente funcional con el almacenamiento distribuido de Longhorn y la implementación de IRIS instaladas. Puedes volver al artículo anterior e intentar hacer todo tipo de daño en el clúster y la implementación de IRIS, solo para ver cómo el sistema se repara a sí mismo. Echa un vistazo a la sección ["Simulación del error"](https://es.community.intersystems.com/post/implementaci%C3%B3n-de-iris-con-alta-disponibilidad-en-kubernetes-sin-mirroring).
## Bonus #1 IRIS en ARM
IRIS EKS y Longhorn son compatibles con la arquitectura ARM, por lo que podemos implementar la misma configuración utilizando instancias de AWS Graviton2, basadas en la arquitectura ARM.
Todo lo que necesitas es cambiar el tipo de instancia para los nodos EKS a la familia "m6g" y la imagen IRIS a la basada en ARM.
eksctl create cluster \
--name my-cluster-arm \
--node-type **m6g.2xlarge** \
--nodes 3 \
--node-volume-size 500 \
--region us-east-1
tldr.yaml
containers:
#- image: store/intersystems/iris-community:2020.4.0.524.0
- image: store/intersystems/irishealth-community-arm64:2020.4.0.524.0
name: iris
O simplemente utiliza:
kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr-iris4h-arm.yaml
¡Eso es todo! Ya tienes el clúster IRIS Kubernetes, ejecutándose en la plataforma ARM.
## Bonus #2 Copia de seguridad y restauración
Una parte de la arquitectura para nivel de producción que se suele pasar por alto es la capacidad de crear copias de seguridad de la base de datos y que las restaure y/o clone rápidamente cuando sea necesario.
En Kubernetes, la manera más habitual de hacerlo es utilizando Persistent Volumen Snapshots (Snapshots de volumen persistente).
En primer lugar, debes instalar todos los componentes de k8s requeridos:
#Install CSI Snapshotter and CRDs
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -n kube-system -f https://github.com/kubernetes-csi/external-snapshotter/raw/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
A continuación, configura las credenciales del bucket S3 para Longhorn (consulta [estas instrucciones](https://longhorn.io/docs/1.1.0/snapshots-and-backups/backup-and-restore/set-backup-target/)):
#Longhorn backup target s3 bucket and credentials longhorn would use to access that bucket
#See https://longhorn.io/docs/1.1.0/snapshots-and-backups/backup-and-restore/set-backup-target/ for manual setup instructions
longhorn_s3_bucket=longhorn-backup-123xx #bucket name should be globally unique, unless you want to reuse existing backups and credentials
longhorn_s3_region=us-east-1
longhorn_aws_key=AKIAVHCUNTEXAMPLE
longhorn_aws_secret=g2q2+5DVXk5p3AHIB5m/Tk6U6dXrEXAMPLE
El siguiente comando tomará las variables de entorno del paso anterior y las utilizará para configurar la copia de seguridad de Longhorn
#configure Longhorn backup target and credentials
cat <<EOF | kubectl apply -f -
apiVersion: longhorn.io/v1beta1
kind: Setting
metadata:
name: backup-target
namespace: longhorn-system
value: "s3://$longhorn_s3_bucket@$longhorn_s3_region/" # backup target here
---
apiVersion: v1
kind: Secret
metadata:
name: "aws-secret"
namespace: "longhorn-system"
labels:
data:
# echo -n '<secret>' | base64
AWS_ACCESS_KEY_ID: $(echo -n $longhorn_aws_key | base64)
AWS_SECRET_ACCESS_KEY: $(echo -n $longhorn_aws_secret | base64)
---
apiVersion: longhorn.io/v1beta1
kind: Setting
metadata:
name: backup-target-credential-secret
namespace: longhorn-system
value: "aws-secret" # backup secret name here
EOF
Puede parecer mucho, pero esencialmente le indica a Longhorn que utilice un bucket S3 específico con las credenciales precisas para almacenar el contenido de las copias de seguridad.
¡Eso es todo! Si ahora vas a la interfaz de usuario de Longhorn, podrás crear copias de seguridad, restaurarlas, etc.

Un recordatorio rápido sobre cómo conectarse a la interfaz de usuario de Longhorn:
kubectl get pods -n longhorn-system
# note the full pod name for 'longhorn-ui-...' pod
kubectl port-forward longhorn-ui-df95bdf85-469sz 9000:8000 -n longhorn-system
Esto reenviaría el tráfico desde la interfaz de usuario de Longhorn a tu http://localhost:9000
## Programación de la copia de seguridad/restauración
Hacer copias de seguridad y restauraciones a través de la interfaz de usuario de Longhorn podría ser un primer paso suficientemente bueno, pero vamos a ir un paso más allá y realizar copias de seguridad y restauraciones programáticamente, utilizando APIs de Snapshots k8s.
En primer lugar, el propio snapshot. iris-volume-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
name: iris-longhorn-snapshot
spec:
volumeSnapshotClassName: longhorn
source:
persistentVolumeClaimName: iris-pvc
Este snapshot de volumen se refiere al volumen de la fuente "iris-pvc" que usamos para nuestra implementación de IRIS. Así que con solo aplicar esto, el proceso para crear una copia de seguridad se iniciaría inmediatamente .
Es una buena idea ejecutar el Freeze/Thaw de IRIS Write Daemon antes o después del snapshot.
#Freeze Write Daemon
echo "Freezing IRIS Write Daemon"
kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalFreeze()"
status=$?
if [[ $status -eq 5 ]]; then
echo "IRIS WD IS FROZEN, Performing backup"
kubectl apply -f backup/iris-volume-snapshot.yaml -n $namespace
elif [[ $status -eq 3 ]]; then
echo "IRIS WD FREEZE FAILED"
fi
#Thaw Write Daemon
kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalThaw()"
El proceso de restauración es bastante sencillo. Esencialmente se crea un nuevo PVC y se especifican los snapshots como la fuente.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: iris-pvc-restored
spec:
storageClassName: longhorn
dataSource:
name: iris-longhorn-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
A continuación, solo hay que crear una nueva implementación, basada en este PVC. Comprueba este [script de prueba en un repositorio de github](https://github.com/antonum/ha-iris-k8s/blob/main/backup/test.sh) que podría realizarse con la siguiente secuencia:
* Crear una nueva implementación de IRIS
* Añadir algunos datos a IRIS
* Freeze Write Daemon, tome un snapshot, thaw Write Daemon
* Crear un clon de implementación de IRIS, basado en el snapshot
* Verificar que todos los datos todavía están allí
En este momento tendrá dos implementaciones idénticas de IRIS, una de las cuales es un clon hecho por medio de una copia de seguridad de la otra.
¡Espero que os resulte útil!
Artículo
Ricardo Paiva · 21 nov, 2022
En la primera parte de este artículo he mostrado cómo empezar un nuevo proyecto en Django, y cómo definir nuevos modelos y añadir modelos ya existentes.
En esta publicación, voy a mostrar un Panel de Administración (disponible con la configuración predeterminada) y cómo puede ser útil.
Nota importante: si intentáis reproducir los pasos de este artículo, no funcionará para vosotros. Porque mientras escribía la publicación he realizado varios ajustes en el proyecto django-iris, e incluso en el driver DB-API de InterSystems, para arreglar algunos problemas ahí también, y creo que el driver aún está en desarrollo y habrá un driver más estable en el futuro. Así que vamos a asumir que este artículo solo explica cómo podría ser si tuviéramos todo terminado.
Vamos a volver a nuestro código y ver lo que tenemos en urls.py, el principal punto de entrada de todas las solicitudes web.
"""main URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
Se puede ver que ya hay una URL admin definida ahí.
Empezamos el servidor de desarrollo, por comando
python manage.py runserver
Si se va por la URL http://localhost:8000/admin, aparece el formulario de inicio de sesión de Django administration
Para entrar aquí, hace falta un usuario, que podemos crear con este comando
$ python manage.py createsuperuser
Username (leave blank to use 'daimor'): admin
Email address: admin@example.com
Password:
Password (again):
The password is too similar to the username.
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
Ya podemos usar ese usuario y contraseña. Está bastante vacío ahora, pero ya da acceso a Groups y Users.
Más datos
Previamente ya he instalado el paquete post-and-tags con zpm. Lo podéis instalar también
zpm "install posts-and-tags"
Ahora podemos obtener los modelos para todas las tablas (community.post, community.comment, community.tag) instaladas con este paquete
$ python manage.py inspectdb community.post community.comment community.tag > main/models.py
Esto producirá un archivo un poco largo, así que lo he puesto en este desplegable:
main/models.py
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# * Make sure each ForeignKey and OneToOneField has `on_delete` set to the desired behavior
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from django.db import models
class CommunityPost(models.Model):
acceptedanswerts = models.DateTimeField(db_column='AcceptedAnswerTS', blank=True, null=True) # Field name made lowercase.
author = models.CharField(db_column='Author', max_length=50, blank=True, null=True) # Field name made lowercase.
avgvote = models.IntegerField(db_column='AvgVote', blank=True, null=True) # Field name made lowercase.
commentsamount = models.IntegerField(db_column='CommentsAmount', blank=True, null=True) # Field name made lowercase.
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase.
deleted = models.BooleanField(db_column='Deleted', blank=True, null=True) # Field name made lowercase.
favscount = models.IntegerField(db_column='FavsCount', blank=True, null=True) # Field name made lowercase.
hascorrectanswer = models.BooleanField(db_column='HasCorrectAnswer', blank=True, null=True) # Field name made lowercase.
hash = models.CharField(db_column='Hash', max_length=50, blank=True, null=True) # Field name made lowercase.
lang = models.CharField(db_column='Lang', max_length=50, blank=True, null=True) # Field name made lowercase.
name = models.CharField(db_column='Name', max_length=250, blank=True, null=True) # Field name made lowercase.
nid = models.IntegerField(db_column='Nid', primary_key=True) # Field name made lowercase.
posttype = models.CharField(db_column='PostType', max_length=50, blank=True, null=True) # Field name made lowercase.
published = models.BooleanField(db_column='Published', blank=True, null=True) # Field name made lowercase.
publisheddate = models.DateTimeField(db_column='PublishedDate', blank=True, null=True) # Field name made lowercase.
subscount = models.IntegerField(db_column='SubsCount', blank=True, null=True) # Field name made lowercase.
tags = models.CharField(db_column='Tags', max_length=350, blank=True, null=True) # Field name made lowercase.
text = models.TextField(db_column='Text', blank=True, null=True) # Field name made lowercase.
translated = models.BooleanField(db_column='Translated', blank=True, null=True) # Field name made lowercase.
type = models.CharField(db_column='Type', max_length=50, blank=True, null=True) # Field name made lowercase.
views = models.IntegerField(db_column='Views', blank=True, null=True) # Field name made lowercase.
votesamount = models.IntegerField(db_column='VotesAmount', blank=True, null=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'community.post'
class CommunityComment(models.Model):
id1 = models.CharField(db_column='ID1', primary_key=True, max_length=62) # Field name made lowercase.
acceptedanswerts = models.DateTimeField(db_column='AcceptedAnswerTS', blank=True, null=True) # Field name made lowercase.
author = models.CharField(db_column='Author', max_length=50, blank=True, null=True) # Field name made lowercase.
avgvote = models.IntegerField(db_column='AvgVote', blank=True, null=True) # Field name made lowercase.
correct = models.BooleanField(db_column='Correct', blank=True, null=True) # Field name made lowercase.
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase.
hash = models.CharField(db_column='Hash', max_length=50, blank=True, null=True) # Field name made lowercase.
id = models.IntegerField(db_column='Id') # Field name made lowercase.
post = models.CharField(db_column='Post', max_length=50, blank=True, null=True) # Field name made lowercase.
text = models.TextField(db_column='Text', blank=True, null=True) # Field name made lowercase.
texthash = models.CharField(db_column='TextHash', max_length=50, blank=True, null=True) # Field name made lowercase.
type = models.CharField(db_column='Type', max_length=50) # Field name made lowercase.
votesamount = models.IntegerField(db_column='VotesAmount', blank=True, null=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'community.comment'
unique_together = (('type', 'id'),)
class CommunityTag(models.Model):
description = models.TextField(db_column='Description', blank=True, null=True) # Field name made lowercase.
name = models.TextField(db_column='Name', primary_key=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'community.tag'
El dashboard de administración en Django, se puede extender mediante desarrollo. Y es posible añadir algunas tablas más. Para hacerlo, tenemos que añadir un nuevo archivo llamado main/admin.py. He añadido unos pocos comentarios directamente en el código, para explicar algunas líneas.
from django.contrib import admin
# immport our community models for our tables in IRIS
from .models import (CommunityPost, CommunityComment, CommunityTag)
# register class which overrides default behaviour for model CommunityPost
@admin.register(CommunityPost)
class CommunityPostAdmin(admin.ModelAdmin):
# list of properties to show in table view
list_display = ('posttype', 'name', 'publisheddate')
# list of properties to show filter for on the right side of the tablee
list_filter = ('posttype', 'lang', 'published')
# default ordering, means from the latest date of PublishedDate
ordering = ['-publisheddate', ]
@admin.register(CommunityComment)
class CommunityCommentAdmin(admin.ModelAdmin):
# only this two fields show, (post is numeric by id in table post)
list_display = ('post', 'created')
# order by date of creation
ordering = ['-created', ]
@admin.register(CommunityTag)
class CommunityTagAdmin(admin.ModelAdmin):
# not so much to show
list_display = ('name', )
Portal extendido
Vamos a volver a nuestra página de administración en Django, y vemos algunas cosas nuevas ahí
A la derecha se ve el panel de filtros, y lo que es importante es que tiene todos los posibles valores de cada campo.
Desafortunadamente, InterSystems SQL no es compatible con la funcionalidad LIMIT, OFFSET. Y Django no es compatible con TOP. Así que se mostrará aquí la paginación, pero no funciona. Y no hay forma de hacerla funcionar por el momento, y no creo que nunca funcione, lamentablemente.
Se puede incluso analizar el objeto, y Django mostrará el formulario, con los tipos de campo correctos. (Nota: Este conjunto de datos no contiene datos en el campo Text)
Objeto Comments
Problemas esperados con licencias de la Community Edition
Si trabajáis con la Community Edition, podéis encontraros este problema, así es como se ve cuando todas las conexiones están ocupadas, puede pasar muy rápido. Y si véis que la respuesta del servidor es bastante larga, seguramente es el caso, porque IRIS no responde rápido en este caso; por alguna razón lleva mucho tiempo.
Incluso cuando IRIS dice, te queda capacidad
No permite más de 5 conexiones, así que necesitaréis terminar uno o más procesos para que funcione. O reiniciar el servidor de Django
En desarrollo, también se puede limitar el servidor Django al modo no threading, y funcionará en un proceso. Y no debería solicitar más conexiones a IRIS.
python manage.py runserver --nothreading
Artículo
Daniel Aguilar · 26 jun, 2023
¡Hola Comunidad!
¿Habéis tenido que conectar alguna vez IRIS con un sistema SAP?
Tuve que enfrentarme al reto de conectar InterSystems IRIS con SAP, y una vez más pude comprobar el gran acierto que hizo InterSystems añadiendo la posibilidad de ejecutar código nativamente de Python desde IRIS.
Esto me hizo la integración muy fácil gracias a la librería pyrfc.
Con esta librería, fui capaz de realizar llamadas a RFC's de SAP (Remote Function Call) desde una clase de IRIS y recibir datos de la base de datos de SAP.
Pero.., ¿qué es una RFC?
RFC es un protocolo de comunicación usado por SAP. Permite la interacción entre diferentes sistemas SAP o no SAP. También permite a aplicaciones externas comunicarse con el sistema SAP y acceder a las funciones y datos disponibles.
Cuando una RFC es ejecutada, una aplicación externa envía una petición sobre la red al sistema SAP. El sistema SAP recibe la petición, la procesa, y devuelve la respuesta con el resultado o datos a la aplicación externa.
RFC se puede usar para tareas de integración, ejecución de programas y recibir información desde SAP.
En resumen, una RFC de SAP es una llamada remota a una función o servicio disponible en un sistema SAP que permite la comunicación y el intercambio de información entre sistemas, sistemas externos o aplicaciones.
Finalidad del artículo:
Me he decidido a escribir este artículo para describir los pasos que he seguido y el resultado recibido por si alguno se ve en la misma situación poder facilitarle la tarea.
También he publicado una aplicación en el OpenExchange (link) con casi todo lo necesario para arrancar el Docker y conectar con el sistema SAP.
¿Porqué digo casi todo?. Para poder conectar con un servidor SAP, necesitamos descargar el SAP NetWeaver RFC SDK. Por desgracias, está protegido por copyright, y, por este motivo, no puedo compartir los fichero con vosotros. Por lo que si queréis descargar el SDK de la página de SAP debéis tener un usuario de tipo "S".
La aplicación del OpenExchange está lista para descargar, añadir los ficheros del SDK, configurar la conexión con el servidor SAP y ejecutar.
¿Te interesa?, Genial, pues vamos!!
Pasos para descargar SAP NetWeaver RFC SDK:
Una vez tengamos nuestro usuario tipo "S" (Si no tienes un usuario tipo "S" pide a tu administrador del sistema SAP que te facilite uno), debemos entrar en el siguiente link.
Hacemos clic en el link con las instrucciones de descarga:
Después, clic en el link de descarga de los ficheros:
Finalmente, clic en “SAP NW RFC SDK 7.50”
Elegimos Linux X86_64 Bit version (si tienes planeado usar mi aplicación del OpenExchange con Docker) y hacemos click en el botón "descargar":
Añadiendo los ficheros SAP NetWeaver RFC SDK a el projecto:
Cuando tengamos descargados y descomprimidos los ficheros, debemos copiar el contenido de la carpeta "nwrfcsdk" dentro de la carpeta "nwrfcsdk" del proyecto.
Explicación de los archivos auxiliares y el Dockerfile:
El fichero "nwrfcsdk.conf" debe contener la ruta donde los ficheros SDK van a ser copiados. Si no tienes pensado cambiar la ruta no es necesario modificar nada.
El fichero Dockerfile ya incluye el código necesario para descargar e instalar las librerías necesarias para realizar la conexión:
Lo que hace es copiar el fichero "nwrfcsdk.conf", el contenido de la carpeta "nwrfcsdk" y el fichero "sapnwrfc.cfg" (este fichero lo explico luego) dentro del Docker.
También instala las librerías de Python:
pyrfc: se usa para ejecutar las RFC de SAP
configparser: se usa para leer los parámetros de la conexión almacenados en el fichero de configuración sapnwrfc.cfg
Configurando la conexión con el servidor SAP:
Para conectar con el servidor SAP, debes completar la información requerida en el fichero "sapnwrfc.cfg".
Si no tienes la información, pero puedes entrar en tu servidor SAP, sigue los siguientes pasos para encontrarla. Si no tienes acceso al servidor SAP, deberás preguntarle al administrador del sistema SAP la información.
Obtener la información de conexión desde el servidor SAP:
Las capturas de pantalla puede variar en función de si estás usando SAP GUI (usuarios Windows) o SAP GUI for JAVA (usuarios macOS y Linux). En mi caso, es SAP GUI for JAVA.
1 - Entra en SAP GUI y elige tu servidor. Después, haz click en el botón conectar:
2 - Introduce tu usuario y contraseña y aprieta "Enter" o haz clic en el botón aceptar:
3 - Una vez estés dentro del sistema, ve a la barra de menús y selecciona "System" y después la opción "Status" en el desplegable:
En esta ventana, tu puedes obtener la siguiente información:
El valor del campo "Client" para la propiedad "client" del fichero de configuración.
El valor del campo "User" para la propiedad "user" del fichero de configuración (En este caso, mi usuario del sistema SAP y el de RFC es el mismo. Aunque para entornos de producción, es recomendable usar usuarios diferentes).
El valor del campo "Host" para la propiedad "ashost" del fichero de configuración. (Puede ser un nombre de equipo o una dirección IP).
En el caso de que tu usuario para RFC sea el mismo que el del sistema SAP, debes usar la misma contraseña en la propiedad "passwd" del fichero de configuración.
Si tienes problemas con la conexión, comprueba la información con tu administrador de sistemas de SAP que tu usuario tiene permisos para ejecutar RFC's.
Una vez tengamos los pasos listos, estamos listos para empezar!!.
Comprobando la conexión con SAP
Para comprobar la conexión con SAP, puedes ejecutar el metodo TestConecction del la clase RFC.RFCUtil.cls
ClassMethod TestConnection() [ Language = python ]
{
try:
# Import python libraries
from pyrfc import Connection, ABAPApplicationError, ABAPRuntimeError, LogonError, CommunicationError
from configparser import ConfigParser
# Connect to the SAP Server
config = ConfigParser()
config.read('/opt/irisapp/sapnwrfc.cfg')
params_connection = config._sections['connection']
conn = Connection(**params_connection)
# Launch RFC call to STFC_CONNECTION
result = conn.call('STFC_CONNECTION', REQUTEXT=u'Hello SAP!')
# Close the connection
conn.close()
# Print the result
print(result)
except Exception as e:
print(e)
}
Si todo va bien, deberíais ver un mensaje en la consola como este:
(Si recibes un error por favor comprueba la información del fichero "sapnwrfc.cfg".)
Como puedes ver, la manera de ejecutar una RFC es muy fácil:
1 - Conectamos con el servidor SAP
2 - Ejecutamos el metodo conn.call('Nombre de la RFC a ejecutar', Parametros RFC)
3 - Cerramos la conexión
4 - Procesamos el resultado
En este ejemplo, el único parámetro admitido es un String, y lo hemos enviado como un parámetro del tipo REQUTEXT.
Consultar la información de una tabla de SAP
Ahora vamos a llamar a la RFC "RFC_READ_TABLE" que nos permite leer una tabla de SAP.
ClassMethod GetDataTableSFLIGHT() [ Language = python ]
{
try:
# Import python libraries
from pyrfc import Connection, ABAPApplicationError, ABAPRuntimeError, LogonError, CommunicationError
from configparser import ConfigParser
# Connect to the SAP Server
config = ConfigParser()
config.read('/opt/irisapp/sapnwrfc.cfg')
params_connection = config._sections['connection']
conn = Connection(**params_connection)
# Define query parameters
params = {
'QUERY_TABLE': 'SFLIGHT',
'DELIMITER': ',',
'FIELDS': [
{'FIELDNAME': 'CARRID'},
{'FIELDNAME': 'CONNID'},
{'FIELDNAME': 'FLDATE'},
{'FIELDNAME': 'PRICE'}
],
'OPTIONS': [],
}
# Call to función RFC 'RFC_READ_TABLE'
result = conn.call('RFC_READ_TABLE', **params)
# Process results
if 'DATA' in result:
data = result['DATA']
fields = result['FIELDS']
# Imprime los nombres de campo
for field in fields:
print(field['FIELDNAME'], end='\t')
print()
# Imprime los datos
for entry in data:
values = entry['WA'].split(',')
for value in values:
print(value, end='\t')
print()
else:
print('No data found.')
# Close SAP connection
conn.close()
except CommunicationError:
print("Could not connect to server.")
raise
except LogonError:
print("Could not log in. Wrong credentials?")
raise
except (ABAPApplicationError, ABAPRuntimeError):
print("An error occurred.")
raise
except Exception as e:
print(e)
}
Para este ejemplo, hemos pasado como parámetro una variable tipo estructura con la siguiente información:
QUERY_TABLE: Es la tabla que queremos consultar (SFLIGHT tabla demo de vuelos de SAP)
DELIMITER: Nos permite elegir el separador
FIELDS: Son las columnas que queremos consultar, en este caso estoy consultado CARRID (Id de la compañia), CONNID (Id conexión de vuelo), FLDATE (fecha del vuelo), y PRICE (precio del vuelo).
Una vez ejecutado, podemos procesar la respuesta y imprimirla por pantalla o lo que queramos hacer con ella... xD.
Finalmente, cerramos la conexión.
Si todo ha ido bien, deberíais ver algo parecido a esto en la consola:
Consultando datos de una tabla filtrando:
Este va a ser un ejemplo un como mas complejo, vamos a consultar la tabla de clientes para obtener los datos de un único cliente filtrando por su id.
ClassMethod GetDataCustomer(clientSapID As %String) [ Language = python ]
{
try:
from pyrfc import Connection, ABAPApplicationError, ABAPRuntimeError, LogonError, CommunicationError
from configparser import ConfigParser
config = ConfigParser()
config.read('/opt/irisapp/sapnwrfc.cfg')
params_connection = config._sections['connection']
conn = Connection(**params_connection)
# Define the parameters
params = {
'QUERY_TABLE': 'KNA1',
'DELIMITER': ',',
'FIELDS': [{'FIELDNAME': 'KUNNR'}, {'FIELDNAME': 'SORTL'}, {'FIELDNAME': 'TELF1'}],
'OPTIONS': [{'TEXT': "KUNNR = '" + clientSapID + "'"}],
}
# Call the RFC 'RFC_READ_TABLE' to obtain the data
result = conn.call('RFC_READ_TABLE', **params)
# Process the result
if 'DATA' in result:
data = result['DATA']
fields = result['FIELDS']
# Print the fields names
for field in fields:
print(field['FIELDNAME'], end='\t')
print()
# Print the data fields.
for entry in data:
values = entry['WA'].split(',')
for value in values:
print(value, end='\t')
print()
else:
print('No data found.')
# Close the connection
conn.close()
except CommunicationError:
print("Could not connect to server.")
raise
except LogonError:
print("Could not log in. Wrong credentials?")
raise
except (ABAPApplicationError, ABAPRuntimeError):
print("An error occurred.")
raise
except Exception as e:
print(e)
}
Parámetros:
QUERY_TABLE: Es la tabla que queremos consultar (KNA1 es la tabla de clientes en SAP)
DELIMITER: Nos permite elegir el separador
FIELDS: Son las columnas que queremos consultar, en este caso estoy consultando KUNNR (id de cliente SAP), SORTL (nombre corto cliente), y TELF1 (Teléfono del cliente).
OPTIONS: Es el filtro que queremos aplicar a la consulta. En este caso, estamos filtrando por el id de cliente recibido por parámetro en el metodo.
Una vez ejecutado, podemos procesar la respuesta e imprimirla por pantalla o lo que queramos... xD.
Finalmente, cerramos la conexión.
Si todo ha ido bien, deberíamos ver algo similar a esto:
Esto es solo el principio ya que gracias a las llamadas RFC, podemos no solo realizar operaciones de creación, lectura, actualizado, y borrado, también podemos ejecutar cualquier código de SAP (estandard o personalizado) expuesto por una RFC, programas, modulos de funciones, etc... RFC se no solo se usa para leer o escribir datos, también se puede usar para ejecutar programas o funciones.
Espero que algún día si os veis en la situación de integrar InserSystems IRIS con SAP, este artículo pueda seros de ayuda.
Si tenéis cualquier duda, por favor escribidme en los comentarios e intentaré ayudaros.
¡Muchas gracias por leerme!!
Link a la aplicación en OpenExchange: https://openexchange.intersystems.com/package/IrisSapConnector
Link al repositorio: https://github.com/daniel-aguilar-garcia/IrisSapConnector/blob/main/sapnwrfc.cfg
Link a la librería pyrfc: https://sap.github.io/PyRFC/ Gracias por el aporte @Daniel.Aguilar . Muy interesante!
Artículo
Javier Lorenzo Mesa · 16 jul, 2021
Tengo algunos modelos analíticos y numerosos paneles de control, y estoy listo para implementarlos en nuestros usuarios finales y administradores. ¿Cómo configurar DeepSee para que los usuarios no alteren las áreas de los demás y se les restrinja el uso de funciones específicas para los desarrolladores?
Ejecutar un sistema de Business Intelligence requiere, con frecuencia, configurar un modelo de seguridad. Este tutorial mostrará cómo configurar un modelo de seguridad sencillo para DeepSee.
El modelo de seguridad se basa en tres tipos de usuarios. Primero, crearemos un usuario sencillo para DeepSee que tenga acceso pero no pueda editar los paneles de control en DeepSee. El segundo tipo de usuario tendrá acceso a las tablas dinámicas en Analyzer y podrá visualizar, editar y crear paneles de control. Finalmente, los usuarios que son “Administradores” tendrán un control más amplio de la implementación, como el acceso a Architect.
También veremos cómo proteger y controlar la visibilidad de los elementos del modelo, como tablas dinámicas, paneles de control, modelos analíticos, etc. Esperamos que estos consejos para solucionar problemas faciliten la implementación de un modelo de seguridad.
Antes de empezar
En este artículo configuraremos un modelo de seguridad básico. Para ello, es necesario familiarizarse con esta página sobre Cómo configurar la seguridad para DeepSee. Si estás probando o creando una prueba de concepto, no utilices la base de datos o el namespace SAMPLES, ya que tiene una configuración especial. En vez de ello, trabaja en un namespace (por ejemplo, APP) con base(s) de datos dedicada(s) (por ejemplo, APP-DATA).
Para continuar con este tutorial, crea un namespace APP basado en una base de datos de APP-DATA en el Portal de administración de seguridad [SMP] > Configuration > System Configuration > Namespaces. Asigna un nuevo recurso %DB_APP-DATA a la base de datos recién creada. Asegúrate de que la aplicación web predeterminada para el namespace APP (/csp/app) está habilitada en DeepSee. Asumimos que tienes un servidor o una instalación personalizada, desde los que puedes iniciar sesión en Caché con un usuario que tiene los privilegios suficientes para ejecutar las operaciones de esta publicación.
Cómo conceder acceso de solo lectura a los paneles de control
En una implementación habitual, se permite que los usuarios finales utilicen Analytics, pero no pueden editar la implementación como tal. En esta sección definiremos un tipo de usuario que solo puede acceder pero no editar los paneles de control de DeepSee.
Cómo crear un rol DSUser
Según la documentación (mira la fila de la tarea "Viewing the User Portal apart from the Analyzer or the mini Analyzer with no ability to create dashboards" / “Ver el Portal de usuario de forma independiente a Analyzer o al mini Analyzer sin la capacidad de crear paneles” en la tabla), necesitamos permisos de USO para utilizar los recursos de %DeepSee_Portal.
Crea un rol DSUser que incluya los siguientes recursos:
Recurso
Permiso
%DeepSee_Portal
USE
%DB_APP-DATA
RW
Los permisos U y RW para el recurso que se encuentra en la tabla anterior deben configurarse automáticamente cuando asignes los roles. Dependiendo de tu namespace, base de datos y configuración de diagramas, necesitarás un permiso RW para proteger los recursos de las bases de datos. En nuestro ejemplo, %DB_APP-DATA es necesario para acceder a la base de datos predeterminada del namespace APP.
Cómo crear un simpleuser
En Users page > Create new user puedes crear un simpleuser con el rol DSUser asignado, como se muestra en esta captura de pantalla:
Cómo probar simpleuser
Abre una ventana de incógnito o privada en el navegador e inicia sesión como simpleuser. Desde el portal de administración, comprueba que las pestañas Architect y Analyzer en la sección DeepSee aparecen sombreadas en gris. Ve al User Portal / Portal de Usuario y confirma que simpleuser puede visualizar los paneles de control. También confirma que simpleuser no puede ver el botón Save en los paneles de control ni el icono “+” en el User Portal para crear elementos en la carpeta. Ten en cuenta que simpleuser puede visualizar tablas dinámicas en el User Portal, pero cuando intente ver una tabla dinámica debería mostrar que el usuario no está autorizado a ver la página. En esta sección de la parte 5, más adelante, veremos cómo ocultar las tablas dinámicas en el User Portal.
Consejo: utiliza dos ventanas del navegador. Una ventana se puede utilizar para iniciar sesión con un usuario administrador (por ejemplo, _SYSTEM o SuperUser) que puede cambiar la configuración del sistema. En la otra ventana, utiliza un navegador en modo incógnito (Chrome) o privado (Firefox, Edge) e inicia sesión con un usuario de prueba. El modo incógnito/privado asegurará que el caché del navegador de la otra ventana no interfiere con tu trabajo y genere algún comportamiento inesperado.
Consejo: en el Portal de administración utiliza el botón Menu en la esquina superior izquierda para navegar rápidamente a las páginas de Users / Usuarios, Roles / Funciones, Resources / Recursos y Web applications / Aplicaciones web.
Cómo solucionar problemas: permisos públicos
Un problema habitual es encontrar que se permiten algunas funciones a pesar del modelo de seguridad. Como se explica de forma más amplia en la parte 5, una causa habitual de este comportamiento inesperado son los permisos públicos sobre los recursos. Por ejemplo, podrías ver que simpleuser es capaz de crear nuevos paneles de control en el User Portal. Si el recurso %DeepSee\_PortalEdit todavía tiene asignados permisos públicos de USE, cualquier usuario podrá crear tablas dinámicas y paneles de control. Para resolver este problema, elimina los permisos públicos de USE en el recurso %DeepSee\_PortalEdit.
En la parte 2 crearemos un segundo tipo de usuario que pueda editar y crear tablas dinámicas y paneles de control en DeepSee.
Este artículo está etiquetado como "Mejores prácticas" ("Best practices")
(Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).