Call a Service Dynamically from flow service

Hi,
I have a table with two columns which has the value and the Corresponding Service that needs to be called when the variable value that I check equals the one in Column1.
Column1 Column2
ABC Packagename.servicename1
DEF Packagename.servicename2
I am writing a flow service where I check if variable v = ABC then it should invoke Packagename.servicename1 stored in the Column2. if Variable v = DEF then it should invoke Packagename.servicename2.
Can you please let me know if this can be done? Or if there is other better approach to do this? I do not want to hard code each value in Column1 as a sequence and invoke the corresponding service but would want to do this dynamically.

It is doable, however adding package and service information to db is a dependency. Its not that different then hardcoding them. Who will manage the integrity of this data? If its not managed by anyone, hardcoding it with a global variable would make more sense to me and its also a dependency.

Are these services hosted in the same integration server? If they are, instead of breaking that dependency using them as they are (regular service invoke, like we drag and drop) and branching them according to a meaningful data will be better. This will not break dependency chain, hence if you make changes to any linked services dependencies will be preserved.

If these services are not hosted in the same integration server and the service signature is not manually inserted to anywhere (like querying them from a service registry or from a vault) hence managed by the owner of the services you can just query that db/vault/registry and cache the results. You can link that string to http.client service like any other string.

2 Likes

@mm4wm Even though you would like to call your service dynamically . Your service name which you like to refer is static .

And for Static values its good to store it in key Pair values in Global variables and In terms of key:value Maintainability I would suggest to used file based approach to load value and store it in cache for greater performance

2 Likes

Hi @mm4wm,

Other folks on the thread seem to have tackled the “why” already so I’ll leave that alone for now and assume you have a good reason to do this. So to answer your question: yes, you can do this. Take a look at the service WmPublic/pub.flow:invokeService.

Hope this helps,
Percio

1 Like

We do this occasionally. We use config files (XML formatted, but could be JSON or properties too) instead of a DB table.

The assumption is that each service that would be invoked has the same signature – same inputs and outputs. You could define a Specification object to help guide that.

First thing you likely want: a helper service to use com.wm.app.b2b.server.Service.doInvoke(). You may already have one. If not, here is one you may find helpful:

EDIT: I forgot about pub.flow:invokeService. Old habits I guess. I don’t recall when that became available but we’ve used our own for so long, I forgot that the built-in service exists. :slight_smile:

	public static final void invokeServiceEx(IData pipeline) throws ServiceException {
		IDataCursor idc = pipeline.getCursor();
		String folder = IDataUtil.getString(idc, "folder");
		String service = IDataUtil.getString(idc, "service");
		boolean thread = IDataUtil.getBoolean(idc, "thread");
		
		com.wm.lang.ns.NSName svc;
		if( ((folder==null) || (folder.length() == 0)) && (service.length() > 0) )
		    svc = com.wm.lang.ns.NSName.create(service); 
		else
		    svc = com.wm.lang.ns.NSName.create(folder,service); 
		
		try
		{
		    //Invoke the service
		    if(thread)
		    {
		        java.util.Date dt = new java.util.Date();
		        java.util.Random z = new java.util.Random();
		        String name = String.valueOf(z.nextInt());
		
		        z.setSeed(dt.getTime());
		        Service.doThreadInvoke(svc, new com.wm.app.b2b.server.Session(name), IDataUtil.clone(pipeline) );
		    }		
		    else
		    {
		        Service.doInvoke(svc, pipeline);
		    }
		}
		catch(Exception ex)
		{
		    throw new ServiceException(ex);
		}
		finally
		{
		    idc.destroy();
		}
	}

With the above, then we create a helper service that exposes the same inputs/outputs as the dynamically called services. The job of this service is to accept the name of the service to invoke along with inputs, call the service using the invokeServiceEx above, then return the results. We usually use scoping to limit the pipeline.

For example, say you have several “map” services that translate a document to another. You want to call any of them. Say they take an input of “sourceDocument” and return an output of “targetDocument”. The helper would be:

