Azure failed to marshal transaction into propagation token for elastic transaction (Works for MSDTC) - azure

In windows azure we have hosted two asp.net webapi project as app service. We need to enable distributed transaction here. We initiate transaction inside one api. Then inside that transaction scope we fetch propagation token of that transaction and send it as a header during another api call. The code is something like bellow.
[HttpGet]
[Route("api/Test/Transaction/Commit")]
public async Task<string> Commit()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted
},
TransactionScopeAsyncFlowOption.Enabled))
{
// cross app domain call
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Get, ConfigurationManager.AppSettings["IdentityServerUri"] + "api/Test/Transaction/NoCommit"))
{
// forward transaction token
request.AddTransactionPropagationToken();
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
}
}
this.Repository.Insert(new Currency { Ccy = "x", IsoCode = "XIS", Name = "XYZ", CurrencyId = 9 });
await this.Repository.SaveChangesAsync();
scope.Complete();
return "value";
}
}
public static class HttpRequestMessageExtension
{
public static void AddTransactionPropagationToken(this HttpRequestMessage request)
{
if (Transaction.Current != null)
{
var token = TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
request.Headers.Add("TransactionToken", Convert.ToBase64String(token));
}
}
}
Inside the api(...api/Test/Transaction/NoCommit) to which we are making the call inside transaction scope, fetch that marshaled propagation token of the transaction from header and using it create instance of that transaction and instantiate TransactionScope using that transaction. Later we use this transaction scope to complete that transaction. We have introduced a action filter to apply this and added that filter to the action which is responsible for that api call. Code for that api and action filter is something like bellow.
[HttpGet]
[EnlistToDistributedTransactionActionFilter]
[Route("api/Test/Transaction/NoCommit")]
public async Task<string> NoCommit()
{
this.Repository.Insert(new Client
{
Name = "Test",
AllowedOrigin = "*",
Active = true,
ClientGuid = Guid.NewGuid(),
RefreshTokenLifeTime = 0,
ApplicationType = ApplicationTypes.JavaScript,
Secret = "ffff",
Id = "Test"
}
);
await this.Repository.SaveChangesAsync();
return "value";
}
public class EnlistToDistributedTransactionActionFilter : ActionFilterAttribute
{
private const string TransactionId = "TransactionToken";
/// <summary>
/// Retrieve a transaction propagation token, create a transaction scope and promote the current transaction to a distributed transaction.
/// </summary>
/// <param name="actionContext">The action context.</param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Contains(TransactionId))
{
var values = actionContext.Request.Headers.GetValues(TransactionId);
if (values != null && values.Any())
{
byte[] transactionToken = Convert.FromBase64String(values.FirstOrDefault());
var transaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(transactionToken);
var transactionScope = new TransactionScope(transaction, TransactionScopeAsyncFlowOption.Enabled);
actionContext.Request.Properties.Add(TransactionId, transactionScope);
}
}
}
/// <summary>
/// Rollback or commit transaction.
/// </summary>
/// <param name="actionExecutedContext">The action executed context.</param>
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Request.Properties.Keys.Contains(TransactionId))
{
var transactionScope = actionExecutedContext.Request.Properties[TransactionId] as TransactionScope;
if (transactionScope != null)
{
if (actionExecutedContext.Exception != null)
{
Transaction.Current.Rollback();
}
else
{
transactionScope.Complete();
}
transactionScope.Dispose();
actionExecutedContext.Request.Properties[TransactionId] = null;
}
}
}
}
So if any exception occurs during this call (api/Test/Transaction/Commit) inside that transaction scope (either in firt api or second api) all the database change done by the both api will be rolled back. This is working fine locally. As locally we get support of MSDTC. But in Azure there is no MSDTC support. In azure we get support from Elastic transaction. Because of this when we are trying to fetch propagation token of the transaction from the first server we are getting exception. So when we try to execute bellow code
var transaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(transactionToken);
We are getting exception with message "Value does not fall within the expected range". This post saying that this method would require promotion to MSDTC by System.Transactions, but for elastic transaction how we will make it work? For elastic transaction we need to marshal transaction into propagation token. How to do this? Looking for the solution.

