InterSystems IRIS Document Database (DocDB) offers a flexible and dynamic approach to managing database data. DocDB embraces the power of JSON (JavaScript Object Notation), providing a schema-less environment for storing and retrieving data.
It is a powerful tool, enables developers to bypass a ton of boiler plate code in interaction with existing applications, serialization, pagination and integration. the seamless flow of DocDB with Interoperability Rest services and operations, gives a big leap in API production and management.
for full DocDB documentation Here. in the context of this article i will showcase a use case in which DocDB will make a perfect fit.
Organizations using InterSystems Products like IRIS, Interoperability, TrakCare,… constantly generates agile and new requirements for sharing data through to other systems to consume. while InterSystems offers Built in JSON Adaptor to enable persistent classes to be JSON Serializable on record level, However neither rewriting your application to extent JSON Adaptor is an easy task nor your data of interest is going to be managed always on record level directly from your application classes. here comes DocDB to the picture, because you can build queries over your application code and dump it into DocDB as JSON, ready to ship to consumer apps.
here is a snippet running any standard IRIS stored procedure in a generic way and saving output as records in json format.
set queryData=$lb("queryclassname:queryname",<no of parameters>) set queryString=$lg(queryData,1) set paramCount=$lg(queryData,2) set args=$lb() for ix=1:1:paramCount{ set var="P"_ix XECUTE ("(in,out) SET out=$g(in)", @var, .y) set $li(args,ix)=$s(y'="":""""_y_"""",1:""""_"""") } set resultSet=##class(%ResultSet).%New(queryString) XECUTE ("(in,out) set out=in.%Execute("_$lts(args)_")",resultSet,.scx) set sc=scx If $$$ISERR(sc) $$$ThrowStatus(sc) set columnNo=resultSet.GetColumnCount() While resultSet.%Next(.sc) { If $$$ISERR(sc) $$$ThrowStatus(sc) set row={} for iz=1:1:columnNo{ set columnName=resultSet.GetColumnName(iz) do row.%Set(columnName,resultSet.GetDataByName(columnName)) } set sc=..AddRecordToReportResult(ID,row) If $$$ISERR(sc) $$$ThrowStatus(sc) }
ClassMethod AddRecordToReportResult(doc As %DynamicObject)As %Status { set sc=$$$OK try { if (##class(%DocDB.Database).xNExists("PreparedReportDB")){ set db=##class(%DocDB.Database).%GetDatabase("PreparedReportDB") } else { set db= ##class(%DocDB.Database).%CreateDatabase("PreparedReportDB") } set nwRecord=db.%SaveDocument(doc) do nwRecord.%Save(0) k nwRecord }catch err{ set sc=$$$ADDSC(sc,$$$ERROR("5001",err.DisplayString())) } return sc }
that is it, your data -whatever it is- is ready to be exposed to consuming apps.
lets take the use case of monitoring integrations, your queries will check the status of your InterSystems interoperability production, the errors, alerts,..
and the data will be saved in a DocDB on intervals.
Class ProdEye.DataTask Extends %SYS.Task.Definition
{ Property IntervalMinutes As %Integer [ InitialExpression = 5 ]
Method OnTask() As %Status
{ try { set packageName=$P($classname(),".",1) set targetDocument={} set ZeroTimeStamp= $$HorologAddSecs^EnsUtil($ztimestamp,-(..IntervalMinutes*60)) set targetDocument.TimeOfQuery=##class(Ens.DataType.UTC).timeUTC() set targetDocument.TimeOfQueryIdx=$classmethod(packageName_".Query","getDateTimeUTCIdx") do $classmethod(packageName_".Query","GetNameSpaceList") k prodArray do $classmethod(packageName_".Query","GetProductionsList",.prodArray) set productionObjectList=[] set currNameSpace="" for { set currNameSpace=$O(prodArray(currNameSpace)) quit:currNameSpace="" set currProd="" for { set currProd=$O(prodArray(currNameSpace,currProd)) quit:currProd="" set newProdData=$g(prodArray(currNameSpace,currProd)) set newProd=$classmethod(packageName_".Data.Production","%New",currProd,$lg(newProdData,1),$lg(newProdData,2)) k compArray do $classmethod(packageName_".Query","GetComponentListByProd",.compArray,currProd,currNameSpace) set components="" set currComp="" for { set currComp=$O(compArray(currProd,currComp)) quit:currComp="" set newComp=$classmethod(packageName_".Data.ProductionConfigItem","%New",currComp,$g(compArray(currProd,currComp))) set compId=$classmethod(packageName_".Query","GetComponentId",currComp,currProd,currNameSpace) set newComp.Type= $classmethod(packageName_".Query","GetComponentType",compId,currNameSpace) set newComp.QueueSize=$classmethod(packageName_".Query","GetComponentQueueData",currComp,currNameSpace) set MessageStats=$classmethod(packageName_".Query","GetComponentMessageCountAndAvg",currComp,ZeroTimeStamp,currNameSpace) set:$lv(MessageStats) newComp.MessageCount=$lg(MessageStats,1) set:$lv(MessageStats) newComp.MessageAVGProcessingMilliseconds=$lg(MessageStats,2) set newComp.JobsStatus=$classmethod(packageName_".Data.JobStatus","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentJobs",currComp,currNameSpace)) set newComp.Errors=$classmethod(packageName_".Data.Log","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentErrors",currComp,ZeroTimeStamp,currNameSpace)) set newComp.Warnings=$classmethod(packageName_".Data.Log","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentWarnings",currComp,ZeroTimeStamp,currNameSpace)) set newComp.Alerts=$classmethod(packageName_".Data.Log","BuildPropertyFromList",$classmethod(packageName_".Query","GetComponentAlerts",currComp,ZeroTimeStamp,currNameSpace)) do newProd.Components.Insert(newComp) } set newProdJSONStr="" do newProd.%JSONExportToString(.newProdJSONStr) set newProdJSON={}.%FromJSON(newProdJSONStr) do:newProdJSON'="" productionObjectList.%Push(newProdJSON) } } do targetDocument.%Set("ProductionList",productionObjectList) if (##class(%DocDB.Database).xNExists(packageName_".DB.ProdEyeDocument")){ set db=##class(%DocDB.Database).%GetDatabase(packageName_".DB.ProdEyeDocument") } else { set db= ##class(%DocDB.Database).%CreateDatabase("ProdEye.DB.ProdEyeDocument") do db.%CreateProperty("TimeOfQueryIdx","%Integer","$.TimeOFQueryIdx",0) } set nwRecord=db.%SaveDocument(targetDocument) if $$$ISERR(nwRecord){$$$ThrowStatus(nwRecord)} set nwRecord.ProfileName=$get(^ProdEyeProfileName(0)) set nwRecord.TimeOfQueryIdx=targetDocument.TimeOfQueryIdx do nwRecord.%Save() return $$$OK } catch error { do BACK^%ETN return $$$ERROR("5001",error.AsStatus()) }
} }
now any external consuming app can fetch the data to check productions status, alerts, errors and warnings using a simple InterSystems REST service as the following
Include Ensemble Class ProdEye.Prod.ProdEyeRest Extends EnsLib.REST.Service
{ Property PageSize As %Integer [ InitialExpression = 10 ]
Property TokenValidationURL As %String
Parameter SETTINGS = "PageSize,TokenValidationURL"
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes> <Route Url="/getdata" Method="POST" Call="GetData"/> </Routes>
} Method GetData(pInput As %Library.AbstractStream, Output poutput As %Stream) As %Status
{ quit:'$IsObject(pInput) $$$OK quit:(pInput.SizeGet()=0) $$$OK set paramsObj={}.%FromJSON(pInput.Read()) set cutoff=paramsObj.cutoff set authorisation=paramsObj.Authorisation set profilename=paramsObj.profilename $$$TRACE(..Adapter.IOAddr_" Profile "_profilename_" cutoff parameter: "_paramsObj.%ToJSON()) if ('..Authorise(authorisation,..TokenValidationURL)){ do poutput.Write("{""Error"":""Invalid Credentials""}") do ..ReportHttpStatusCode(403) return $$$OK } if ('..ValidateCutoff(cutoff)){ do poutput.Write("{""Error"":""Invalid Request Parameters""}") do ..ReportHttpStatusCode(400) return $$$OK } try{ set packageName=$P($classname(),".",1) SET db = ##class(%DocDB.Database).%GetDatabase(packageName_".DB.ProdEyeDocument") set restriction=[] do restriction.%Push("TimeOfQueryIdx") do restriction.%Push(cutoff) do restriction.%Push(">") do restriction.%Push("ProfileName") do restriction.%Push(profilename) do restriction.%Push("=") SET result = db.%FindDocuments(restriction) if (($IsObject(result))&&($Isobject(result.content))){ set resultPage={} set resultPage.Next=0 set resultList=[] set iter=result.content.%GetIterator() for dx=1:1:..PageSize{ set resultItem=iter.%GetNext(.key,.resultItemvalue) if $IsObject(resultItemvalue){ set resultItemvalue={}.%FromJSON(resultItemvalue.%Doc) do resultList.%Push(resultItemvalue) set:dx>1 resultPage.Next=resultItemvalue.TimeOfQueryIdx } } set resultPage.content=resultList do poutput.Write(resultPage.%ToJSON()) }else { Throw $$$ERROR("5001","Error During Processing Data") } } catch error { do poutput.Write("{""Error"":""Error During Processing Data : """_error.AsSystemError()_""" ""}") do ..ReportHttpStatusCode(500) } return $$$OK
} ClassMethod ValidateCutoff(val As %String) As %Boolean
{ set sc=1 quit:+val<=0 0 quit:$l(+val)<5 0 set valdt=$E(val,1,5)_","_$E(val,6,10) try { set date=$zdt(valdt) }catch err{ set sc=0 } return sc
} ClassMethod Authorise(val As %String, TokenValidationURL As %String) As %Boolean
{ quit:$g(val)="" 0 if (TokenValidationURL=""){ set type=$P(val," ",1) set type=$zstrip($zstrip(type,"*c"),"<=>w") quit:$ZCVT(type,"L")'="basic" 0 set userpass=$P(val," ",2) set userpassDecoded=$system.Encryption.Base64Decode(userpass) set user=$P(userpassDecoded,":",1) set user=$zstrip($zstrip(user,"*c"),"<=>w") set pass=$P(userpassDecoded,":",2) set pass=$zstrip($zstrip(pass,"*c"),"<=>w") set prodeyeUser=##class(Ens.Config.Credentials).GetValue("ProdEyeUser","Username") set prodeyePass=##class(Ens.Config.Credentials).GetValue("ProdEyeUser","Password") return ((prodeyeUser=user)&&(prodeyePass=pass)) }elseif(TokenValidationURL'=""){ set prodeyeAuthUser=##class(Ens.Config.Credentials).GetValue("ProdEyeAuthUser","Username") set prodeyeAuthPass=##class(Ens.Config.Credentials).GetValue("ProdEyeAuthUser","Password") set tokenVerificationRequest=##class(%Net.HttpRequest).%New() do tokenVerificationRequest.SetHeader("content-type","application/json") do tokenVerificationRequest.SetHeader("Authorisation","BASIC "_$system.Encryption.Base64Encode(prodeyeAuthUser_":"_prodeyeAuthPass)) set token=$P(val," ",2) set data={} set data.Token=token do tokenVerificationRequest.EntityBody.Write(data.%ToJSON()) do tokenVerificationRequest.Post(TokenValidationURL) set response=tokenVerificationRequest.HttpResponse if ($IsObject(response) && $IsObject(response.Data) && response.StatusCode=200 ){ set message="" while 'response.Data.AtEnd {set message=message_response.Data.Read(,.tSC) if 'tSC quit} set responseMessage={}.%FromJSON(message) if (responseMessage.%IsDefined("IsValid") && (responseMessage.IsValid="1")){ return 1 }else{ return 0 } }else{ return 0 } }
} }
an actual example of a consuming app of the above use case is shared here on open exchange. it is a mobile app developed in dart for Flutter framework.
in addition to that, DocDB exposes direct API over all Document Tables , described in detail Here
The Edge that DocDB functionality offers is making API development for information sharing across systems driven by the use case focus while fast tracking and standardizing all the data serialization, staging and integration.