Service: invokeMapService
Inputs: sourceDocument, service
Outputs: targetDocument
Body:
…MAP – map the 2 inputs to a temp document named “__invokeScope”
…call inovokeServiceEx – set Scope to __invokeScope; this limits the pipeline for the invoked map service
…MAP – map the output from __invokeScope to the output var of the helper service

Then in your main service where you’re determining which service to invoke based upon the DB look up, call this “invokeMapService”. It will get the outputs just as if it called the service directly.

Other side notes – be cautious if you decide to use global vars. Without a naming scheme and management plan, it can get out of control. I would also caution about using caching. IME, it has never been necessary/helpful and only introduces complexity. If performance measurements indicate more speed is needed, and using cache shows it provides the necessary speed increase, then by all means use it. But do not make assumptions about it being faster – my experience has been it has zero meaningful impact. But definitely a YMMV aspect.

HTH.

1 Like

This service is only capable of invoking IS services. It can also be done by using pub.client:http or pub.client:soapClient or any other similar invoking service. That’s why I didn’t involve how, it can be miss leading imo.

“Better” always requires a context. I can say that in some scenarios, dynamic invocation based upon externalized settings (config file, DB table) can preferable over changing a BRANCH in a FLOW service and deploying that. It depends on various factors, including the administrivia around code deployments vs. DB changes vs. config file changes. If it is as much effort to get a config file change approved and applied as it is to do a code deployment, then perhaps externalizing settings like this offers no real advantage.

That said, as you note dynamic invocation does destroy dependency checking and can lead to mishaps later. Thus, as with most things, it is a trade-off. Which set of pain do you want to live with? :slight_smile:

1 Like

A fair point. I did not interpret the OPs intent as to be trying to call any arbitrary component anywhere. Only to call an IS-hosted service, on the same instance, determined at run-time.

I guess we can wait for @mm4wm to clarify if needed.

1 Like

There is a very big if at the beginning of that sentence :slight_smile:. It doesn’t matter though since it is marked as solved now.

1 Like

The original post states:

Hence the recommendation.

Percio

Thank you for the solution! Yes, these services are hosted on the same integration server. And the Package.ServicenameX that I mentioned in the column B is a Put service that connects to the MQ connection and puts a message in there. The Input parameter is the message that will be put on the MQ. Based on the value in Column A, the corresponding service which puts the message in queue should be invoked.

Thank you, I will look at this.

Thank you, I will look into this approach.

Hi, that is correct, the service is hosted in the same instance.

Just curious, why do you need a db dependency then? You can also drag and drop the service and branch them according to what you have in your service. Imo its always a good idea to keep things as simple as possible.

1 Like

Yes, thank you for the suggestion, that’s how I did to branch and then drag drop the service accordingly but was just trying to see if this can be done from not necessarily DB but may be a file which has the key value pairs like someone suggested so that in the future if we like to add one more entry, it can be controlled through the change in file/DB entry which would be simple rather than change the code again.

Instead of using a db, you can use a cache. It will be faster and you can add any key value pair anytime you want.

Using universal name can also be a good idea if you know what kind of data you can have in the future. For example if you are branching by using a data in the document, then you can set that value as universal name and call the service using that data. Its simple and it keeps everything tidy. Check this document for details.This is one of the valid use cases using WmPublic/pub.flow:invokeService.

I recall pub,remote:invoke can invoke services remotely or locally (just setup an IS remote alias to itself)

https://documentation.softwareag.com/webmethods/microservices_container/msc10-5/10-5_MSC_PIE_webhelp/index.html#page/pie-webhelp/pub_remote_invoke.html

It is, but you can’t call services from other end points like from an API written in .net core. It has to be a webMethods Integration Server.

Yes.

Dynamically invoking other services from a Flow service is useful in many situations. For example, we added a custom field to Trading Networks profiles. There, we can set the name of an arbitrary package that does special processing. At runtime, if a package name is set, our code dynamically calls <PACKAGE_NAME>.processing:service. That way we flexibly change processing behaviour without updating Flow or editing a config file on disk.