Dev Tip: Understanding OutSystems Service Actions Under the Hood

In modern application development, service-oriented architecture (SOA) has become a key approach for designing scalable and maintainable systems. OutSystems supports this architecture by allowing developers to build Service Modules and Service Applications, which encapsulate core functionalities and expose them for reuse. Among the various methods to expose services, Service Actions play a crucial role, especially when scalability and modularity are priorities. This blog delves into the inner workings of Service Actions, illustrating how they operate under the hood.

Service Modules and Service Applications

Before diving into Service Actions, it’s essential to understand the foundation they rest upon. In OutSystems, Service Modules are designed to enforce separation of concerns by encapsulating core service functionalities, such as service logic, integrations (e.g., REST), processes, timers, and database entities. These modules are free from UI-related elements, ensuring they remain focused solely on the backend service logic.

As your application portfolio grows, organizing these Service Modules into Service Applications can help manage complexity. These applications group related modules, streamlining the architecture and making it easier to manage dependencies and updates.

Exposing Functionality with Service Actions

When exposing services within the same OutSystems environment, developers can choose between Server Actions and Service Actions. While Server Actions allow for tightly-coupled modules, Service Actions offer a more loosely-coupled approach, providing greater flexibility and independence between modules.

How Service Actions Work

Under the hood, a Service Action is essentially a REST-based remote call that runs in a separate process from the consumer module. Despite the remote nature of the call, the usage pattern for Service Actions is quite similar to that of Server Actions, making it easy for developers to transition between the two.

Sample Generated Service Action Code from OutSystems

To better illustrate how Service Actions function, consider the following sample code for a Service Action:

				
					[Route("serviceapi/serviceaction1")]
[HttpPost]
[OnResponseFilter(null)]
[OnRequestFilter(null, "Pbtcu6cOf06jgYOes0kPKA", "ServiceAction1")]
[RESTServiceAPIMethodProperties(Name = "ServiceAction1", IsRequestBinary = false, IsResponseBinary = false)]
public IHttpActionResult ServiceAPIServiceAction1()
{
 AppInfo appInfo = null;
 HeContext heContext = null;
 bool commit = false;
 SecurityTokenAPI.RequestSecurityTokenPayload requestSecurityTokenPayload = null;
 try
 {
  appInfo = AppInfo.GetAppInfo();
  heContext = appInfo.OsContext;
  heContext.IsServiceAction = true;
  Debugger.SetRequestName("Request '" + HttpContext.Current.Request.Url.AbsolutePath + "'");
  ((CoreServicesApiController)this).CheckApplicationEnabled(appInfo);
  string result = ((ApiController)this).get_Request().Content.ReadAsStringAsync().Result;
  if (!((CoreServicesApiController)this).IsRequestValid(result, "1b778424-1c99-4f95-ab28-ff70b11e512f", ref requestSecurityTokenPayload))
  {
   SecurityException ex = new SecurityException("Invalid request");
   ((CoreServicesApiController)this).LogApplicationError((Exception)ex, heContext);
   return ((CoreServicesApiController)this).GetExceptionResult((Exception)ex);
  }
  ((CoreServicesApiController)this).SetContextInfo(heContext, requestSecurityTokenPayload);
  ((CoreServicesApiController)this).TraceSerializerSettings(base.BehaviorsConfiguration.get_SerializerSettings());
  RequestPayload<S4PIServiceAction1Input> val = JsonConvert.DeserializeObject<RequestPayload<S4PIServiceAction1Input>>(result, base.BehaviorsConfiguration.get_SerializerSettings());
  RequestTracer requestTracer = RuntimePlatformUtils.GetRequestTracer();
  if (requestTracer != null)
  {
   requestTracer.MainEventType = RequestTracerEventType.ExposedServiceActionExecuted;
   requestTracer.RegisterBeginRequest(DateTime.Now);
  }
  ((CoreServicesApiController)this).TrySetOriginalRequestKey(val.RequestKey, heContext);
  _ = val.InputParameters;
  heContext?.RequestCancelation.ThrowIfCanceled();
  Flows.ActionServiceAction1(heContext, out RLUserList outParamOut);
  commit = true;
  return ((CoreServicesApiController)this).GetSuccessResult((object)new S4PIServiceAction1Output(RLUserList.ToArray(outParamOut, (ENUserEntityRecord str) => JSONENUserEntityRecord.FromStructure(str, base.BehaviorsConfiguration))), requestSecurityTokenPayload);
 }
 catch (Exception ex2)
 {
  ((CoreServicesApiController)this).LogApplicationError(ex2, heContext);
  return ((CoreServicesApiController)this).GetExceptionResult(ex2, requestSecurityTokenPayload);
 }
 finally
 {
  DatabaseAccess.FreeupResources(commit);
  Debugger.PopAll();
  heContext.IsServiceAction = false;
 }
}c
				
			

This code snippet highlights several key aspects of Service Actions:

Annotations

  • Defines the route URL for the service action. The method will handle requests sent to serviceapi/serviceaction1.
  • Specifies that this method handles HTTP POST requests.

 

Security and Validation: The request is validated to ensure it is legitimate before proceeding with the logic.

Context Management: The heContext object is used to maintain the execution context throughout the Service Action.

Session Information Passing: When a Service Action is called, OutSystems automatically passes essential session information, including UserId, TenantId, Locale, and Request Key. This ensures that the Service Action can access the necessary context, such as the current user or tenant, and maintain consistency across logs.

Security: Service Actions inherit the authentication context from the consumer session, ensuring that only authenticated requests are processed.

Deserialization: The request payload is deserialized into an object using JSON.

Exception Handling and Governance: Exceptions thrown within a Service Action, whether user-defined or communication-related, can be caught and managed by the consumer modules.

 

When to Use Service Actions

Deciding whether to use Server Actions or Service Actions depends on the specific requirements of your application. For smaller applications or those with tightly-coupled release cycles, Server Actions might suffice due to their simplicity and lower development effort. However, as your application grows and you need to support independent release cycles across multiple modules, Service Actions become more advantageous despite the increased complexity.

Service Actions in OutSystems provide a powerful mechanism to expose functionality in a loosely-coupled manner, allowing for independent deployment and scalability. Understanding how they work under the hood, from session management to fault tolerance, equips developers with the knowledge to make informed architectural decisions. As your application evolves, Service Actions can play a critical role in ensuring that your system remains modular, maintainable, and ready to adapt to changing business needs.

 

Author:
John Salamat
Senior Tech Lead | OutSystems MVP

This article was originally published on LinkedIn

A selection from our recent work