Elastic Transactions are designed to allow transactions across Azure SQL Database and Azure SQL Managed Instance from a single .net application in Azure.
It is not built for distributing transactions across clients.
"Only client-coordinated transactions from a .NET application are supported"
https://learn.microsoft.com/en-us/azure/azure-sql/database/elastic-transactions-overview

Related

How to add customDimensions and set operation_parentId for Azure function log

I created a http trigger V1 azure function on net framework 4.8, and used ILogger for logging. The code is like this.
I checked the Application Insight and queried for traces table. This table contains columns named customDimensions and operation_ParentId. May I ask is there anyway to add custom property in customDimensions column, or set a new Guid value for operation_ParentId? I know that I can use TelemetryClient sdk to create a custom telemetry client for logging. Just curious if there is any easy way which doesn't need to create a new telemetry client, because azure function offers bulit-in integration with application insight.
Also, since azure function runtimes automatically tracks requests, is there any way to change the operation_ParentId and customDimensions for requests table as well? Thanks a lot!
To get both the headers and App Insights to get the custom operation Id, two things must be overridden.
The first is an Activity that wraps the HttpClient, which is responsible for controlling the correlation headers and the other is App Insights' dependency tracing.
Although you can disable Actions completely in your HttpClients, you can just remove the one in the client by setting Activity.Current = null to limit side effects.
var operationId = "CR" + Guid.NewGuid().ToString();
var url = "https://www.microsoft.com";
using (var client = new HttpClient())
{
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Get, url))
{
//Makes the headers configurable
Activity.Current = null;
//set correlation header manually
requestMessage.Headers.Add("Request-Id", operationId);
await client.SendAsync(requestMessage);
}
}
The next step is to remove the App Insights default tracking for this request. Again, you can disable dependency tracking completely, or you can filter out the default telemetry for this request. Processors are registered inside the Startup class just like initializers.
services.AddApplicationInsightsTelemetryProcessor<CustomFilter>();
public class CustomFilter : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
// next will point to the next TelemetryProcessor in the chain.
public CustomFilter(ITelemetryProcessor next)
{
this.Next = next;
}
public void Process(ITelemetry item)
{
// To filter out an item, return without calling the next processor.
if (!OKtoSend(item)) { return; }
this.Next.Process(item);
}
// Example: replace with your own criteria.
private bool OKtoSend(ITelemetry item)
{
var dependency = item as DependencyTelemetry;
if (dependency == null) return true;
if (dependency.Type == "Http"
&& dependency.Data.Contains("microsoft.com")
//This key is just there to help identify the custom tracking
&& !dependency.Context.GlobalProperties.ContainsKey("keep"))
{
return false;
}
return true;
}
}
Finally, you must inject a telemetry client and call TelemetryClient.TrackDependency() in the method that makes the remote call.
var operationId = "CR" + Guid.NewGuid().ToString();
//setup telemetry client
telemetry.Context.Operation.Id = operationId;
if (!telemetry.Context.GlobalProperties.ContainsKey("keep"))
{
telemetry.Context.GlobalProperties.Add("keep", "true");
}
var startTime = DateTime.UtcNow;
var timer = System.Diagnostics.Stopwatch.StartNew();
//continue setting up context if needed
var url = "https:microsoft.com";
using (var client = new HttpClient())
{
//Makes the headers configurable
Activity.Current = null;
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Get, url))
{
//Makes the headers configurable
Activity.Current = null;
//set header manually
requestMessage.Headers.Add("Request-Id", operationId);
await client.SendAsync(requestMessage);
}
}
//send custom telemetry
telemetry.TrackDependency("Http", url, "myCall", startTime, timer.Elapsed, true);
Refer here more information.
Note: The above is possible by disabling the built-in dependency tracking and App Insights and handling it on your own. But the better approach is let .NET Core & App Insights do the tracking.

