查找

Digest
· Nov 3

Publications des développeurs d'InterSystems, semaine Octobre 27 - Novembre 02, 2025, Résumé

Octobre 27 - Novembre 02, 2025Week at a GlanceInterSystems Developer Community
Article
· Nov 3 13m read

Création d'un service d'API REST pour exporter le bundle FHIR généré au format JSON

Bonjour à tous,

Continuons à travailler sur la génération de données de test et l'exportation des résultats via une API REST. 😁

Ici, je souhaite réutiliser la classe `datagen.restservice` créée dans l'article précédent : « Écriture d'un service API REST pour exporter les données patient générées au format .csv ».

Cette fois-ci, nous prévoyons de générer un bundle FHIR incluant plusieurs ressources pour tester le référentiel FHIR.

Voici une référence si vous souhaitez en savoir plus sur FHIR : « The Concept of FHIR: A Healthcare Data Standard Designed for the Future ».

C'est parti ! 😆

1. Créez une nouvelle classe utilitaire `datagen.util.genfhirjson.cls` pour héberger les fonctions de génération de données FHIR.

Class datagen.utli.genfhirjson Extends %RegisteredObject
{
}

2. Écrivez une fonction Python `genfhirbundle` pour générer un bundle FHIR au format JSON (j'ai demandé de l'aide au Chat-GPT pour cette partie 😂🤫).

J'ai ajouté ici un argument `isirisfhir` (valeur par défaut : 0) pour indiquer si ce bundle FHIR est destiné au référentiel FHIR IRIS.

Ceci est nécessaire en raison de la syntaxe différente des références de ressources utilisant un UUID.

Par exemple, voici à quoi ressemble une référence FHIR pour une ressource patient utilisant un UUID.

 "subject": {
          "reference": "Patient/3ce18a5b-b904-4c77-8a33-b09c3f8c79cc"
        }

Le référentiel IRIS FHIR, qui référence une ressource patient à l'aide de son UUID, ressemble à ceci :

 "subject": {
          "reference": "3ce18a5b-b904-4c77-8a33-b09c3f8c79cc"
        }

Par conséquent, un argument est ajouté en tant que sélecteur.