Azure Mobile Services - IMobileServiceSyncTable PullAsync does not fill local sync table

I'm working with Azure Mobile Services for my Xamarin.iOS app. I have my App Service set up both in the backend and on the client side and I'm able to successfully register an account on my app and see the entry in the respective table in the backend.
When I try to populate the local sync table however, using the call to PullAsync, the sync table is always empty, even when I try to return all the records in that table, without using any filters in my query.
I'm not sure why the PullAsync doesn't populate my sync tables, even when there are no exceptions.
Below is my code:
Initializing the Local Store
public class AzureMobileClientServiceDataManager : IAzureMobileClientServiceDataManager, IMobileServiceSyncHandler
{
const string localDbPath = "sample.db";
MobileServiceSQLiteStore store;
MobileServiceClient client { get; set; }
public AzureMobileClientServiceDataManager(IAzureMobileClientService azureMobileClientService)
{
CurrentPlatform.Init();
SQLitePCL.CurrentPlatform.Init();
//Initialize the Mobile service client with the Mobile App URL,Gatewaty URL and Key
client = azureMobileClientService.GetMobileServiceClientInstance();
}
public async Task InitializeStoreAsync()
{
store = new MobileServiceSQLiteStore(localDbPath);
store.DefineTable<Account>();
store.DefineTable<UserProfile>();
store.DefineTable<Purchase>();
await client.SyncContext.InitializeAsync(store, this);
}
public MobileServiceClient GetInstance()
{
return client;
}
public Task OnPushCompleteAsync(MobileServicePushCompletionResult result)
{
foreach (var error in result.Errors)
{
Console.WriteLine("Error :" + error.RawResult);
}
return Task.FromResult(0);
}
public Task<JObject> ExecuteTableOperationAsync(IMobileServiceTableOperation operation)
{
return operation.ExecuteAsync();
}
}
public interface IAzureMobileClientServiceDataManager
{
Task InitializeStoreAsync();
MobileServiceClient GetInstance();
}
Right after a successful registration of a new user, I call this function to populate my tables.
public async Task<Result<bool>> PopulateData()
{
try{
await accountTable.PullAsync("localAccount", accountTable.Where(ac => ac.Id == mobileServiceClient.CurrentUser.UserId));
await userProfileTable.PullAsync("localUserProfile", userProfileTable.Where(up => up.AccountId == mobileServiceClient.CurrentUser.UserId));
return Result<bool>.Success(true);
}catch(Exception ex)
{
return Result<bool>.Failure(ex, ex.StackTrace);
}
}
Do you have an azuredatamanager per table? If so, you are going to run into problems. The MobileServiceClient is meant to be global, and you will bump into issues with multiple tables, as you are re-initializing the table each time.
Also, try adding logging SQLite store to your app, which will log all local database statements, here's a sample: https://github.com/Azure-Samples/app-service-mobile-dotnet-todo-list-files/blob/master/src/client/MobileAppsFilesSample/Helpers/LoggingHandler.cs

Azure App Service - Update object from table controller