Class datagen.utli.genfhirjson Extends %RegisteredObject
{

ClassMethod genfhirbundle(isirisfhir = 0) As %String [ Language = python ]
{
    # w ##class(datagen.utli.genfhirjson).genfhirbundle(0)
    # w ##class(datagen.utli.genfhirjson).genfhirbundle(1)
    from faker import Faker
    import random
    import json
    import uuid
    from datetime import datetime
    import base64

    fake = Faker()


    # ----------------------
    # Patient
    # ----------------------
    def generate_fhir_patient():
        patient_id = str(uuid.uuid4())
        return {
            "resourceType": "Patient",
            "id": patient_id,
            "identifier": [{
                "use": "usual",
                "system": "http://hospital.smarthealth.org/patient-ids",
                "value": str(random.randint(100000, 999999))
            }],
            "name": [{
                "use": "official",
                "family": fake.last_name(),
                "given": [fake.first_name()]
            }],
            "gender": random.choice(["male", "female"]),
            "birthDate": fake.date_of_birth(minimum_age=0, maximum_age=100).strftime("%Y-%m-%d"),
            "address": [{
                "use": "home",
                "line": [fake.street_address()],
                "city": fake.city(),
                "state": fake.state(),
                "postalCode": fake.postcode(),
                "country": fake.country()
            }],
            "telecom": [{
                "system": "phone",
                "value": fake.phone_number(),
                "use": "mobile"
            }],
            "meta": {"lastUpdated": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")}
        }


    # ----------------------
    # Practitioner
    # ----------------------
    def generate_fhir_practitioner():
        practitioner_id = str(uuid.uuid4())
        return {
            "resourceType": "Practitioner",
            "id": practitioner_id,
            "identifier": [{
                "system": "http://hospital.smarthealth.org/staff-ids",
                "value": str(random.randint(1000, 9999))
            }],
            "name": [{
                "family": fake.last_name(),
                "given": [fake.first_name()],
                "prefix": ["Dr."]
            }],
            "telecom": [{
                "system": "phone",
                "value": fake.phone_number(),
                "use": "work"
            }],
            "meta": {"lastUpdated": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")}
        }


    # ----------------------
    # Encounter
    # ----------------------
    def generate_fhir_encounter(patient_id, practitioner_id):
        encounter_id = str(uuid.uuid4())
        if isirisfhir==0:
            patient_ref=f"Patient/{patient_id}"
            practitioner_ref=f"Practitioner/{practitioner_id}"
        else:
            patient_ref=patient_id
            practitioner_ref=practitioner_id
        return {
            "resourceType": "Encounter",
            "id": encounter_id,
            "status": "finished",
            "class": {
                "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
                "code": "AMB",
                "display": "ambulatory"
            },
            "subject": {"reference": patient_ref},
            "participant": [{
                "individual": {"reference": practitioner_ref}
            }],
            "period": {
                "start": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
                "end": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
            }
        }


    # ----------------------
    # Condition
    # ----------------------
    def generate_fhir_condition(patient_id, encounter_id, practitioner_id):
        condition_id = str(uuid.uuid4())
        code_choice = random.choice([
            ("44054006", "Diabetes mellitus"),
            ("195967001", "Asthma"),
            ("38341003", "Hypertension"),
            ("254637007", "Viral upper respiratory tract infection")
        ])
        if isirisfhir==0:
            patient_ref=f"Patient/{patient_id}"
            practitioner_ref=f"Practitioner/{practitioner_id}"
            encounter_ref=f"Encounter/{encounter_id}"
        else:
            patient_ref=patient_id
            practitioner_ref=practitioner_id
            encounter_ref=encounter_id
        return {
            "resourceType": "Condition",
            "id": condition_id,
            "clinicalStatus": {
                "coding": [{
                    "system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
                    "code": "active"
                }]
            },
            "code": {
                "coding": [{
                    "system": "http://snomed.info/sct",
                    "code": code_choice[0],
                    "display": code_choice[1]
                }]
            },
            "subject": {"reference": patient_ref},
            "encounter": {"reference": encounter_ref},
            "asserter": {"reference": practitioner_ref},
            "onsetDateTime": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
        }


    # ----------------------
    # Vitals Observation
    # ----------------------
    def generate_fhir_vitals(patient_id, encounter_id, practitioner_id):
        observation_id = str(uuid.uuid4())
        if isirisfhir==0:
            patient_ref=f"Patient/{patient_id}"
            practitioner_ref=f"Practitioner/{practitioner_id}"
            encounter_ref=f"Encounter/{encounter_id}"
        else:
            patient_ref=patient_id
            practitioner_ref=practitioner_id
            encounter_ref=encounter_id
        return {
            "resourceType": "Observation",
            "id": observation_id,
            "status": "final",
            "category": [{
                "coding": [{
                    "system": "http://terminology.hl7.org/CodeSystem/observation-category",
                    "code": "vital-signs",
                    "display": "Vital Signs"
                }]
            }],
            "code": {
                "coding": [{
                    "system": "http://loinc.org",
                    "code": "85354-9",
                    "display": "Blood pressure panel"
                }]
            },
            "subject": {"reference": patient_ref},
            "encounter": {"reference": encounter_ref},
            "performer": [{"reference": practitioner_ref}],
            "effectiveDateTime": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
            "component": [
                {
                    "code": {
                        "coding": [{
                            "system": "http://loinc.org",
                            "code": "8480-6",
                            "display": "Systolic blood pressure"
                        }]
                    },
                    "valueQuantity": {
                        "value": random.randint(100, 140),
                        "unit": "mmHg",
                        "system": "http://unitsofmeasure.org",
                        "code": "mm[Hg]"
                    }
                },
                {
                    "code": {
                        "coding": [{
                            "system": "http://loinc.org",
                            "code": "8462-4",
                            "display": "Diastolic blood pressure"
                        }]
                    },
                    "valueQuantity": {
                        "value": random.randint(60, 90),
                        "unit": "mmHg",
                        "system": "http://unitsofmeasure.org",
                        "code": "mm[Hg]"
                    }
                }
            ]
        }


    # ----------------------
    # Lab Observations
    # ----------------------
    def generate_fhir_lab(patient_id, encounter_id, practitioner_id):
        labs = [
            ("2339-0", "Glucose [Mass/volume] in Blood", "mg/dL", random.randint(70, 200)),
            ("2093-3", "Cholesterol [Mass/volume] in Serum", "mg/dL", random.randint(150, 250)),
            ("4548-4", "Hemoglobin A1c/Hemoglobin.total in Blood", "%", round(random.uniform(4.5, 10.0), 1))
        ]
        lab_obs = []
        if isirisfhir==0:
            patient_ref=f"Patient/{patient_id}"
            practitioner_ref=f"Practitioner/{practitioner_id}"
            encounter_ref=f"Encounter/{encounter_id}"
        else:
            patient_ref=patient_id
            practitioner_ref=practitioner_id
            encounter_ref=encounter_id
        for loinc, display, unit, value in labs:
            obs_id = str(uuid.uuid4())
            lab_obs.append({
                "resourceType": "Observation",
                "id": obs_id,
                "status": "final",
                "category": [{
                    "coding": [{
                        "system": "http://terminology.hl7.org/CodeSystem/observation-category",
                        "code": "laboratory",
                        "display": "Laboratory"
                    }]
                }],
                "code": {
                    "coding": [{
                        "system": "http://loinc.org",
                        "code": loinc,
                        "display": display
                    }]
                },
                "subject": {"reference": patient_ref},
                "encounter": {"reference": encounter_ref},
                "performer": [{"reference": practitioner_ref}],
                "effectiveDateTime": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
                "valueQuantity": {
                    "value": value,
                    "unit": unit,
                    "system": "http://unitsofmeasure.org",
                    "code": unit
                }
            })
        return lab_obs


    # ----------------------
    # Medication + MedicationRequest
    # ----------------------
    def generate_fhir_medication(patient_id, encounter_id, practitioner_id):
        meds = [
            ("860975", "Metformin 500mg tablet"),
            ("860976", "Lisinopril 10mg tablet"),
            ("860977", "Salbutamol inhaler"),
            ("860978", "Atorvastatin 20mg tablet")
        ]
        med_code, med_name = random.choice(meds)
        med_id = str(uuid.uuid4())
        med_request_id = str(uuid.uuid4())
        if isirisfhir==0:
            patient_ref=f"Patient/{patient_id}"
            practitioner_ref=f"Practitioner/{practitioner_id}"
            encounter_ref=f"Encounter/{encounter_id}"
        else:
            patient_ref=patient_id
            practitioner_ref=practitioner_id
            encounter_ref=encounter_id

        medication = {
            "resourceType": "Medication",
            "id": med_id,
            "code": {
                "coding": [{
                    "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                    "code": med_code,
                    "display": med_name
                }]
            }
        }

        medication_request = {
            "resourceType": "MedicationRequest",
            "id": med_request_id,
            "status": "active",
            "intent": "order",
            "medicationReference": {"reference": f"Medication/{med_id}"},
            "subject": {"reference": patient_ref},
            "encounter": {"reference": encounter_ref},
            "requester": {"reference": practitioner_ref},
            "authoredOn": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
            "dosageInstruction": [{
                "text": f"Take {random.randint(1,2)} tablet(s) daily"
            }]
        }

        return [medication, medication_request]


    # ----------------------
    # DocumentReference (Discharge Summary)
    # ----------------------
    def generate_fhir_documentreference(patient_id, encounter_id, practitioner_id):
        doc_id = str(uuid.uuid4())
        note_text = f"Discharge summary for patient {patient_id}. Diagnosis: Stable. Follow-up in 2 weeks."
        encoded_note = base64.b64encode(note_text.encode("utf-8")).decode("utf-8")
        if isirisfhir==0:
            patient_ref=f"Patient/{patient_id}"
            practitioner_ref=f"Practitioner/{practitioner_id}"
            encounter_ref=f"Encounter/{encounter_id}"
        else:
            patient_ref=patient_id
            practitioner_ref=practitioner_id
            encounter_ref=encounter_id

        return {
            "resourceType": "DocumentReference",
            "id": doc_id,
            "status": "current",
            "type": {
                "coding": [{
                    "system": "http://loinc.org",
                    "code": "18842-5",
                    "display": "Discharge summary"
                }]
            },
            "subject": {"reference": patient_ref},
            "author": [{"reference": practitioner_ref}],
            "context": {"encounter": [{"reference": encounter_ref}]},
            "content": [{
                "attachment": {
                    "contentType": "text/plain",
                    "language": "en",
                    "data": encoded_note,
                    "title": "Discharge Summary Note"
                }
            }]
        }


    # ----------------------
    # Bundle Generator (transaction)
    # ----------------------
    def generate_patient_bundle(n=1):
        bundle = {
            "resourceType": "Bundle",
            "type": "transaction",
            "id": str(uuid.uuid4()),
            "meta": {"lastUpdated": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")},
            "entry": []
        }

        for _ in range(n):
            patient = generate_fhir_patient()
            practitioner = generate_fhir_practitioner()
            encounter = generate_fhir_encounter(patient["id"], practitioner["id"])
            condition = generate_fhir_condition(patient["id"], encounter["id"], practitioner["id"])
            vitals = generate_fhir_vitals(patient["id"], encounter["id"], practitioner["id"])
            labs = generate_fhir_lab(patient["id"], encounter["id"], practitioner["id"])
            meds = generate_fhir_medication(patient["id"], encounter["id"], practitioner["id"])
            docref = generate_fhir_documentreference(patient["id"], encounter["id"], practitioner["id"])

            all_resources = [patient, practitioner, encounter, condition, vitals] + labs + meds + [docref]

            for resource in all_resources:
                bundle["entry"].append({
                    "fullUrl": f"urn:uuid:{resource['id']}",
                    "resource": resource,
                    "request": {
                        "method": "POST",
                        "url": resource["resourceType"]
                    }
                })

        return bundle


    # ----------------------
    # Example Run
    # ----------------------
    fhir_bundle = generate_patient_bundle(1)

    return json.dumps(fhir_bundle, indent=2)
}

}

Testons la fonction à l'aide du script suivant, pour générer un bundle FHIR pour un dépôt FHIR autre qu'IRIS.

w ##class(datagen.utli.genfhirjson).genfhirbundle(0)

et vérifiez la syntaxe de référence

Testez maintenant la fonction avec le script suivant, pour générer un bundle FHIR pour le dépôt IRIS FHIR 😁

w ##class(datagen.utli.genfhirjson).genfhirbundle(1)

et vérifiez la syntaxe de référence


Parfait ! Revenons à l'article précédent : « Écriture d'un service API REST pour exporter les données patient générées au format .csv ».

Nous souhaitons ajouter une nouvelle fonction et mettre à jour la route de la classe `datagen.restservice`.

1. Ajoutez une nouvelle fonction `GenFHIRBundle`, avec un paramètre `isirisfhir` indiquant la syntaxe de référence souhaitée.

Si aucun paramètre n'est fourni, la fonction renverra la syntaxe de référence pour un référentiel FHIR autre qu'IRIS.

ClassMethod GenFHIRBundle() As %Status
{
    // get the parameter isirisfhir
    set isirisfhir=$GET(%request.Data("isirisfhir",1),"")
    // default value for non - IRIS repository
    if isirisfhir="" set isirisfhir=0
    // gen the FHIR bundle
    w ##class(datagen.utli.genfhirjson).genfhirbundle(isirisfhir)
    
    return $$$OK
}

2. Ensuite, nous ajoutons une route pour le service REST et nous compilons 😀

<Route Url="/gen/fhirbundle" Method="GET" Call="GenFHIRBundle"/>

La classe datagen.restservice mise à jour ressemblera à ceci :


Super ! C'est tout ce qu'il nous reste à faire ! 😁

Testons avec Postman !

Essayez d'effectuer une requête GET à l'URL suivante pour générer un bundle FHIR pour un dépôt FHIR autre qu'IRIS.

localhost/irishealth/csp/mpapp/gen/fhirbundle

 

Parfait !

Essayez maintenant d'effectuer une requête GET à l'URL suivante pour générer un bundle FHIR pour le dépôt IRIS FHIR.

localhost/irishealth/csp/mpapp/gen/fhirbundle?isirisfhir=1

Ça marche super bien !!! 😆😉

Merci beaucoup d'avoir lu. 😘

Discussion (0)1
Log in or sign up to continue
Announcement
· Nov 2

InterSystems Open Exchange Applications Digest, October 2025

Hello and welcome to the October 2025 Open Exchange Recap.
General Stats:
26 new apps in October
579 downloads in October
1,158 applications all time
43,414 downloads all time
3,410 developers joined
New Applications
quarkus-iris-monitor-system
By Davi Massaru Teixeira Muta
IRIStool and Data Manager
By Pietro Di Leo
yaml-adaptor
By Yuri Marx
DevHub
By Ashok Kumar T
golang-fiber-iris-realworld-example-app
By Dmitry Maslennikov
python-iris-audio-query
By Yu Han Eng
Interop-LookupTable
By Ashok Kumar T
IRIS ESB
By Andrew Sklyarov
Erralyzer
By Ashok Kumar T
sanitary-surveillance
By Vinícius Sena
gorm-iris
By Dmitry Maslennikov
irisconns
By Eric Fortenberry
java-iris-python-heart-diagnosis-system
By Joseph Ekpe
gj :: configExplorer
By John Murray
GBLSizeMonitor
By Ashok Kumar T
Reusable-Web-Interface-for-Data-Lookup-Table
By Sanjib Pandey
Free DBsize with Swagger
By Robert Cemper
TablePluse
By Ashok Kumar T
InterSystems IRIS GraphRAG
By Fan Ji
swaggertools-jsonviewer
By Ashok Kumar T
iris-docdb-ui
By Ashok Kumar T
inquisidor
By Luis Angel Pérez Ramos
FocusBoard
By Ashok Kumar T
iris-haos
By Ron Sweeney
ObjectScript-Native-API-demo
By Robert Cemper
Stream-Sample-for-Beginners
By Robert Cemper
New Releases
MDX2JSON by Eduard Lebedyuk
v3.2.49
FIX: when axis with "agg" type received a children object
SQL-for-ERRORS-Global by Robert Cemper
v1.0.9
fix iris.script to use zpm
Intersystems-Monitoring by Teunis Stolker
v1.0.25
  • Improved System Monitoring by sending explicit properties
  • Allow sending 500 i.s.o. 100 Events and Message Headers per request
  • No longer sending Info events
  • No longer sending AuditEvents for UnknownUser
  • Added headers for Monitoring Package version and IRIS version in each request
  • Now compatible with newer ZPM versions (IPM)
v1.0.26
EventLogService: Moved condition TraceCat <> 'user' into SQL query
IntegrityLog-Web by Ashok Kumar T
v1.0.2
Enhanced the user interface and implemented new features.
IRIS internal WebSocket Client by Robert Cemper
v1.2.0
  • Added SSL for WSS support
  • new Demo with available
    • "wss://echo.websocket.org/"
  • SSL requires manual installation in ZPM as this might cause serious conflicts
JsonTraceViewer by Guillaume Rongier
v1.2.0
Fix issue with new json format form IRIS
Support YAML too now.
Thanks to AshokThangavel for his PR.
Most downloaded
MDX2JSON
By Eduard Lebedyuk
ObjectScript-Math
By Peter Steiwer
DeepSeeWeb
By Anton Gnibeda
zpm-registry
By Evgeny Shvarov
yaml-utils
By Benjamin De Boe
WebTerminal
By Nikita Savchenko
Intersystems-Monitoring
By Teunis Stolker
Test Coverage Tool
By Timothy Leavitt
ssl-client
By Evgeny Shvarov
Embedded Git
By Pravin Barton
October, 2025Month at a GlanceInterSystems Open Exchange
Discussion (0)1
Log in or sign up to continue
Article
· Nov 2 7m read

Interoperability Naming Convention (IRIS Project Organization Guide)

Over time, while I was working with Interoperability on the IRIS Data Platform, I developed rules for organizing a project code into packages and classes. That is what is called a Naming Convention, usually. In this topic, I want to organize and share these rules. I hope it can be helpful for somebody.

 

Disclaimer: This guide is a good fit for Interoperability projects. When you have integrations, data flows, when you use IRIS for ETL processes, etc. Overall, when your code is combined with Productions. If you are using the IRIS Data Platform just as a backend, for example, the project structure can be different, of course.

General naming rules

Follow these naming rules always when it is possible:

  • For names of packages and classes, we use the UpperCamelCase
  • The package name must be in the singular form. Exception: when the names of packages are generated automatically (by importing WSDL or XSD, for example) 

          🚫 Incorrect: Messages     ✅ Correct: Message 

          🚫 Incorrect: Processes     ✅ Correct: Process

  • Abbreviation for the package name is possible. Sample: InterSystemsCorporation = ISC
  • Production item name must be the same as the class name. Exception: when we have a few Production items with different settings based on one class. In this case, I advise you to add .Type (some delimiter) at the end of the business host name. For example, we have a class  MyProject.Operation.API and two business hosts use this class: MyProject.Operation.API.Node01 and MyProject.Operation.API.Node02
  • Do not repeat the package name in the class name

          🚫 Incorrect: MyProject.Operation.EmailOperation     ✅ Correct: MyProject.Operation.EmailAlert

  • Only one domain in the full class name (see details below)

          🚫 Incorrect: ISC.Alert.Operation.EmailNotifier     ✅ Correct: ISC.Operation.EmailNotifier

Levels of project structure

Let's look at our subject from the upper level to the project depths. Generally, the structure of modules will look like: src > Domains > Class Types > Class Files. Or as a class full name: Domain.ClassType.ClassName.cls. Next, more details about each level.

0️⃣ Zero level (project root)

 

On this level, we have: 

  • .vscode directory with settings.json inside (created automatically by Visual Studio Code)
  • src folder - all your custom code must be here 
  • .gitignore file, my default looks like:
.vscode
fields.md
.DS_Store
  • Docker files, if you use Docker for deployment
  • Optionally: LICENSE, README.md, and also more deployment-related things 

1️⃣ Level one (domains)

All the following files and folders must be inside src. It's the level of domains, the biggest piece of your code separation. Any complex project on IRIS has many integrated applications and sub-projects combined in a single namespace (within a single Production). At this level can be located only folders (packages) called by companies, projects, sub-modules, or connected systems. It can be folders like: ERP, Kafka, Alert, FHIR, SampleProjectISC. No classes allowed here.

The choice of domain delimiter at the top level is up to you. It depends on your specific needs. A good idea is to use the company's name if you're creating custom code in a typical InterSystems solution. Or use the connected application name if it's an integration project, especially if you have many integrated applications.

In the screenshot below, you can see how sub-module names are used as a level one delimiter. This delimiter was selected because its independent modules, Alert (alerting sub-system) and Broker (message broker), can be used separately. Of course, here could be used a structure like ESB > Alert > ... and ESB > Broker > ... with a common package ESB. But it contradicts a key principle of this convention: a single domain level. No nested domains allowed.

Also, here is the place for the package with the name Production. In this package must be located your production class uses a fixed name - Main.cls. We are using a folder on the first level, because Production is a common item for all projects, modules, and all what we have in the namespace.

 

You can add more production classes to this package if you are using environment settings separation by production classes (check this discussion), such as Dev.cls, Test.cls, and so on.

2️⃣ Level two (packages by class types)

This is the level of separation by class types. No classes allowed here. This inner folder list will be almost the same for each domain/project folder. Of course, it will depend on your task-specific needs, and you can add suitable only for you packages to this level (don't forget to follow general naming rules). I prepared a list of typical package names that I use at this level.

A few regular packages, common to all projects:

  • 📦 Adapter - package for adapter classes. I am not splitting it into inbound and outbound adapters because, usually, projects do not contain a lot of adapters
  • 📦 Service - your classes, which extend Ens.BusinessService. No folders allowed here, just a list of classes
  • 📦 Message - a package for messages, typically children of Ens.Request and Ens.Response. There can be many nesting levels here. Usually, I use the following structure: Message > Application > Type > Root.cls. Good practice to create message-related classes by XSD import
  • 📦 Process - classes which extend Ens.BusinessProcess. No folders allowed inside
  • 📦 Operation - classes which extend Ens.BusinessOperation. No folders allowed inside
  • 📦 Storage - a place for storing persistent data. It is the classes that extend the %Persistent type. If you need to store some data, you can create your tables here. No folders allowed inside
  • 📦 Transform - transformation classes (extend Ens.DataTransform). All format or protocol-related transformations, also called mappings, must be stored here (Company.Transform.SomeJson2SomeXML.cls for example). No folders allowed inside
  • 📦 UnitTest - list of test cases (classes that extend  %UnitTest.TestCase)

And more project-related packages:

  • 📦 WebClient - SOAP clients folder. You must import each client into a new folder, like WebClient > MyCustomClient. Can be a few nested folders automatically created by WSDL import here
  • 📦 API - if you publish REST APIs on the IRIS side. Also, possible nested folders. Sample: API > MyCustomREST - inside this folder, your disp, impl, and spec classes will be located. These classes are generated when we use a specification-first way for API creation. More details about it can be found here
  • 📦 Task - if you use task classes (extend %SYS.Task.Definition). No folders allowed inside
  • 📦 CSP - if you use Cache Server Pages for creating some UI

3️⃣ Level three (single classes)

Level of single classes (.cls files). As explained above, most of your class files must be located on the third level. Try to avoid creating folders here. Actually, this convention does not create a deeply nested structure. And it is true with a few exceptions, like 📦 Message, 📦 WebClient, and 📦 API folders. Mostly because in these cases, we use code generation for creating a folder structure.

 

Benefits

  • Easy to find related classes when you have only a generic problem understanding. For example, the issue sounds like "We have a problem with putting data into Kafka". You can be sure that related classes are located in the package Kafka.Operation.*, because we talk about a specific application (Kafka), and the outbound data flow direction (Operation)
  • You can create any automated processing for business hosts based only on their names. The expression $p(item.ClassName, ".", 2) is always equal to a type of class - Service, Process, Operation, etc.
  • The convention about business host names in Production allows us to quickly find related classes and effectively manage huge Interoperability Productions
  • It's a simple concept for new developers. A similar structure is used in some system packages

Conclusion

Generally, that is all that I want to tell you about my Project Organization Guide. Of course, in addition to the project structure rules, I also have a Code Style Guide with typical patterns and many other details. But let it go next time. A detailed sample of using this convention can be found in the IRIS ESB project. It would be great if someone else could share: what naming convention are you using for packages and class organization?

4 Comments
Discussion (4)3
Log in or sign up to continue