In the Azure app service mobile backend service, REST API requests are handled by TableController implementation. These methods can be invoked by using corresponding methods available in client SDKs. So, i can query for a particular entity and update its status from the client side.
But how to invoke them in the server side or within the same controller? For example, if I want to query for a particular todoItem and update its status from some custom method here like
Use LookUp(id) to get the item
Update the status
Use UpdateAsync(id, item)
Here I don't know how to create a Delta object of TodoItem to call UpdateAsync(id, patch) method.
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
initrackerserviceContext context = new initrackerserviceContext();
DomainManager = new EntityDomainManager<TodoItem>(context, Request);
}
// GET tables/TodoItem
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
// PATCH tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<TodoItem> PatchTodoItem(string id, Delta<TodoItem> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/TodoItem
public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
{
TodoItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteTodoItem(string id)
{
return DeleteAsync(id);
}
}
Just use the standard Entity Framework mechanisms. For instance, to find and update a record with a status, you can just use the context:
var item = await context.TodoItems.Where(i => i.Id.Equals(myId)).FirstOrDefaultAsync<TodoItem>();
if (item != null) {
item.Complete = true;
context.Entry(item).State = EntityState.Modified;
await context.SaveChangesAsync();
}
My EF coding is not the greatest ad-hoc, but you should get the idea. Just do the Entity Framework thing.
It's better to use TableController.ReplaceAsync() method that is already implemented for us here in the source code of EntityDomainManager.
var item = Lookup(item.Id).Queryable.FirstOrDefault();
if (item != null)
{
item.Complete = true;
item = await ReplaceAsync(item.Id, item);
}
The ReplaceAsync() method correctly handles the exceptions, so I would not recommend working directly with the EF context.

Azure AD Graph client library batch processing

Could some one tell me whether I can use the batch processing to add group memberships?
if yes can you please give me an example
thanks in advance
kind regards,
Snegha
According to the documentation, Graph API support batch processing. The Microsoft Azure Active Directory Client support batch processing.
You can find a lot of samples to use the Azure AD Graph API here :
Azure Samples for active directory
Especially you have a full sample on most of the actions that you can perform with the Graph API here :
Call the Azure AD Graph API from a native client (search for "Batch Operations" in the program.cs file).
Unfortunately batch processing is not working for navigation properties or at least I did not find a way to make it work...
So let's have a look at the documentation.
Graph API support for OData batch requests:
A query is a single query or function invocation.
A change set is a group of one or more insert, update, or delete operations, action invocations, or service invocations.
A batch is a container of operations, including one or more change sets and query operations.
The Graph API supports a subset of the functionality defined by the
OData specification:
A single batch can contain a maximum of five queries and/or change sets combined.
A change set can contain a maximum of one source object modification and up to 20 add-link and delete-link operations
combined. All operations in the change set must be on a single source
entity.
Here is the batch request syntax :
https://graph.windows.net/TenantName/$batch?api-version=1.6
A batch request is sent to the server with a single POST directive.
The payload is a multi-part MIME message containing the batch and its constituent queries and change sets. The payload includes two types of MIME boundaries:
A batch boundary separates each query and/or change set in the batch.
A change set boundary separates individual operations within a change set.
An individual request within a change set is identical to a request made when that operation is called by itself. (here is a sample request)
You can find here the full sample code : azure-active-directory-batchprocessing
So Basically, you need to obtain the authentication token:
var authority = "https://login.microsoftonline.com/mytenantName.onmicrosoft.com";
var resource = "https://graph.windows.net/";
var clientId = "ClientId of the application in the Azure Active Directory";
var clientSecret = "ClientSecret of the application in the Azure Active Directory";
var token = new AuthenticationContext(authority, false).AcquireToken(resource,
new ClientCredential(clientId, clientSecret)).AccessToken;
In your question you'd like to add member to a group (see Graph API Documentation on groups):
// Get the objectId of the group
var groupId = ...
// Get the member ids you'd like to add to the group
var memberIds = ...
Here is the code to add members to a group:
private static async Task AddMemberToGroup(string token, string groupId, IList<string> memberIds)
{
if (memberIds.Count > 100)
{
// A batch can contain up to 5 changesets. Each changeset can contain up to 20 operations.
throw new InvalidOperationException("Cannot send more than 100 operation in an batch");
}
var batch = new BatchRequest("https://graph.windows.net/MyTenantName.onmicrosoft.com");
// A changeset can contain up to 20 operations
var takeCount = 20;
var skipCount = 0;
var take = memberIds.Skip(skipCount).Take(takeCount).ToList();
while (take.Count > 0)
{
AddChangeset(batch, groupId, take);
skipCount += takeCount;
take = memberIds.Skip(skipCount).Take(takeCount).ToList();
}
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var response = await client.SendAsync(batch.Request);
}
}
private static void AddChangeset(BatchRequest batch, string groupId, IEnumerable<string> memberIds)
{
var changeset = batch.AddChangeSet();
foreach (var memberId in memberIds)
{
// Create the HttpRequest to add a member to a group
var request = AddMemberToGroupRequest(groupId, memberId);
// Add the operation to the changeset
changeset.AddOperation(request);
}
}
private static HttpRequestMessage AddMemberToGroupRequest(string groupId, string memberId)
{
// Create a request to add a member to a group
var request = new HttpRequestMessage(HttpMethod.Post,
$"https://graph.windows.net/MyTenantName.onmicrosoft.com/groups/{groupId}/$links/members?api-version=1.6");
// Create the body of the request
var jsonBody =
JsonConvert.SerializeObject(new DirectoryObject($"https://graph.windows.net/MyTenantName.onmicrosoft.com/directoryObjects/{memberId}"));
// Set the content
request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
// Return the request
return request;
}
public class BatchRequest
{
private readonly MultipartContent _batchContent;
public BatchRequest(string tenantUrl)
{
// Create the batch request
Request = new HttpRequestMessage(HttpMethod.Post,
$"{tenantUrl}/$batch?api-version=1.6");
// Initializes the batch content
_batchContent = new MultipartContent("mixed", "batch_" + Guid.NewGuid());
Request.Content = _batchContent;
}
public HttpRequestMessage Request { get; }
public ChangeSet AddChangeSet()
{
// Create a new changeset
var changeSet = new ChangeSet();
// Add the content of the changeset to the batch
_batchContent.Add(changeSet.Content);
// return the changeset
return changeSet;
}
public HttpMessageContent CreateOperation(HttpRequestMessage request)
{
var content = new HttpMessageContent(request);
content.Headers.ContentType = new MediaTypeHeaderValue("application/http");
content.Headers.Add("Content-Transfer-Encoding", "binary");
return content;
}
}
public class ChangeSet
{
public ChangeSet()
{
Content = new MultipartContent("mixed", "changeset_" + Guid.NewGuid());
}
public MultipartContent Content { get; }
public void AddOperation(HttpRequestMessage request)
{
var operationContent = new HttpMessageContent(request);
operationContent.Headers.ContentType = new MediaTypeHeaderValue("application/http");
operationContent.Headers.Add("Content-Transfer-Encoding", "binary");
Content.Add(operationContent);
}
}
public class DirectoryObject
{
public DirectoryObject(string url)
{
this.url = url;
}
public string url { get; }
}

Unable to use multiple instances of MobileServiceClient concurrently

I structured my project into multiple mobile services, grouped by the application type eg:
my-core.azure-mobile.net (user, device)
my-app-A.azure-mobile.net (sales, order, invoice)
my-app-B.azure-mobile.net (inventory & parts)
I'm using custom authentication for all my services, and I implemented my own SSO by setting the same master key to all 3 services.
Things went well when I tested using REST client, eg. user who "logged in" via custom api at my-core.azure-mobile.net is able to use the returned JWT token to access restricted API of the other mobile services.
However, in my xamarin project, only the first (note, in sequence of creation) MobileServiceClient object is working properly (eg. returning results from given table). The client object are created using their own url and key respectively, and stored in a dictionary.
If i created client object for app-A then only create for app-B, I will be able to perform CRUD+Sync on sales/order/invoice entity, while CRUD+Sync operation on inventory/part entity will just hang there. The situation is inverse if I swap the client object creation order.
I wonder if there is any internal static variables used within the MobileServiceClient which caused such behavior, or it is a valid bug ?
=== code snippet ===
public class AzureService
{
IDictionary<String, MobileServiceClient> services = new Dictionary<String, MobileServiceClient>();
public MobileServiceClient Init (String key, String applicationURL, String applicationKey)
{
return services[key] = new MobileServiceClient (applicationURL, applicationKey);
}
public MobileServiceClient Get(String key)
{
return services [key];
}
public void InitSyncContext(MobileServiceSQLiteStore offlineStore)
{
// Uses the default conflict handler, which fails on conflict
// To use a different conflict handler, pass a parameter to InitializeAsync.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=521416
var syncHandler = new MobileServiceSyncHandler ();
foreach(var client in services) {
client.Value.SyncContext.InitializeAsync (offlineStore, syncHandler);
}
}
public void SetAuthenticationToken(String uid, String token)
{
var user = new MobileServiceUser(uid);
foreach(var client in services) {
client.Value.CurrentUser = user;
client.Value.CurrentUser.MobileServiceAuthenticationToken = token;
}
}
public void ClearAuthenticationToken()
{
foreach(var client in services) {
client.Value.CurrentUser = null;
}
}
}
=== more code ===
public class DatabaseService
{
public static MobileServiceSQLiteStore LocalStore = null;
public static string Path { get; set; }
public static ISet<IEntityMappingProvider> Providers = new HashSet<IEntityMappingProvider> ();
public static void Init (String dbPath)
{
LocalStore = new MobileServiceSQLiteStore(dbPath);
foreach(var provider in Providers) {
var types = provider.GetSupportedTypes ();
foreach(var t in types) {
JObject item = null;
// omitted detail to create JObject using reflection on given type
LocalStore.DefineTable(tableName, item);
}
}
}
}
=== still code ===
public class AzureDataSyncService<T> : IAzureDataSyncService<T>
{
public MobileServiceClient ServiceClient { get; set; }
public virtual Task<List<T>> GetAll()
{
try
{
var theTable = ServiceClient.GetSyncTable<T>();
return theTable.ToListAsync();
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine("GetAll<{0}> EXCEPTION TYPE: {1}, EXCEPTION:{2}", typeof(T).ToString(), msioe.GetType().ToString(), msioe.ToString());
}
catch (Exception e)
{
Debug.WriteLine("GetAll<{0}> EXCEPTION TYPE: {1}, EXCEPTION:{2}", typeof(T).ToString(), e.GetType().ToString(), e.ToString());
}
List<T> theCollection = Enumerable.Empty<T>().ToList();
return Task.FromResult(theCollection);
}
}
=== code ===
public class UserService : AzureDataSyncService<User>
{
}
public class PartService : AzureDataSyncService<Part>
{
}
const string coreApiURL = #"https://my-core.azure-mobile.net/";
const string coreApiKey = #"XXXXX";
const string invApiURL = #"https://my-inventory.azure-mobile.net/";
const string invApiKey = #"YYYYY";
public async void Foo ()
{
DatabaseService.Providers.Add (new CoreDataMapper());
DatabaseService.Providers.Add (new InvDataMapper ());
DatabaseService.Init (DatabaseService.Path);
var coreSvc = AzureService.Instance.Init ("Core", coreApiURL, coreApiKey);
var invSvc = AzureService.Instance.Init ("Inv", invApiURL, invApiKey);
AzureService.Instance.InitSyncContext (DatabaseService.LocalStore);
AzureService.Instance.SetAuthenticationToken("AAA", "BBB");
UserService.Instance.ServiceClient = coreSvc;
PartService.Instance.ServiceClient = invSvc;
var x = await UserService.GetAll(); // this will work
var y = await PartService.GetAll(); // but not this
}
It's ok to use multiple MobileServiceClient objects, but not with the same local database. The offline sync feature uses a particular system tables to keep track of table operations and errors, and it is not supported to use the same local store across multiple sync contexts.
I'm not totally sure why it is hanging in your test, but it's possible that there is a lock on the local database file and the other sync context is waiting to get access.
You should instead use different local database files for each service and doing push and pull on each sync context. With your particular example, you just need to move LocalStore out of DatabaseService and into a dictionary in AzureService.
In general, it seems like an unusual design to use multiple services from the same client app. Is there a particular reason that the services need to be separated from each other?

Resources