<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[The Technical Blog]]></title><description><![CDATA[Integration Architect | SAP Integration Suite / PO Consultant | AWS Solutions Architect]]></description><link>https://quachtd.com/</link><image><url>https://quachtd.com/favicon.png</url><title>The Technical Blog</title><link>https://quachtd.com/</link></image><generator>Ghost 3.8</generator><lastBuildDate>Tue, 14 Apr 2026 09:33:44 GMT</lastBuildDate><atom:link href="https://quachtd.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[SAP Integration Suite integrates with AWS Kinesis Data Stream for Data Analytics]]></title><description><![CDATA[In this blog, we will experiment with extracting data from SAP Integration Suite API and using AWS Kinesis Data Stream for data ingestion and then have an insight into the collected data with Apache Flink. Also, reacting to anomaly detection of the data.]]></description><link>https://quachtd.com/sap-integration-suite-integrates-with-aws-kinesis-data-stream-for-data-analytics/</link><guid isPermaLink="false">65f4d3e8269a12045d85e2ac</guid><category><![CDATA[SAP Integration Suite]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Fri, 15 Mar 2024 23:19:07 GMT</pubDate><media:content url="https://quachtd.com/content/images/2024/03/AnhQuach-CatalinaWaves.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://quachtd.com/content/images/2024/03/AnhQuach-CatalinaWaves.jpg" alt="SAP Integration Suite integrates with AWS Kinesis Data Stream for Data Analytics"><p>In this blog, we will experiment with extracting data from SAP Integration Suite API and using AWS Kinesis Data Stream for data ingestion and then have an insight into the collected data with Apache Flink. Also, reacting to anomaly detection of the data.</p>
<p>SAP Integration Suite API provides different sets of APIs such as Integration Content which has enough functions to build a CI/CD pipeline for interface development, Partner Directory provides a flexible role base logical, Security artifacts, and Message Processing Log... We use a few OData endpoints in the Message Processing Log and Integration Content in this blog, consuming API sets is the same except the required role for each API set is different – the role is to <a href="https://quachtd.com/authorize-api-client-access-sap-cloud-integration-api/">authorize</a> the OAuth client to consume APIs. You should consult <a href="https://help.sap.com/docs/cloud-integration/sap-cloud-integration/api-details">SAP doc</a> for more detail on the required roles of each API set.</p>
<p>We have 2 parts. SAP Integration Suite (CPI) is the focus of this part where we implement data collecting and reacting - undeploy the artifact which causes permanent errors that we get notification from AWS. In the second part, we will implement an AWS data pipeline to ingress and analyze data and then send out a notification (AWS SNS) if there is something that exceeds the predefined threshold. With collected data, we can also send it to a data lake to study (ML) and can make a better decision in SAP Message Monitor or SAP Security artifact rotation.</p>
<h1 id="thecollectingworkflow">The collecting workflow</h1>
<p>In a period, we call Message Processing Log OData API to get all messages and for each error message we will call another endpoint to enrich the message with more detail of the error, and then streaming messages to AWS Kinesis Data Stream by using AWS adapter.</p>
<p>Let’s say we have a 2-minute timer, and we have 5000 messages which include 1000 error messages (just an example), in the ideal case we must make 1 + 1000 API calls in a 2-minute window. What if it could not make it? We may increase the timer to have a bigger window to collect data, but it won’t be lucky all the time.</p>
<p>Like any long-run workflow, it’s better to decouple it into asynchronous parts. We can use any event/message service to leverage the decoupling, there are many options in the market, and it depends on each company. We are using AWS and prefer to use Event Bus of Amazon EventBridge as it natively integrates with many target services in AWS. As I know so far there are 3 AWS adapters available on SAP Integration Suite but only the Advantco AWS adapter supports EventBridge now.</p>
<p>In the download package, we also provide an implementation of Event Mesh for your reference, but you will see why we choose a cloud native event service over Event Mesh. With Event Mesh, it simply does the decoupling that why at Integration flow #2 we consume both Success and Error messages from the broker to route them to AWS Kinesis. Amazon EventBridge Event Bus is also an event router where we can route the Success messages to Kinesis inside AWS (we will have details in part 2 of the blog), so we only need to consume the Error messages in Integration flow #2 to enrich with detailed error and route it to the final destination AWS Kinesis.</p>
<p>Another reason and the most important why we chose the Advantco AWS adapter for the implementation is because it’s the only adapter that supports the IAM role – the security best practice in AWS.</p>
<p>We have 2 integration flows:</p>
<ul>
<li>Integration flow 1: Collect all messages and split them one by one then send it out to a SQS topic.</li>
<li>Integration flow 2: Asynchronously receive messages from the SQS topic, enrich the message with the error detail, and then send to a Kinesis Data Stream. Multiple threads are counted here, which helps to handle the high throughput.</li>
</ul>
<p>Some screenshots of the integration flows are below. You can download the whole package of integration artifacts [here](<a href="https://github.com/quachtd/sap_is_aws_data_integration/blob/main/AWS">https://github.com/quachtd/sap_is_aws_data_integration/blob/main/AWS</a> Data Integration 1.0.1.zip) for more details of the implementation. It’s just a proof of concept, you have to <a href="https://api.sap.com/package/DesignGuidelinesHandleErrors/integrationflow">handle error gracefully</a> and add more logic if needed.</p>
<p><strong>Integration flow 1 – MessageProcessing_CI2AWS_OB</strong><br>
<img src="https://quachtd.com/content/images/2024/03/Screenshot1.png" alt="SAP Integration Suite integrates with AWS Kinesis Data Stream for Data Analytics"></p>
<p><strong>Integration flow 2 – MessageProcessing_CI2AWS_GetMoreDetail_OB</strong><br>
As I mentioned above with EventBridge Event Bus we route Success messages directly to Kinesis in AWS, for the Error messages we route them to AWS SQS that is why we use AWS SQS as the sender adapter here.<br>
We are supposed to make the integration flow simpler with OData adapter, but the OData receiver adapter doesn’t support navigation of the API which is why we use HTTP adapter and require an additional Integration Process for the enrichment step.</p>
<p><img src="https://quachtd.com/content/images/2024/03/Screenshot2.png" alt="SAP Integration Suite integrates with AWS Kinesis Data Stream for Data Analytics"></p>
<h1 id="thereactingworkflow">The reacting workflow</h1>
<p>Any exceed of the predefined threshold will trigger an SNS message in AWS, and then the subscribed SQS topic will receive the message (configured in AWS). In SAP Integration Suite, we consume the message and consider it as a notification for remediation.</p>
<p>In this implementation, if any integration flow keeps repeating errors for 4 hours continuously then a notification is triggered (in AWS which we will cover in part2) and the integration flow will be undeployed and a notification message will be sent out to inform the support team.</p>
<p>We have 1 integration flow to accomplish it:</p>
<ul>
<li>Integration flow 3: Receive a notification message from an SQS topic, then undeploy the integration flow where the integration id is from the message payload, and finally send out an SNS message to inform the support team. Email/Mobile notification from the SNS topic is configured in AWS, not a part of this integration flow.</li>
</ul>
<p>The screenshot of the integration flow is below.</p>
<p><strong>Integration flow 3 – IntegrationContent_AWS2CI_UndeployRuntimeArtifact_IB</strong><br>
<img src="https://quachtd.com/content/images/2024/03/Screenshot3.png" alt="SAP Integration Suite integrates with AWS Kinesis Data Stream for Data Analytics"></p>
<p>That’s it for the first part. We will continue with the second part of working on AWS.<br>
All you need to try out this implementation:</p>
<ul>
<li>[Download the package of artifacts.](<a href="https://github.com/quachtd/sap_is_aws_data_integration/blob/main/AWS">https://github.com/quachtd/sap_is_aws_data_integration/blob/main/AWS</a> Data Integration 1.0.1.zip)</li>
<li>Look at the API sets and configure the access as I mentioned above.</li>
<li><a href="https://www.advantco.com/contact-us">Reach out to Advantco for the trial version of AWS adapter</a> and they will also help you to understand the configuration of AWS adapter (EventBridge, SQS, SNS, Kinesis).</li>
</ul>
<p>Hope you find something new from this blog and enjoy it.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Switching clusters in kubectl CLI]]></title><description><![CDATA[<p>This is as my note</p><!--kg-card-begin: markdown--><h3 id="showallconfiguredclusterscontexts">Show all configured clusters/contexts</h3>
<blockquote>
<p>&gt; kubectl config get-contexts</p>
</blockquote>
<h3 id="sameasinconfigfile">Same as in config file</h3>
<blockquote>
<p>&gt; cat ~/.kube/config<br>
We can add/delete/modify directly the config file</p>
</blockquote>
<h3 id="switchclustercontext">Switch cluster/context</h3>
<blockquote>
<p>&gt; kubectl config use-context [context name]</p>
</blockquote>
<h3 id="changenamespace">Change namespace</h3>
<blockquote>
<p>&gt; kubectl config set-context --current --namespace [ns</p></blockquote>]]></description><link>https://quachtd.com/switching-cluster-in-kubectl-cli/</link><guid isPermaLink="false">657b0463269a12045d85e22c</guid><category><![CDATA[kubectl]]></category><category><![CDATA[kubernetes]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Thu, 14 Dec 2023 13:50:37 GMT</pubDate><content:encoded><![CDATA[<p>This is as my note</p><!--kg-card-begin: markdown--><h3 id="showallconfiguredclusterscontexts">Show all configured clusters/contexts</h3>
<blockquote>
<p>&gt; kubectl config get-contexts</p>
</blockquote>
<h3 id="sameasinconfigfile">Same as in config file</h3>
<blockquote>
<p>&gt; cat ~/.kube/config<br>
We can add/delete/modify directly the config file</p>
</blockquote>
<h3 id="switchclustercontext">Switch cluster/context</h3>
<blockquote>
<p>&gt; kubectl config use-context [context name]</p>
</blockquote>
<h3 id="changenamespace">Change namespace</h3>
<blockquote>
<p>&gt; kubectl config set-context --current --namespace [ns name]</p>
</blockquote>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Authorize API client to access Cloud Integration API]]></title><description><![CDATA[Authorize API client to access Cloud Integration API]]></description><link>https://quachtd.com/authorize-api-client-access-sap-cloud-integration-api/</link><guid isPermaLink="false">653b0ef0269a12045d85e08d</guid><category><![CDATA[SAP Cloud Integration]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Fri, 20 Oct 2023 16:43:00 GMT</pubDate><media:content url="https://quachtd.com/content/images/2023/10/Epson_10262023200750.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2023/10/Epson_10262023200750.jpg" alt="Authorize API client to access Cloud Integration API"><p>The config is different for Neo and Cloud Foundry (CF) environment. This post is for OAuth2 with authorization type of client id and secret. We can use OAuth2 with other grant type or certificate, the config is similar. </p><p></p><h2 id="cloud-integration-neo">Cloud Integration, Neo</h2><p>#1 Register API Client on cockpit<br>Cockpit &gt; Security &gt; OAuth &gt; then Clients tab &gt; create a new Client.<br><br>looking for Subscription with "tmn"node, and select Grant of Client Credentials. As the screenshot.<br></p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2023/10/Screenshot-2023-10-26-at-7.21.06-PM.png" class="kg-image" alt="Authorize API client to access Cloud Integration API"></figure><p>#2 Assign specific roles (required by API endpoints) to the API client.<br>Looking for roles that required by the API endpoint. In this case, I use MessageProcessLogs and the roles for Neo are: IntegrationOperationServer.read, NodeManager.read</p><p>Cockpit &gt; Security &gt; OAuth &gt; Authorizations<br><br>Hit button Show Assignments for <em>oauth_client_&lt;created client id&gt;</em> to assign role. Looking for Application "tmn" and assign roles to it.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2023/10/Screenshot-2023-10-26-at-7.27.44-PM.png" class="kg-image" alt="Authorize API client to access Cloud Integration API"></figure><p>#3 Test it with Postman<br>We have client id and client secret. Now we need to get token endpoint for OAuth, go to cockpit &gt; Security &gt; OAuth &gt; go to Branding tab&gt; there is a token endpoint here.</p><p>From SAP Cloud Integration API doc, we have the endpoint such as /MessageLoggingLogs but we don't know the full URL. Basically the full url is: the url where you access CPI tenant + "/api" + the endpoint.<br>Or you can find it from cockpit &gt; Applications &gt; Subscriptions &gt; click on the applicaiton with "tmn" &gt; you will see the url with /api.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2023/10/Screenshot-2023-10-26-at-7.48.02-PM.png" class="kg-image" alt="Authorize API client to access Cloud Integration API"></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2023/10/Screenshot-2023-10-26-at-7.50.19-PM.png" class="kg-image" alt="Authorize API client to access Cloud Integration API"><figcaption>Hit Get New Access Token before make the call to the endpoint.</figcaption></figure><h2 id="cloud-integration-cloud-foundry">Cloud Integration, Cloud Foundry</h2><p>#1 Create a Instance for API Client<br>Looking for roles that required by the API endpoint. In this case, I use MessageProcessLogs and the role for CF is: MonitorDataRead.<br><br>Note: we can reuse the existing Instance, but we have to maintain required roles for different API endpoints. So, add MonitorDataRead to the existing one.<br><br>Go to Cockpit &gt; Services &gt; Instances and Subscriptions &gt; create a new instance of "Process Integration Runtime" with "api" plan, and then select role MonitorDataRead (this is important, don't miss it), and "Client Credentials" as grant type.</p><p>#2 Create a Service Key from the created instance.<br>Choose "ClientId/Secret" as the Key Type.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2023/10/Screenshot-2023-10-26-at-7.56.41-PM.png" class="kg-image" alt="Authorize API client to access Cloud Integration API"></figure><p>#3 Test with Postman:</p><p>From the previous step, we have everything from client id, secret, url (hostname) of the endpoint, and token endpoint for Postman.<br>Using Oauth with Postman is similar to the part of testing with Neo.</p><p></p><h2 id="references">References</h2><ul><li><a href="https://help.sap.com/docs/cloud-integration/sap-cloud-integration/message-processing-logs">https://help.sap.com/docs/cloud-integration/sap-cloud-integration/message-processing-logs</a></li><li><a href="https://help.sap.com/docs/cloud-integration/sap-cloud-integration/setting-up-oauth-inbound-authentication-with-client-credentials-grant-for-api-clients">https://help.sap.com/docs/cloud-integration/sap-cloud-integration/setting-up-oauth-inbound-authentication-with-client-credentials-grant-for-api-clients</a></li><li><a href="https://help.sap.com/docs/cloud-integration/sap-cloud-integration/setting-up-inbound-http-connections-for-api-clients">https://help.sap.com/docs/cloud-integration/sap-cloud-integration/setting-up-inbound-http-connections-for-api-clients</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Configure HTTPS for Elasticsearch]]></title><description><![CDATA[Configure https for elasticsearch]]></description><link>https://quachtd.com/configure-https-for-elasticsearch/</link><guid isPermaLink="false">636541fac3566207fe91a101</guid><category><![CDATA[Elasticsearch]]></category><category><![CDATA[https]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Fri, 04 Nov 2022 17:07:40 GMT</pubDate><media:content url="https://quachtd.com/content/images/2022/11/Epson_02152021190732.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2022/11/Epson_02152021190732.jpg" alt="Configure HTTPS for Elasticsearch"><p></p><p>1.       Generate CA and key for ELK stack</p><!--kg-card-begin: html--><pre><code class="lang-java">./bin/elasticsearch-certutil ca</code></pre><!--kg-card-end: html--><p></p><p>2.       Generate certs and its key for nodes (in the cluster, do the same for each node)</p><!--kg-card-begin: html--><pre><code class="lang-java">./bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12</code></pre><!--kg-card-end: html--><p></p><p>3.       Config the node inter communication - elasticsearch.yml</p><!--kg-card-begin: html--><pre><code>xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.client_authentication: required
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12</code>
</pre><!--kg-card-end: html--><p>if there is a password for the private key then add it to elasticsearch keystore.</p><!--kg-card-begin: html--><pre><code class="lang-java">./bin/elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password

./bin/elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_password
</code></pre><!--kg-card-end: html--><p></p><p>4.       Generate cert and its key for https config</p><!--kg-card-begin: html--><pre><code class="lang-java">./bin/elasticsearch-certutil http</code></pre><!--kg-card-end: html--><p>It generated elasticsearch-ssl-http.zip file:</p><!--kg-card-begin: html--><pre><code>/elasticsearch
|_ README.txt
|_ http.p12
|_ sample-elasticsearch.yml

/kibana
|_ README.txt
|_ elasticsearch-ca.pem
|_ sample-kibana.yml</code>
</pre><!--kg-card-end: html--><p>5.       From Elasticsearch, use the key http.p12 from previous step - elasticsearch.yml</p><!--kg-card-begin: html--><pre><code>xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: http.p12</code>
</pre><!--kg-card-end: html--><p>if there is a password for the private key, then add it to elasticsearch keystore.</p><!--kg-card-begin: html--><pre><code class="lang-java">./bin/elasticsearch-keystore add xpack.security.http.ssl.keystore.secure_password</code></pre><!--kg-card-end: html--><p></p><p>6.       From Kibana, use the cert elasticsearch-ca.pem from the previous step – kibana.yml</p><!--kg-card-begin: html--><pre><code>elasticsearch.ssl.certificateAuthorities: elasticsearch-ca.pem

elasticsearch.hosts: https://<your_elasticsearch_host>:9200</your_elasticsearch_host></code>
</pre><!--kg-card-end: html--><p></p><p>References:<br><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup-https.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup-https.html</a></p>]]></content:encoded></item><item><title><![CDATA[Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO]]></title><description><![CDATA[At the time of writing this blog – Election week 2020, Confluent Cloud Shema Registry using Let’s Encrypt to sign the certificates for Schema Registry (HTTPS endpoint), it uses TLS 1.2, ECDHE_RSA with P-256, and AES_256_GCM.]]></description><link>https://quachtd.com/troubleshoot-tls-1-2-with-elliptic-curve-cryptography/</link><guid isPermaLink="false">5fa8638b6832ff125c509eea</guid><category><![CDATA[SAP PI/PO]]></category><category><![CDATA[TLS 1.2]]></category><category><![CDATA[Elliptic curve]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Sun, 08 Nov 2020 21:45:15 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/11/Epson_10282020133704.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/11/Epson_10282020133704.jpg" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"><p>Updated: 7/22/2024 - Symptom: "Error during ping operation:Error while silently connecting: org.w3c.www.protocol.http.HttpException: Peer sent alert: Alert Fatal: handshake failure", SAP note <a href="https://me.sap.com/notes/0003424764">3424764</a></p><p>At the time of writing this blog – Election week 2020, Confluent Cloud Shema Registry using Let’s Encrypt to sign the certificates for Schema Registry (HTTPS endpoint), it uses TLS 1.2, ECDHE_RSA with P-256, and AES_256_GCM.</p><p>And it’s not working with SAP PO 7.5 latest SP 19. There is a list of default cipher suites, but it’s not including the one above. For more information or those steps below not working, refer to this note <a href="https://launchpad.support.sap.com/#/notes/2284059">2284059</a>.</p><p>The solution is to update the cipher suits list. Follow these steps:</p><p>1.       Identify the problem. Follow this note <a href="https://launchpad.support.sap.com/#/notes/2616423">2616423</a></p><p>2.       Make sure the JCE policy files on the server are unlimited. Follow this note <a href="https://launchpad.support.sap.com/#/notes/1240081">1240081</a></p><p>3.       Add a system property “iaik.security.ssl.configFile” to point custom-defined cipher suites. Follow this note <a href="https://launchpad.support.sap.com/#/notes/2708581">2708581</a></p><p>4.       Restart Java AS to get the new system property effect.</p><!--kg-card-begin: html--><br><!--kg-card-end: html--><p>Implementation Steps:</p><p>Note: Don’t forget to import certs chain of the target endpoint to the Netweaver Keystore.</p><p>1.       Identify the problem</p><ul><li>Identify the certificate of the request endpoint.</li></ul><p>Using Chrome Developer Tool (Ctrl + Shift + I)</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/11/photo1.png" class="kg-image" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"><figcaption>Chrome Developer Tool</figcaption></figure><p>Or from website <a href="https://www.ssllabs.com/ssltest/index.html">https://www.ssllabs.com/ssltest/index.html</a></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/11/photo2.png" class="kg-image" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"><figcaption>SSLLabs</figcaption></figure><ul><li>With an error of handshake in SAP PO trace or XPI Inspector – start XPI Inspector and then trigger a request message to the target endpoint.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/11/photo3.png" class="kg-image" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"><figcaption>XPI Inspector</figcaption></figure><p>SSLException: Peer sent alert: Alert Fatal: handshake failure</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/11/photo4.png" class="kg-image" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"></figure><p>Or error of limited JCE policy<br>Unable to encrypt SSL message: java.security.InvalidKeyException: Illegal key size</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/11/photo5.png" class="kg-image" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"></figure><p>2.       Edit/Validate file java.security to the JCE policy unlimited</p><p>\usr\sap\&lt;SID&gt;\SYS\exe\jvm\&lt;platform&gt;\sapjvm_8.1.065\sapjvm_8\jre\lib\security</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/11/photo6.png" class="kg-image" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"></figure><p>3.       Add property “Diaik.security.ssl.configFile”</p><p>Create a file SSLContext.properties – place at anywhere in the server.</p><!--kg-card-begin: html--><pre>
<code>
#
client.allowLegacyRenegotiation=true
extension=signature_algorithms
extension=server_name.noncritical
extension=elliptic_curves
extension=ec_point_formats
securityProvider=iaik.security.ssl.ECCelerateProvider
#
# enable cipher suites with ECDHE key exchange (unlimited strength)
cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
cipherSuite=TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
cipherSuite=TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256
cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
cipherSuite=TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384
cipherSuite=TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384
#
# keep default cipher suites as fallback
cipherSuite=TLS_RSA_WITH_AES_128_GCM_SHA256
cipherSuite=TLS_RSA_WITH_AES_128_CBC_SHA
cipherSuite=TLS_RSA_WITH_AES_128_CBC_SHA256
cipherSuite=TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256
cipherSuite=TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
cipherSuite=TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256
cipherSuite=TLS_RSA_WITH_AES_256_GCM_SHA384
cipherSuite=TLS_RSA_WITH_AES_256_CBC_SHA
cipherSuite=TLS_RSA_WITH_AES_256_CBC_SHA256
cipherSuite=TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384
cipherSuite=TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
cipherSuite=TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256
cipherSuite=SSL_RSA_WITH_3DES_EDE_CBC_SHA
cipherSuite=SSL_RSA_WITH_RC4_128_SHA
#
# enable old&slow DHE cipher suites (you don't want them with limited strength 1024-bit DHE at all)
cipherSuite=TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
cipherSuite=TLS_DHE_RSA_WITH_AES_128_CBC_SHA
cipherSuite=TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
cipherSuite=TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
cipherSuite=TLS_DHE_RSA_WITH_AES_256_CBC_SHA
cipherSuite=TLS_DHE_RSA_WITH_AES_256_CBC_SHA256

</code>
</pre><!--kg-card-end: html--><p>Add System property – NWA &gt; Configuration &gt; Infrastructure &gt; Java System Properties &gt; Additional VM Parameters</p><p>WINDOWS<br>-Diaik.security.ssl.configFile=file:/d:/tmp/SSLContext.properties</p><p>UNIX<br>-Diaik.security.ssl.configFile=file:/tmp/SSLContext.properties</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/11/photo7.png" class="kg-image" alt="Troubleshoot TLS 1.2 with Elliptic-curve cryptography in SAP PO"></figure><p>4.       The restart Java AS.</p>]]></content:encoded></item><item><title><![CDATA[Note: Use sdaMakerTool to deploy JDBC/JMS driver to SAP PI/PO]]></title><description><![CDATA[<p><strong>Validate existing installed drivers</strong></p><p>Go to url <a href="http://hostname">http://hostname</a>:/nwa/classloader</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/09/nwa_classloader.png" class="kg-image"></figure><p>Request the existing SDA file (from Basis) if there are notice drivers that using with JMS/JDBC channels in the system.</p><blockquote><em>If it is the first time deploy any driver to the system then get <strong>com.sap.aii.adapter.</strong></em></blockquote>]]></description><link>https://quachtd.com/note-deploy-jdbc-jms-driver-to-sap-pi-po/</link><guid isPermaLink="false">5f5be0706832ff125c509e32</guid><category><![CDATA[SAP PI/PO]]></category><category><![CDATA[JDBC]]></category><category><![CDATA[JMS]]></category><category><![CDATA[SDAMakerTool]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Mon, 26 Oct 2020 22:28:45 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/10/Epson_10192020121019.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/10/Epson_10192020121019.jpg" alt="Note: Use sdaMakerTool to deploy JDBC/JMS driver to SAP PI/PO"><p><strong>Validate existing installed drivers</strong></p><p>Go to url <a href="http://hostname">http://hostname</a>:/nwa/classloader</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/09/nwa_classloader.png" class="kg-image" alt="Note: Use sdaMakerTool to deploy JDBC/JMS driver to SAP PI/PO"></figure><p>Request the existing SDA file (from Basis) if there are notice drivers that using with JMS/JDBC channels in the system.</p><blockquote><em>If it is the first time deploy any driver to the system then get <strong>com.sap.aii.adapter.lib.sda</strong> from the SAPXI3RDPARTY*.SCA (download from software center)</em></blockquote><p><strong>Add driver file to the SDA file</strong></p><p>Such as we are going to use JDBC for MySQL 5.1.41 then go to MySQL website to download JDBC driver for that version.</p><p>Use SDA Marker Tool to add the Jar file to the existing SDA file - SAP note 1987079</p><!--kg-card-begin: html--><pre><code class="lang-java">java -jar sdaMakerTool.jar</code></pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/09/sdkMakerTool.png" class="kg-image" alt="Note: Use sdaMakerTool to deploy JDBC/JMS driver to SAP PI/PO"></figure><p>The tool actually update 2 places in the SDA file:</p><p> 1. {sda file}\lib - add Jar file to lib folder</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/09/sdafile_lib.png" class="kg-image" alt="Note: Use sdaMakerTool to deploy JDBC/JMS driver to SAP PI/PO"></figure><p> 2. {sda file}\server - add one line to provider.xml</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/09/sdafile_server_provider.png" class="kg-image" alt="Note: Use sdaMakerTool to deploy JDBC/JMS driver to SAP PI/PO"></figure><blockquote><em>We can manually edit the SDA file to add Jar file and edit provider.xml if we don't want to use SDA Marker Tool</em></blockquote><p>Many SAP Notes just in cases this not working: 1138877, 850116, 831162, 1987079, 1931899, 2513069</p>]]></content:encoded></item><item><title><![CDATA[Google ADC with short-lived credential]]></title><description><![CDATA[In this blog we will go through steps that using GCP ADC (application default credential) to impersonate another service account to generate the access token.]]></description><link>https://quachtd.com/google-adc/</link><guid isPermaLink="false">5f3ee1116832ff125c509c5c</guid><category><![CDATA[GCP]]></category><category><![CDATA[GCP ADC]]></category><category><![CDATA[Service Account]]></category><category><![CDATA[serviceAccountTokenCreator]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Fri, 21 Aug 2020 01:53:21 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/08/IMG_20200820_184424.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/08/IMG_20200820_184424.jpg" alt="Google ADC with short-lived credential"><p>In this blog we will go through steps that using GCP ADC (application default credential) to impersonate another service account to generate the access token.</p><ol><li>Create a VM</li></ol><!--kg-card-begin: html--><pre class="lang-js"><code>gcloud compute instances create instance-1 --machine-type=f1-micro --zone=us-central1-a --scopes=https://www.googleapis.com/auth/cloud-platform</code></pre><!--kg-card-end: html--><p>Validate the Cloud API access scopes is "Allow full access to all Cloud APIs"</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/08/vm_cloud_api_access_scopes-1.png" class="kg-image" alt="Google ADC with short-lived credential"></figure><p>Take note the service account from the created VM, we will use it in the next step.</p><blockquote>The default service account for the compute is {project number}-<a>compute@developer.gserviceaccount.com</a></blockquote><p>2. Assign role <em>roles/iam.serviceAccountTokenCreator</em> to the service account.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/08/iam_role_serviceAccountTokenCreator.png" class="kg-image" alt="Google ADC with short-lived credential"></figure><p>3. Create 2 services accounts for testing purpose</p><p>Create the service account <a href="https://console.cloud.google.com/iam-admin/serviceaccounts/details/115246868235577275006?orgonly=true&amp;project=goingdelit&amp;supportedpurview=organizationId" rel="noopener">gcs-only@goingdelit.iam.gserviceaccount.com</a></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/create_sa_1.1.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>an service account for storage operation only - gcs-only</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/create_sa_1.2.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>assign 2 roles of Storage Admin and Storage Object Admin</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/create_sa_1.3.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>set the service account of the VM</figcaption></figure><p>Repeat to create another service account bigquery<a href="https://console.cloud.google.com/iam-admin/serviceaccounts/details/115246868235577275006?orgonly=true&amp;project=goingdelit&amp;supportedpurview=organizationId" rel="noopener">-only@goingdelit.iam.gserviceaccount.com</a> - but don't grant any role to this user to test the access token</p><p>4. Install "gcloud" and connect it to the GCP project in the VM</p><p>Follow this guide to install it.</p><p><a href="https://cloud.google.com/sdk/install">https://cloud.google.com/sdk/install</a></p><p>Don't forgot the last step to connect to GCP via "gcloud init"</p><p>5. Get the access token of the ADC</p><p>This step have to run inside the VM.</p><!--kg-card-begin: html--><pre class="lang-curl"><code>curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" -H "Metadata-Flavor: Google"</code></pre><!--kg-card-end: html--><p>The output looks like this</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/adc_access_token.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>The access token from the service account that running this VM</figcaption></figure><blockquote><em>Similar with gcloud command "gcloud auth application-default print-access-token"</em></blockquote><p>6. Enable "IAM Service Account Credentials API"</p><p>Open the URL below and enable the API.</p><!--kg-card-begin: markdown--><p><a href="https://console.developers.google.com/apis/api/iamcredentials.googleapis.com/overview">https://console.developers.google.com/apis/api/iamcredentials.googleapis.com/overview</a></p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/08/enable_api_IAMServiceAccountCredentialsAPI.png" class="kg-image" alt="Google ADC with short-lived credential"></figure><p>7. Generate the access for the service account "gcs-only"</p><p>This step we can run anywhere. In our local machine, make a HTTP request to this end point to generate the access token for the service account "gcs-only".</p><!--kg-card-begin: html--><pre class="lang-js"><code>POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{email of the service account}:generateAccessToken</code></pre><!--kg-card-end: html--><!--kg-card-begin: html--><pre class="lang-curl"><code>curl --location --request POST 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/gcs-only@goingdelit.iam.gserviceaccount.com:generateAccessToken' \
--header 'Authorization: Bearer ya29.c.Kn_ZB1Iucwnl1R8u5OJtjMioQE94-_TPv8RaEbzr8AAXG1TeBZT4WY_G9xCDwIFFkoLdYgcOz6AgRN1hpXDP7uQSmhEQocZH9-sJa52DnZXvT39PFdclhNvej1dDxTw9WgiXpI_e74xge1Ss4i2ZfBt2EPxsMjR9jWNVY5ptZdDD' \
--header 'Content-Type: application/json' \
--data-raw '{
  "scope": [
      "https://www.googleapis.com/auth/cloud-platform"
  ]
}'</code></pre><!--kg-card-end: html--><p>Note the Bearer Token is from the step #5</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/generate_access_token.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>Generate the access token for service account "gcs-only"</figcaption></figure><p>Finally we have the access token for the service account "gcs-only", we will use it to perform a storage API test in next step.</p><p>8. Test the generated access token</p><p>Create a Bucket and upload some files to the bucket.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/gcs_bucket.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>Bucket storage_for_gcs_only</figcaption></figure><p>Make a GET request to this endpoint to list all objects in the bucket</p><!--kg-card-begin: html--><pre class="lang-curl"><code>GET https://storage.googleapis.com/storage/v1/b/{bucket name}/o</code></pre><!--kg-card-end: html--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/gcs_get_bucket_objects.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>Use Postman or CURL to test the endpoint, notice the token is the from the previous step #7</figcaption></figure><p>Repeat step #7 to generate access token for the service account "bigquery-only" - it won't work as the impersonated service account don't have the appropriate role to do operation on Storage Objects (look back step #3).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/08/gcs_get_bucket_objects_403.png" class="kg-image" alt="Google ADC with short-lived credential"><figcaption>403 error</figcaption></figure><p><strong>References</strong></p><p><a href="https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials">Create short-lived service account credentials</a></p><p><a href="https://developers.google.com/identity/protocols/oauth2/scopes">OAuth 2.0 Scopes for Google APIs</a></p><p><a href="https://cloud.google.com/docs/authentication/production">ADC - Application default credentials</a></p><p><a href="https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken">Generate Access Token API</a></p><p><a href="https://cloud.google.com/appengine/docs/standard/java/accessing-instance-metadata">Access Instance Metadata Endpoint</a></p>]]></content:encoded></item><item><title><![CDATA[Using Kafka REST Proxy with Protobuf]]></title><description><![CDATA[<p>The Kafka REST Proxy provides REST API to produce and consume messages and view metadata of cluster, broker, partition, and topic.</p><p>Using REST API is the fastest way to experiment with producing and consuming messages from Kafka broker. But it is not simple to use this API set, as with</p>]]></description><link>https://quachtd.com/using-kafka-rest-proxy-with-protobuf/</link><guid isPermaLink="false">5edada2f6832ff125c509bc8</guid><category><![CDATA[Kafka]]></category><category><![CDATA[Schema Registry]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Sat, 06 Jun 2020 00:04:00 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/06/IMG_20200530_174943.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/06/IMG_20200530_174943.jpg" alt="Using Kafka REST Proxy with Protobuf"><p>The Kafka REST Proxy provides REST API to produce and consume messages and view metadata of cluster, broker, partition, and topic.</p><p>Using REST API is the fastest way to experiment with producing and consuming messages from Kafka broker. But it is not simple to use this API set, as with the same API endpoint, it works for different serialization formats. In this post, we use Confluent 5.5, which adding support of Protobuf and JSON Schema to the Schema Registry, and we will use API endpoint to produce messages to a Kafka topic with Protobuf schema, and use API endpoints to consume messages from the topic.</p><h1 id="content-types">Content Types</h1><p>Content types are the first and essential thing to look at when using the API set. It used for <em>Content-Type </em>and <em>Accept</em> headers of the HTTP request.</p><p>This REST API has three versions v1, v2, and v3, but version v3 is in the preview stage, and we do not look at it here</p><!--kg-card-begin: html--><pre class="lang-js"><code>application/vnd.kafka.{Embeded format}.{API version}+json</code></pre><!--kg-card-end: html--><p>Here are the samples:</p><p>The Avro content type is <strong><em>application/vnd.kafka.avro.v2+json</em></strong></p><p>The Protobuf content type is <em><strong>application/vnd.kafka.protobuf.v2+json</strong></em></p><p>The JSON schema content type is <strong><em>application/vnd.kafka.jsonschema.v2+json</em></strong></p><p>We will see how we use it in the next sections.</p><h1 id="message-schema">Message Schema</h1><p>First, we define a schema for producer and consumer, we use the same for both here, but it can involve the schema any time without any impact of other ends. That is the purpose of using schema. Here is the schema of Protobuf format that we use in this post.</p><!--kg-card-begin: html--><pre><code class="lang-protobuf">message value_salesorder_topic {
  required uint32 orderId = 1;
  optional string orderType = 2;
  required uint32 plant = 3;
  optional string soldToCustomer = 4;
  optional string targetSystem = 5;
}
</code></pre><!--kg-card-end: html--><p>Use the Confluent Control Center to set the schema for the topic</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/06/confluentcontrolcenter_set_scheme_protobuf.png" class="kg-image" alt="Using Kafka REST Proxy with Protobuf"><figcaption>Confluent Control Center</figcaption></figure><h1 id="produce-message">Produce Message</h1><p>We will send messages to this Kafka topic “salesorder_topic”.</p><p>The endpoint</p><!--kg-card-begin: html--><pre class="lang-js"><code>POST /topic/salesorder_topic</code></pre><!--kg-card-end: html--><p>Content-Type</p><p>We use Protobuf schema and version 2 of the API. And this is the Content-Type header.</p><!--kg-card-begin: html--><pre class="lang-js"><code>application/vnd.kafka.protobuf.v2+json</code></pre><!--kg-card-end: html--><p>Before making the HTTP request to send a message, we need the schema ID that we set to the topic from the previous step.</p><p>Make a request to the Schema Registry:</p><!--kg-card-begin: html--><pre class="lang-js"><code>curl --location --request GET 'http://{SchemaRegistryHost}:{SchemaRegistryPort}/subjects/salesorder_topic-value/versions/1'</code></pre><!--kg-card-end: html--><p>Response: we got the schema ID 41</p><!--kg-card-begin: html--><pre class="lang-json"><code>{
    "subject": "salesorder_topic-value",
    "version": 1,
    "id": 41,
    "schemaType": "PROTOBUF",
    "schema": "\nmessage value_salesorder_topic {\n  required uint32 orderId = 1;\n  optional string orderType = 2;\n  required uint32 plant = 3;\n  optional string soldToCustomer = 4;\n  optional string targetSystem = 5;\n}\n"
}
</code></pre><!--kg-card-end: html--><p>Put altogether we have this request to send a message:</p><!--kg-card-begin: html--><pre class="lang-js"><code>curl --location --request POST 'http://{RestProxyHost}:{RestProxyPort}/topics/salesorder_topic' \
--header 'Content-Type: application/vnd.kafka.protobuf.v2+json' \
--data-raw '{
  "key_schema": null,
  "value_schema_id": 41,
  "records": [
    {
      "key": null,
      "value": {
      	"orderId": 1,
      	"orderType": "SERVICE",
      	"plant": 5000,
      	"soldToCustomer": "2000",
      	"targetSystem": "SYS1"
      }
    }
  ]
}'
</code></pre><!--kg-card-end: html--><p>Noticed the host and port to send requests are different for the REST Proxy and the Schema Registry.</p><h1 id="consume-message">Consume Message</h1><p>It is not a single endpoint request as the producer. It has to follow steps in order to consume messages from the Kafka topic.</p><p>1.       Create a consumer instance</p><p>2.       Subscribe to a topic or list of topics.</p><p>3.       Consume messages</p><h3 id="create-a-consumer-instance">Create a consumer instance</h3><p>This is a stateful request, the consumer instance is removed after 5 minutes idle, and the output of “base_uri” is used for the next steps.</p><p>The endpoint</p><!--kg-card-begin: html--><pre class="lang-js"><code>POST / consumers /{group name}</code></pre><!--kg-card-end: html--><p>Request</p><!--kg-card-begin: html--><pre class="lang-js"><code>curl --location --request POST 'http://{RestProxyHost}:{RestProxyPort}/consumers/consumergroup' \
--header 'Content-Type: application/vnd.kafka.v2+json' \
--data-raw '{
  "name": "my_consumer_proto",
  "format": "protobuf"
}'
</code></pre><!--kg-card-end: html--><p>Notice the format value of “protobuf” is required as we are using Protobuf schema. The value is “avro” and “jsonschema” for Avro and JSON schema accordingly.</p><p>Example Response</p><!--kg-card-begin: html--><pre class="lang-js"><code>{
    "instance_id": "my_consumer_proto",
    "base_uri": "http://kafkaproxy.sample/consumers/consumergroup/instances/my_consumer_proto"
}
</code></pre><!--kg-card-end: html--><h3 id="subscribe-to-a-topic">Subscribe to a topic</h3><p>The endpoint</p><!--kg-card-begin: html--><pre class="lang-js"><code>{value fo base_uri from the previous step}/subscription</code></pre><!--kg-card-end: html--><p>Request</p><!--kg-card-begin: html--><pre class="lang-js"><code>curl --location --request POST 'http://kafkaproxy.sample/consumers/consumergroup/instances/my_consumer_proto/subscription' \
--header 'Content-Type: application/vnd.kafka.protobuf.v2+json' \
--data-raw '{
  "topics": [
    "salesorder_topic"
  ]
}'
</code></pre><!--kg-card-end: html--><p>Notice at Content-Type and topic name, this is topic with Protobuf schema that we want to consume messages.</p><h3 id="consume-messages">Consume messages</h3><p>The endpoint</p><!--kg-card-begin: html--><pre class="lang-js"><code>{value fo base_uri from the previous step}/records</code></pre><!--kg-card-end: html--><p>Accept header</p><!--kg-card-begin: html--><pre class="lang-js"><code>Accept: application/vnd.kafka.protobuf.v2+json</code></pre><!--kg-card-end: html--><p>Request</p><!--kg-card-begin: html--><pre class="lang-js"><code>curl --location --request GET 'http://kafkaproxy.sample/consumers/consumergroup/instances/my_consumer_proto/records?timeout=3000&max_bytes=300000' \
--header 'Accept: application/vnd.kafka.protobuf.v2+json'
</code></pre><!--kg-card-end: html--><p>Notice at Accept header, when we created a group instance from the previous step with the format “protobuf” then we have to specify which content type we will accept for the response message.</p><p>Example Response</p><!--kg-card-begin: html--><pre class="lang-json"><code>[
    {
        "topic": "salesorder_topic",
        "key": null,
        "value": {
            "orderId": 6,
            "orderType": "SERVICE",
            "plant": 5000,
            "soldToCustomer": "2000",
            "targetSystem": "SYS1"
        },
        "partition": 0,
        "offset": 1
    }
]
</code></pre><!--kg-card-end: html--><h1 id="conclusion">Conclusion</h1><p>The REST Proxy API is a wrapper from the Java library and it is useful to test sending and receiving messages from Kafka. Still, it is not easy to use as we see it takes a couple of steps to receive messages and the group instance is expired every 5 minutes.</p><p>The same steps we can use for other content types such as Avro, JSON schema, all we need is to replace content type appropriately.</p><h1 id="references">References</h1><p>Confluent REST Proxy API reference<br><a href="https://docs.confluent.io/current/kafka-rest/api.html">https://docs.confluent.io/current/kafka-rest/api.html</a></p>]]></content:encoded></item><item><title><![CDATA[Integration with Graph Database Neo4j]]></title><description><![CDATA[There are many databases on the market, but most of them are categorized into three data models relational, document, and graph. Data models and characteristics of the application to store and retrieve data are the factors to choose databases, and we do not compare which one is better.]]></description><link>https://quachtd.com/integration-with-graph-database-neo4j/</link><guid isPermaLink="false">5e8787e6f5908a2392d57346</guid><category><![CDATA[Neo4j]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Fri, 03 Apr 2020 19:50:47 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/04/2.neo4j_related_products-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/04/2.neo4j_related_products-1.png" alt="Integration with Graph Database Neo4j"><p>There are many databases on the market, but most of them are categorized into three data models relational, document, and graph. Data models and characteristics of the application to store and retrieve data are the factors to choose databases, and we do not compare which one is better. Very clear that the document model is the best for use cases where data is self-contained documents and no relationships of many-to-one or many-to-many between documents. The graph model is the opposite, and it targets to use cases where the data relationship is more complicated.</p><p>Neo4j is the most popular graph database now. In this blog, we use it to implement two functionalities as the diagram below.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/1.integraion_diagram-2.png" class="kg-image" alt="Integration with Graph Database Neo4j"></figure><p>-          Find related products: like most of the e-commerce websites, whenever a user views a product, he will see a list of related products that tempt him to buy more. The logic to find related products is different from each website, and here we are going to find products that were sold together with this product in orders that bought by any customers.</p><p>-          Create sales order: when a user makes an order, the e-commerce website sends it to the back office to process, then the sales order may be forwarded to another app to handle. In this case, we will send the sales order to the Neo4j database for storage and doing predictable tasks such as “find the related products.”</p><p>We use Advantco Neo4j Adapter to connect with Neo4j database. It supports native Cypher statements, which can store and retrieve data directly from the database, and it also uses their owner schema on top of Neo4j API to store and retrieve data. We will explore these two features throughout this implementation.</p><h1 id="find-related-products">Find related products</h1><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/2.neo4j_related_products.png" class="kg-image" alt="Integration with Graph Database Neo4j"></figure><p>We find products that were ordered together with this product in the past orders to recommend to customers.</p><p>The interface provides a REST API endpoint for the e-commerce website to look up the related products based on the viewing product. We won’t detail the REST channel configuration here but will express the request body later.</p><p>This interface we use the native Cypher statement feature of the Advantco Neo4j adapter, so no need for any message mapping. All we need is a Cypher query statement to get the data we want. The consumer of the REST API will send out the Cypher query as the request payload, and the adapter sends it to the Neo4j database and responds the result back to the consumer.</p><p>The simple channel configuration to work with Neoj4 database</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/3.channel_native_cypher.png" class="kg-image" alt="Integration with Graph Database Neo4j"></figure><p>There is no specific operation such as storage or retrieval in the channel configuration, only the connectivity information. Notice that we use the message protocol of “Native Neo4j Cypher Statement”, so the Cypher query that sent from the consumer will be wrapped to the appropriate Neo4j API, and the adapter takes care of this step.</p><p>There are other authentication methods from the basic one, such as Kerberos, LDAP, and Custom.</p><p>We can use any REST adapter or HTTP adapter to expose an endpoint API with the HTTP POST method as below.</p><!--kg-card-begin: html--><pre><code class="lang-js">https://server:port/AdvantcoRESTAdapter/RESTServlet/neo4j/api/v1/cypherquery</code></pre><!--kg-card-end: html--><p>The Cypher query to get related products. We can use any as long as it returns the expected result based on our data model. In this case, we are viewing a product with ID 4 on the app, and the app looks for products that related to the product with ID 4.</p><!--kg-card-begin: html--><pre><code class="lang-js">match(p:Product {productID:"4"})<-[:orders]-(o:order), (o)-[:orders]->(rp:Product) where rp.categoryID = p.categoryID return rp</-[:orders]-(o:order),></code></pre><!--kg-card-end: html--><p>The sample of using the exposed API with CURL. It simulates the consumer which is the e-commer website. We just past the Cypher query as the request body.</p><!--kg-card-begin: html--><pre><code class="lang-js">curl --location --request POST 'server:port/AdvantcoRESTAdapter/RESTServlet/neo4j/api/v1/cypherquery' \
    --header 'Content-Type: application/json' \
    --header 'Content-Type: text/plain' \
    --data-raw 'match(p:Product {productID:"4"})<-[:orders]-(o:order), (o)-[:orders]->(pp:Product) where pp.categoryID = p.categoryID return pp'
</-[:orders]-(o:order),></code></pre><!--kg-card-end: html--><h1 id="create-sales-order">Create Sales order</h1><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/04/4.neo4j_sales_order.png" class="kg-image" alt="Integration with Graph Database Neo4j"><figcaption><em>RED is a customer who made the order, ORANGE is an order, and BLUE are products that the customer ordered.</em></figcaption></figure><p>We do not focus on how the e-commerce website sends the order to the back-office, but we will go through the implementation that the back-office sends the sales order to the Neo4j database.</p><p>The previous interface of finding related products we don’t use message mapping by using the native Cypher query, we can see how fast to implement an interface with that way. But within this interface, we create a sales order from an IDOC, so we need to transform an IDOC structure to a structure that could accept by the Neo4j database, it is where we use the Advantco Schema on top of Neo4j API.</p><p>Let say we have a data model as below. We will use the Advantco Workbench to generate the schema needed for the mapping.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/04/5.data_model.png" class="kg-image" alt="Integration with Graph Database Neo4j"><figcaption><i>Customer, Order, Product are nodes. PURCHASED and ORDERS are edges, it indicates who made the order and the details of products in the order.</i></figcaption></figure><p>Base on the data model, we will create a new Order node and two edges (data relationships) of PURCHASED and ORDERS.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2020/04/6.workbench.png" class="kg-image" alt="Integration with Graph Database Neo4j"><figcaption><em>Advantco Workbench</em></figcaption></figure><blockquote><em>If you are from the data model perspective, you may ask why we need a schema for a schema-free database. It’s because we need the source and target data structure to do the data transformation in the middleware. In case we need more dynamic on data structure, we can refer to the way we implement “Find related products” to use native Cypher statements.</em></blockquote><p>The sample mapping from IDOC to the schema</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/7.message_mapping.png" class="kg-image" alt="Integration with Graph Database Neo4j"></figure><p>The payload looks like after the mapping</p><!--kg-card-begin: html--><pre><code class="lang-xml">&#x3C;?xml version=&#x22;1.0&#x22; encoding=&#x22;UTF-8&#x22;?&#x3E;
&#x3C;multiRequest&#x3E;
   &#x3C;request&#x3E;
      &#x3C;operation&#x3E;MERGE&#x3C;/operation&#x3E;
      &#x3C;Order&#x3E;
         &#x3C;ID&#x3E;orderID&#x3C;/ID&#x3E;
         &#x3C;LABEL&#x3E;Order&#x3C;/LABEL&#x3E;
         &#x3C;propertiesToReturn&#x3E;id&#x3C;/propertiesToReturn&#x3E;
         &#x3C;properties&#x3E;
            &#x3C;customerID type=&#x22;string&#x22;&#x3E;match (c:Customer {customerID: &#x26;quot;LETSS&#x26;quot;}) return c.customerID&#x3C;/customerID&#x3E;
            &#x3C;orderID type=&#x22;string&#x22;&#x3E;0003365889&#x3C;/orderID&#x3E;
            &#x3C;orderDate type=&#x22;string&#x22;&#x3E;2020-03-24&#x3C;/orderDate&#x3E;
         &#x3C;/properties&#x3E;
      &#x3C;/Order&#x3E;
      &#x3C;PURCHASED&#x3E;
         &#x3C;START_ID&#x3E;match (c:Customer {customerID: &#x26;quot;LETSS&#x26;quot;}) return c&#x3C;/START_ID&#x3E;
         &#x3C;END_ID&#x3E;0003365889&#x3C;/END_ID&#x3E;
         &#x3C;TYPE&#x3E;PURCHASED&#x3C;/TYPE&#x3E;
      &#x3C;/PURCHASED&#x3E;
      &#x3C;ORDERS&#x3E;
         &#x3C;START_ID&#x3E;0003365889&#x3C;/START_ID&#x3E;
         &#x3C;END_ID&#x3E;match (p:Product) where p.productID in [&#x26;quot;1&#x26;quot;] return p&#x3C;/END_ID&#x3E;
         &#x3C;TYPE&#x3E;ORDERS&#x3C;/TYPE&#x3E;
         &#x3C;properties&#x3E;
            &#x3C;unitPrice type=&#x22;double&#x22;&#x3E;1719.0010&#x3C;/unitPrice&#x3E;
            &#x3C;productID type=&#x22;string&#x22;&#x3E;1&#x3C;/productID&#x3E;
            &#x3C;quantity type=&#x22;int&#x22;&#x3E;10&#x3C;/quantity&#x3E;
         &#x3C;/properties&#x3E;
      &#x3C;/ORDERS&#x3E;
      &#x3C;ORDERS&#x3E;
         &#x3C;START_ID&#x3E;0003365889&#x3C;/START_ID&#x3E;
         &#x3C;END_ID&#x3E;match (p:Product) where p.productID in [&#x26;quot;2&#x26;quot;] return p&#x3C;/END_ID&#x3E;
         &#x3C;TYPE&#x3E;ORDERS&#x3C;/TYPE&#x3E;
         &#x3C;properties&#x3E;
            &#x3C;unitPrice type=&#x22;double&#x22;&#x3E;020&#x3C;/unitPrice&#x3E;
            &#x3C;productID type=&#x22;string&#x22;&#x3E;2&#x3C;/productID&#x3E;
            &#x3C;quantity type=&#x22;int&#x22;&#x3E;20&#x3C;/quantity&#x3E;
         &#x3C;/properties&#x3E;
      &#x3C;/ORDERS&#x3E;
      &#x3C;ORDERS&#x3E;
         &#x3C;START_ID&#x3E;0003365889&#x3C;/START_ID&#x3E;
         &#x3C;END_ID&#x3E;match (p:Product) where p.productID in [&#x26;quot;3&#x26;quot;] return p&#x3C;/END_ID&#x3E;
         &#x3C;TYPE&#x3E;ORDERS&#x3C;/TYPE&#x3E;
         &#x3C;properties&#x3E;
            &#x3C;unitPrice type=&#x22;double&#x22;&#x3E;0.30&#x3C;/unitPrice&#x3E;
            &#x3C;productID type=&#x22;string&#x22;&#x3E;3&#x3C;/productID&#x3E;
            &#x3C;quantity type=&#x22;int&#x22;&#x3E;30&#x3C;/quantity&#x3E;
         &#x3C;/properties&#x3E;
      &#x3C;/ORDERS&#x3E;
      &#x3C;ORDERS&#x3E;
         &#x3C;START_ID&#x3E;0003365889&#x3C;/START_ID&#x3E;
         &#x3C;END_ID&#x3E;match (p:Product) where p.productID in [&#x26;quot;4&#x26;quot;] return p&#x3C;/END_ID&#x3E;
         &#x3C;TYPE&#x3E;ORDERS&#x3C;/TYPE&#x3E;
         &#x3C;properties&#x3E;
            &#x3C;unitPrice type=&#x22;double&#x22;&#x3E;0.40&#x3C;/unitPrice&#x3E;
            &#x3C;productID type=&#x22;string&#x22;&#x3E;4&#x3C;/productID&#x3E;
            &#x3C;quantity type=&#x22;int&#x22;&#x3E;40&#x3C;/quantity&#x3E;
         &#x3C;/properties&#x3E;
      &#x3C;/ORDERS&#x3E;
   &#x3C;/request&#x3E;
&#x3C;/multiRequest&#x3E;
</code></pre><!--kg-card-end: html--><p>The channel configuration is not much different from the previous except the message protocols of Neo4j. The payload itself tells what to do (CREATE/UPDATE/DELETE/QUERY) on which nodes and edges, no need other specify on the channel configuration unless we want to customize different settings.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/8.channel_with_payload.png" class="kg-image" alt="Integration with Graph Database Neo4j"></figure><p>Note that we are using XML payload here, but the adapter also supports other payload types such as CSV and JSON.</p><h1 id="conclusion">Conclusion</h1><p>It’s fast and easy to fulfill the requirement of integration with the Neo4j database on the SAP platform with the Advantco adapter.</p><p>From the integration perspective, it’s good to know the Cypher statement to either using native Cypher or using Advantco Schema to manipulate data through the adapter.</p><h1 id="references">References</h1><p>Cypher statement<br><a href="https://neo4j.com/docs/cypher-manual/current/introduction/">https://neo4j.com/docs/cypher-manual/current/introduction/</a></p><p>Free Online Neo4j sandbox to play with graph database<br><a href="https://neo4j.com/sandbox/">https://neo4j.com/sandbox/</a></p><p>Advantco Neo4j adapter<br><a href="https://advantco.com/product/adapter/neo4j">https://advantco.com/product/adapter/neo4j</a></p>]]></content:encoded></item><item><title><![CDATA[A detail look at the Salesforce Composite API]]></title><description><![CDATA[Doing data integration with Salesforce is not simple. We have to understand API objects which are analogous to database tables and KSQL to extract data from Salesforce, and different types of API set and when to use appropriately, and the concept of External Id… and limit of API calls.]]></description><link>https://quachtd.com/a-detail-look-at-the-salesforce-composite-api/</link><guid isPermaLink="false">5e877af0f5908a2392d57309</guid><category><![CDATA[Salesforce]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Fri, 03 Apr 2020 18:31:04 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/04/salesforce-logo.png" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/1.AdvantcoAdapterIntegrationDiagram-2.png" class="kg-image" alt="A detail look at the Salesforce Composite API"></figure><img src="https://quachtd.com/content/images/2020/04/salesforce-logo.png" alt="A detail look at the Salesforce Composite API"><p>Doing data integration with Salesforce is not simple. We have to understand API objects which are analogous to database tables and KSQL to extract data from Salesforce, and different types of API set and when to use appropriately, and the concept of External Id… and limit of API calls.</p><p>With the Advantco Salesforce Adapter, we simplify the connectivity and transparently consume different API sets, so it makes the implementation more straightforward and faster.</p><h1 id="the-problem">The problem</h1><p>Trying to make multiple API calls for a “simple” task and get exceeded API request limit?</p><p>Let examine an example of the sales order. SAP sends an sales order with a header and two order lines. It is not a simple example if we don’t want to say it’s a challenge in integration without adapter support.</p><p>We manually take care of the access token for every subsequence request calls to Salesforce by using Netweaver BPM or Java Mapping, and manually create mapping schema for each Salesforce object for REST/SOAP API calls…</p><p>To create an order header and several order lines, we need at least two requests to Salesforce. The first is to create the order header. In the second request, it looks up the created order header and then creates order lines. So we have to make sure these two requests execute in the right sequence, no other way.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/2.SeparateMapping.png" class="kg-image" alt="A detail look at the Salesforce Composite API"></figure><p>The consequence of this restriction makes orders queue up when one of them failed in transmission to Salesforce.</p><p>In high throughput time, we may get the error of exceeding the API request limit if the number of requests is not managed well.</p><h1 id="the-solution">The solution</h1><p>With the Advantco Salesforce adapter, the adapter supports session reuse to reduce the number of login call to Salesforce. So we maintain the connection and make a subsequent request in a single channel. It makes the integration flow clean, neither BPM needed nor an extra Java mapping to maintain the access token.</p><p>The choice of different types of API set is not a problem now. The adapter will do it. It’ll automatically select the appropriate API according to the feature we are using.</p><p>The schema is generated from the Advantco Salesforce Workbench, we don’t cover it in this topic, but it provides a ton of features such as Schema generation, SOQL editor, APEX client generator, Outbound Messaging (OBM)…</p><p>The implementation is faster and easier. But the queue up issue still.</p><p>You may know the Composite API. It helps to improve the performance by reducing round trip requests to Salesforce and minimize the number of API calls by batching up multiple calls to a single one.</p><p>With the Composite API support, we combine order header and order lines to a single request. It solves the problem. We don’t have to maintain the sequence of separate requests as earlier, so no more queue up and the transaction of each order is free from the other.</p><p>More important, it’s a lot easier to have a consistent transaction with the Composite request. Each batch of the Composite request can have up to 25 subrequests, and the batch is rolled back when an error occurs while processing a subrequest. It’s an optional configuration from the adapter.</p><p>The channel configuration using the Composite API.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/3.ChannelConfigWithComposite.png" class="kg-image" alt="A detail look at the Salesforce Composite API"></figure><p>The sample payload of the Composite request. The only difference from the payload of another API set is the field “referenceId”.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/4.SamplePayloadOfComposite.png" class="kg-image" alt="A detail look at the Salesforce Composite API"></figure><p><em>Look up the order header Id with the syntax “@{refQueryOrder.records[0].Id}”</em></p><p>The sample of message mapping.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/5.MessageMapping.png" class="kg-image" alt="A detail look at the Salesforce Composite API"></figure><h1 id="conclusion">Conclusion</h1><p>So it’s straightforward to implement an integration scenario of sales order with the Composite API and the Advantco Salesforce Adapter.</p><p>In the real scenario, we may need to delete unused order lines along with adding new order lines and updating the existing lines. The sample payload we are using the operation UPSERT it means it already takes care of adding and updating existing, we need to add another subrequest of OrderDetail with an operation DELETE to delete unused order lines.</p><h1 id="references">References</h1><p>Advantco Salesforce Adapter<br><a href="https://advantco.com/product/adapter/sfdc">https://advantco.com/product/adapter/sfdc</a></p><p>Salesforce Composite Resource<br><a href="https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite.htm">https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite.htm</a></p>]]></content:encoded></item><item><title><![CDATA[Kafka adapter with Avro serialization and Schema Registry]]></title><description><![CDATA[Confluent Schema Registry stores Avro schemas for Kafka producer and consumer so that producers write data with a schema that can be read by consumers even as producers and consumers evolve their using schema.]]></description><link>https://quachtd.com/kafka-adapter-with-avro-serialization-and-schema-registry/</link><guid isPermaLink="false">5e56dff5d8032b52d20da9c3</guid><category><![CDATA[Kafka]]></category><category><![CDATA[Avro]]></category><category><![CDATA[Schema Registry]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Wed, 26 Feb 2020 21:30:05 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/04/kafka_avro-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/04/kafka_avro-2.png" alt="Kafka adapter with Avro serialization and Schema Registry"><p>Confluent Schema Registry stores Avro schemas for Kafka producer and consumer so that producers write data with a schema that can be read by consumers even as producers and consumers evolve their using schema.</p><p>In this post, I am not going to explain what and how those tech terms work in theory. Instead, we will focus on the practical implementation in SAP PO. It’s almost identical to the cloud version of SAP CPI.</p><p>Our scenario is every time SAP sends a customer master (DEBMAS) to a third party it will send a copy to Kafka for real-time analytics or act as a middle stream for other apps. We use the Advantco Kafka adapter here.</p><h1 id="schema-evolution">Schema Evolution</h1><p>To get up to speed in case you are not familiar with this subject, read the following paragraphs from the Confluent website to understand Avro schema and Confluent Schema Registry.</p><p>“An important aspect of data management is schema evolution. After the initial schema is defined, applications may need to evolve it over time. When this happens, it’s critical for the downstream consumers to be able to handle data encoded with both the old and the new schema seamlessly...</p><p>When using Avro, one of the most important things is to manage its schemas and consider how those schemas should evolve. Confluent Schema Registry is built for exactly that purpose.”</p><h1 id="prerequisites">Prerequisites</h1><p>Let start by creating Kafka stuff before jumping on the channel configuration.</p><p>Create a Kafka topic as the command below, or use any UI tool such as Confluent Control Center to create one.</p><!--kg-card-begin: html--><pre class="lang-js"><code>./bin/kafka-topics --create --zookeeper localhost:2181   --replication-factor 1 --partitions 1 --topic debmas07_avro</code></pre><!--kg-card-end: html--><p>Assume we have an Avro schema ready. If not we can use any online tools to generate one from an XML of the IDOC DEBMAS07 and make some changes if needed. To quickly have an Avro schema for this sample, I just simply use the Advantco Kafka Workbench to convert the XML payload to JSON and then use <a href="https://toolslick.com/generation/metadata/avro-schema-from-json">this online tool</a> to generate an Arvo schema from the JSON.</p><p>Use Schema Registry API to upload the Avro schema to the Schema Registry, with a subject name debmas07_avro-value. Or any UI tool such as Advantco Kafka Workbench.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/02/1AdvantcoKafkaWorkbench.png" class="kg-image" alt="Kafka adapter with Avro serialization and Schema Registry"></figure><p>Create a stream from the created topic and schema. It’ll be used later for the real-time queries (KSQL). Run the command below after logging into the KSQL server.</p><!--kg-card-begin: html--><pre class="lang-js"><code>CREATE STREAM sdebmas07_avro \
(KUNNR VARCHAR, \
NAME1 VARCHAR, \
STRAS VARCHAR, \
ORT01 VARCHAR, \
LAND1 VARCHAR) \
WITH (kafka_topic='debmas07_avro', value_format='AVRO', value_avro_schema_full_name='com.company.avro.Debmas07');</code></pre><!--kg-card-end: html--><h1 id="producer-channel">Producer channel</h1><p>Assume we have an ICO that SAP sends an IDOC DEBMAS07 to a receiver channel of Kafka adapter.</p><p>The configuration of the receiver channel to produce messages to the Kafka topic. All messages will be converted to JSON and then serialize to Avro before sending it to Kafka broker. The Avro schema is stored on the Confluent Schema Registry and referencing to a schema by subject name and version.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/02/2ChannelReceiver.png" class="kg-image" alt="Kafka adapter with Avro serialization and Schema Registry"></figure><p>Convert the XML payload to JSON format and store the only segment of E1KNA1M.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/02/3ConverterXML2JSON.png" class="kg-image" alt="Kafka adapter with Avro serialization and Schema Registry"></figure><h1 id="consumer-channel">Consumer channel</h1><p>Assume we have another ICO that consumes Kafka messages from the Kafka sender adapter and forward it to a receiver adapter, such as File.</p><p>Versions of Arvo schema can be the same or different on the sender and receiver channels. It depends on the Compatibility Type using in the Confluent Schema Registry. Here we use the BACKWARD compatibility type and the new schema version (version 2) to consume messages while the producer is still using the last version (version 1).</p><p>Let make a new version of the schema via the Advantco Kafka Workbench by edit the current version – delete field SEGMENT as the screenshot below and save it. So we have a new version 2 is different from the version 1 only field SEGMENT.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/02/3.2AvroSchemaVersion2.png" class="kg-image" alt="Kafka adapter with Avro serialization and Schema Registry"></figure><p>The Kafka sender channel consumes messages from the Kafka topic, it deserializes the message payload from the Avro schema which was used to serialize the message but in a new version.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/02/4ChannelSender.png" class="kg-image" alt="Kafka adapter with Avro serialization and Schema Registry"></figure><p>So if we look at the output data of the interface we will not see field “SEGMENT” according to version 2 of the schema.</p><h1 id="ksql-channel">KSQL channel</h1><p>KSQL is a stream engine that running SQL statements for Kafka. With powerful provided from KSQL, we easily query data in real-time.</p><p>In this example, we are querying customer who is from the US.</p><p>Assume we have another ICO that the Kafka sender adapter executes a KSQL query and a File adapter to receive the output.</p><p>We already have a stream data pipeline created above, and this is the channel configuration.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/02/5ChannelKSQL.png" class="kg-image" alt="Kafka adapter with Avro serialization and Schema Registry"></figure><p>KSQL query.</p><!--kg-card-begin: html--><pre class="lang-js"><code>SELECT KUNNR   as ID, NAME1 as Name, STRAS as BillingStreet, ORT01 as BillingCity, LAND1 as   BillingCountry FROM sdebmas07_avro WHERE LAND1 = 'US' EMIT CHANGES;</code></pre><!--kg-card-end: html--><p>We don’t see any Arvo configuration on the channel because the stream sdebmas07_avro (from FROM clause) was created by reading Avro-formatted data.</p><h1 id="conclusion">Conclusion</h1><p>Avro is a cross-languages serialization of data using a schema. It’s widely used in Kafka to serialize data between apps that developed in different platforms. And with the support from the adapter and the Confluent Schema Registry, we don’t have to write any single line of code to exchange data with other apps, as well as a central place to maintain the schema that developed by a team and reusing by other.</p><p>The Advantco Kafka adapter is supporting in two SAP platforms on-prems (PI/PO) and cloud (CPI). There is no difference in functionality.</p><h1 id="references">References</h1><p>Schema Evolution and Compatibility<br><a href="https://docs.confluent.io/current/schema-registry/avro.html">https://docs.confluent.io/current/schema-registry/avro.html</a></p><p>Advantco Kafka Adapter<br><a href="https://www.advantco.com/product/adapter/apache-kafka-adapter-for-sap-netweaver-pipo">https://www.advantco.com/product/adapter/apache-kafka-adapter-for-sap-netweaver-pipo</a></p><p>Apache Avro<br><a href="https://avro.apache.org/docs/current/">https://avro.apache.org/docs/current/</a></p>]]></content:encoded></item><item><title><![CDATA[2-way SSL (mutual authentication) with Kafka]]></title><description><![CDATA[In this post, we are going to use 2 different clients to connect the Kafka broker with 2-way SSL. We will use Advantco Kafka adapter as well as Kafka console to produce and consume messages from the broker.]]></description><link>https://quachtd.com/2-way-ssl-mutual-authentication-with-kafka/</link><guid isPermaLink="false">5d8c0f3cd8032b52d20da90b</guid><category><![CDATA[Kafka]]></category><category><![CDATA[2-way SSL]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Thu, 26 Sep 2019 20:30:00 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/04/post_image-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/04/post_image-2.png" alt="2-way SSL (mutual authentication) with Kafka"><p>In this post, we are going to use 2 different clients to connect the Kafka broker with 2-way SSL. We will use Advantco Kafka adapter as well as Kafka console to produce and consume messages from the broker.</p><p>With 2-way SSL, here is how a client requests a resource in an encrypted channel:</p><p>1.       A client requests a protected topic from the broker<br>2.       The broker presents its identity by a certificate<br>3.       The client verifies the broker certificate<br>4.       In the other way, the client also sends its certificate to the broker for the verification<br>5.       The broker verifies the client certificate<br>6.       If success, the broker grants access to the topic to the client</p><p>In this post we just focus on how to setup on client side, and we assume the broker is set up with SSL/TLS and ready to use. We will need this information from the broker for the connectivity:</p><p>a.       The port that the broker listening for SSL (SSL://{port} or SASL_SSL://{port}).<br>b.       The certificate for the server authentication. It could be exported from the truststore (server.truststore.jks) or the keystore (server.keystore.jks). Let say a cluster with multiple brokers and we have a different keystore for each broker, but the CA Root certificate to sign the CSR for each keystore/broker is the same. Let name it as <em>caroot_godaddy.crt</em> for reference later.<br>c.       The client authentication flag by setting <em>ssl.client.auth</em> has to be “requested” or “required”.</p><p>We are using terms “keystore” and “truststore” frequently throughout the article. Let explain it first, “keystore” is a repository/file that stores private-public keys and certificates, and “truststore” is a repository/file that stores only certificates.</p><p>Now we have the certificate from the broker (<em>caroot_godaddy.crt</em>) for the server authentication (aka 1-way SSL). For the client authentication (second way SSL), we need a keystore which includes public-private keys and a signed certificate (self-signed or from a trusted CA) and a CA Root certificate that signed the certificate.</p><h1 id="create-a-keytstore-and-a-ca-root-certificate">Create a keytstore and a CA Root certificate</h1><p>The CA Root certificate is to sign the certificate signing request (CSR), and we can generate it to sign the certificate (self-signed) or we can export it from a trusted CA.</p><p>Let create a keystore for the client.</p><p>Create a client keystore</p><!--kg-card-begin: html--><pre><code>keytool -genkey -keystore client.keystore.jks -validity 3650 -storepass "password1" -keypass "password1" -dname "CN=client1" -alias client1 -storetype pkcs12
</code></pre><!--kg-card-end: html--><p>Create a client cert sign request (CSR)</p><!--kg-card-begin: html--><pre><code>keytool -keystore client.keystore.jks -certreq -file client-cert-sign-request -alias client1 -storepass "password1" -keypass "password1"</code></pre>
<!--kg-card-end: html--><p>The CSR file could be signed by a trusted CA or by self-signed.</p><p>Here are steps if we want to sign the certificate by ourselves</p><!--kg-card-begin: html--><table class="c-table-noborder">
    <tr>
        <td>&nbsp;</td>
        <td>
            Create CA key and CA cert <br>
            <pre><code>openssl req -new -x509 -keyout ca-key -out ca-cert -days 3650</code></pre>
            Sign the CSR file with the CA key and CA cert <br>
            <pre><code>openssl x509 -req -CA ca-cert -CAkey ca-key -in client-cert-sign-request -out client-cert-signed -days 3650 -CAcreateserial -passin pass:password1</code></pre>
            Create a complete chain certificate. Use any text editor to copy content of file ca-cert and client-cert-signed to a new file called completeChain.crt. Similar with Unix command <br>
            <pre><code>cat ca-cert client-cert-signed > completeChain.crt</code></pre>
        </td>
    </tr>
</table><!--kg-card-end: html--><p>Now we will import the certificate chain to the keystore.</p><!--kg-card-begin: html--><pre><code>keytool -keystore client.keystore.jks -import -file completeChain.crt -alias client1 -storepass "password1" -keypass "password1" -noprompt</code></pre><!--kg-card-end: html--><p>The keystore is ready to use. The last step is to export the CA Root certificate.</p><blockquote>If we signed the certificate by ourselves then the <em>ca-cert</em> is the CA Root certificate.</blockquote><p>We are using KeyStore Explorer to export the CA Root certificate.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/keystore_export_cert1.png" class="kg-image" alt="2-way SSL (mutual authentication) with Kafka"></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://quachtd.com/content/images/2019/09/keystore_export_cert2.png" class="kg-image" alt="2-way SSL (mutual authentication) with Kafka"><figcaption>Name the CA Root certificate to <i>carootsapcpip0520.crt</i></figcaption></figure><h1 id="import-the-ca-root-certificate-to-kafka-broker">Import the CA Root certificate to Kafka broker</h1><p>This step should be performed by Kafka team. We send the CA Root certificate file from the previous step and ask them to import it to the broker truststore.</p><p>Assume the broker truststore file name is <em>server.truststore.jks</em></p><!--kg-card-begin: html--><pre><code>keytool -keystore server.truststore.jks -alias carootsapcpip0520 -import -file carootsapcpip0520.crt -storepass "{password of truststore}" -noprompt</code></pre><!--kg-card-end: html--><blockquote>This step is not always required. Like in a corporation we are using the same trusted CA to sign certificates for multiple system including Kafka brokers, then the CA Root certificate is already in the truststore.</blockquote><h1 id="make-a-connection-with-advantco-kafka-adapter">Make a connection with Advantco Kafka Adapter</h1><p>There are different versions for 2 platforms SAP CPI/HCI and PI/PO, but the adapter configuration is identical.</p><p>The 2-way SSL can be used for none authentication mode as long as other authentication modes such as Kerberos, Plain, SCRAM..</p><p>We have a keystore (<em>client.keystore.jks)</em>and a certificate (<em>caroot_godaddy.crt)</em>from the previous steps, let import it to the Key Storage for referencing in the channel configuration later.</p><p>Java KeyStore (JKS) format of the keystore is not supported by PI/PO Key Storage, we have to convert it to P12 format.</p><p>Convert JKS to P12</p><!--kg-card-begin: html--><pre><code>keytool -importkeystore -srckeystore client.keystore.jks -destkeystore client.keystore.p12 -deststoretype pkcs12</code></pre><!--kg-card-end: html--><p>Key Storage View SAP_KAFKA_CLIENT</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/nw_keystorage_view.png" class="kg-image" alt="2-way SSL (mutual authentication) with Kafka"></figure><blockquote>Similar with SAP CPI/HCI, create 2 entries one for Keystore and one for Certificate. The cloud platform supports JKS, so we don’t have to convert the keystore to P12 format.</blockquote><p>Here is an example of 2-way SSL with Kerberos</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/nw_kafkachannel_configuration.png" class="kg-image" alt="2-way SSL (mutual authentication) with Kafka"></figure><h1 id="test-the-connectivity-with-kafka-console">Test the connectivity with Kafka console</h1><p>The best way to test 2-way SSL is using Kafka console, we don’t have to write any line of code to test it.</p><p>Simply download Kafka from Apache Kafka website to the client, it includes kafka-console-producer and kafka-console-consumer in bin directory. We will use one of it to test the connectivity.</p><p>To use the console we have to create 2 things:</p><p>Wrapper the server certificate (<em>caroot_godaddy.crt)</em> to a client truststore</p><!--kg-card-begin: html--><pre><code>keytool -keystore client.truststore.jks -alias CARoot -import -file caroot_godaddy.crt -storepass "password1" -noprompt</code></pre><!--kg-card-end: html--><p>Create a client property file <em>client-ssl.properties</em> and copy it to kafka bin directory, should have following lines:</p><!--kg-card-begin: html--><pre><code>security.protocol=SSL
ssl.truststore.location=client.truststore.jks
ssl.truststore.password=password1
ssl.endpoint.identification.algorithm=
ssl.keystore.location=client.keystore.jks
ssl.keystore.password=password1
ssl.key.password=password1
</code></pre><!--kg-card-end: html--><p>It’s ready to use the kafka console producer to produce a test message to a topic</p><!--kg-card-begin: html--><pre><code>./bin/kafka-console-producer --broker-list {broker name}:{port} --producer.config client-ssl.properties –topic {topic name}</code></pre><!--kg-card-end: html--><p>We are done. Hope you find it useful.</p>]]></content:encoded></item><item><title><![CDATA[Event-Driven Integration with GCP serverless services]]></title><description><![CDATA[In this post, we will go through a scenario where we use Google Cloud Platform (GCP) serverless services to archive Event-Driven model.]]></description><link>https://quachtd.com/event-driven-integration-with-gcp-serverless-services/</link><guid isPermaLink="false">5d7b91ebd8032b52d20da814</guid><category><![CDATA[SAP Cloud Platform Integration]]></category><category><![CDATA[GCP]]></category><category><![CDATA[Advantco GCP Adapter]]></category><dc:creator><![CDATA[Dai (Bato) Quach]]></dc:creator><pubDate>Fri, 13 Sep 2019 14:58:15 GMT</pubDate><media:content url="https://quachtd.com/content/images/2020/04/post_image.png" medium="image"/><content:encoded><![CDATA[<img src="https://quachtd.com/content/images/2020/04/post_image.png" alt="Event-Driven Integration with GCP serverless services"><p>In this post, we will go through a scenario where we use Google Cloud Platform (GCP) serverless services to archive Event-Driven model.</p><p>The scenario is the Ink Replenishment program, whenever a user buys a printer at a store or online the system will send the user an email to register to the program. If the user decided to opt-in to the program and then the user will automatically receive a suggestion to buy new Ink whenever the ink low.<s> </s>So the user doesn’t have to look up for the Ink code of the printer in order to buy a new one, she just responses back Yes or No to the system to buy a new Ink.</p><p>Once the Yes response sent, the system finds the store who sold the printer based on the serial number and forward the order to the store to fulfill it. We will address this order scenario in the event-driven model in this blog.</p><p>Let says we have a chain of Command and Event as the diagram below.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/EvenDrive_Diagram.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><p>The very first command is from the customer, she responses YES to trigger the order workflow from her computer/Phone, and the last command is to fulfill the order and it will be taken care from the external stores. There are 2 other commands need to implement in the internal system are Package Order and Send Order.</p><p>Before we go to the detail of the implementation, let have a quick explanation about Event the diagram above. We are using Google Cloud Pub/Sub for messaging events, and each event is generated with a specific data object after the command executed. (That is the reason why we don’t see any data object in the diagram except a serial of commands and events)</p><p>An example for the event Order Submitted: once the customer confirmed to order the new Inks, the application on the customer laptop/phone will trigger an event and push it to a topic in Cloud Pub/Sub, and then the event will trigger another command which is subscribing to the topic.</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
	&quot;customerId&quot;: &quot;0000001234&quot;,
	&quot;serial&quot;: &quot;serial number of the printer&quot;,
	&quot;date&quot;: &quot;2019-09-06T14:38:00.000&quot;,
	&quot;items&quot;: [
		{
			&quot;code&quot;: &quot;64&quot;,
			&quot;color&quot;: &quot;Y&quot;,
			&quot;inklevel&quot;: 24
		},
		{
			&quot;code&quot;: &quot;64&quot;,
			&quot;color&quot;: &quot;B&quot;,
			&quot;inklevel&quot;: 10
		}
	]
}
</code></pre>
<!--kg-card-end: markdown--><h2 id="sap-cpi-and-advantco-gcp-adapter">SAP CPI and Advantco GCP Adapter</h2><p>Now it’s time to think about the implementation commands of Package Order and Send Order. We can use Google Cloud Functions to respond to the events from Cloud Pub/Sub to process, transform and enrich data. So, Cloud Functions is a good glue to connect point to point, specifically as a command in our model to consume data from an event and then generate a new event to trigger another command.</p><p>But besides the effort of coding to connect to the back-office such as SAP, Salesforce… which are not in GCP stack. We do have a better and faster option which is SAP CPI, it already has adapters (IDOC, ODATA, SFO…) to connect to those systems.</p><p>Look at the diagram below where Salesforce is the back office and SAP CPI connecting events with help from Advantco GCP adapter to consume and produce event messages from Google Cloud Pub/Sub.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2020/04/post_image-1.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><h2 id="package-order">Package Order</h2><p>The flow of the command Package Order. It makes the flow so much simple with Advantco GCP adapter and SFO (Salesforce) adapter.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/CPI_PackageOrder.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><p><strong>The GCP sender channel</strong>, it does some other stuff beside pulling event messages from Cloud Pub/Sub such as uncompressing GZIP message, converting JSON message to XML. It makes the flow clean and simple.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/GCP_Adapter_SenderChannel.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><p>The connection configuration within the adapter supports Service Account and OAuth2, it can open multiple concurrent connections for a high volume of flowing messages.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/GCP_Adapter_ConnectionConfig.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><p><strong>Content Enricher</strong>is a very cool step from SAP CPI, it helps to combine/enrich two data sources to one. Yes, in a single step without Message Mapping. It’s a reason why we converted the SubmittedOrder from JSON to XML in the GCP sender channel above.</p><p>Source 1 - a submitted order</p><!--kg-card-begin: html--><pre><code class="lang-xml">
&#x3C;?xml version=&#x22;1.0&#x22; encoding=&#x22;UTF-8&#x22;?&#x3E;
&#x3C;SubmitedOrder&#x3E;
&#x9;&#x3C;customerId&#x3E;0000001238&#x3C;/customerId&#x3E;
&#x9;&#x3C;serial&#x3E;1234554321&#x3C;/serial&#x3E;
&#x9;&#x3C;date&#x3E;2019-09-06T14:38:00.000&#x3C;/date&#x3E;
&#x9;&#x3C;items&#x3E;
&#x9;&#x9;&#x3C;item&#x3E;
&#x9;&#x9;&#x9;&#x3C;code&#x3E;64&#x3C;/code&#x3E;
&#x9;&#x9;&#x9;&#x3C;color&#x3E;Y&#x3C;/color&#x3E;
&#x9;&#x9;&#x9;&#x3C;inklevel&#x3E;24&#x3C;/inklevel&#x3E;
&#x9;&#x9;&#x3C;/item&#x3E;
&#x9;&#x9;&#x3C;item&#x3E;
&#x9;&#x9;&#x9;&#x3C;code&#x3E;64&#x3C;/code&#x3E;
&#x9;&#x9;&#x9;&#x3C;color&#x3E;B&#x3C;/color&#x3E;
&#x9;&#x9;&#x9;&#x3C;inklevel&#x3E;10&#x3C;/inklevel&#x3E;
&#x9;&#x9;&#x3C;/item&#x3E;
&#x9;&#x3C;/items&#x3E;
&#x3C;/SubmitedOrder&#x3E;
        	</code></pre><!--kg-card-end: html--><p>Source 2 - customer and store info</p><!--kg-card-begin: html--><pre><code class="lang-xml">
&#x3C;?xml version=&#x22;1.0&#x22; encoding=&#x22;UTF-8&#x22;?&#x3E;
&#x3C;queryResponse&#x3E;
&#x9;&#x3C;Customer__c&#x3E;
&#x9;&#x9;&#x3C;id&#x3E;...&#x3C;/id&#x3E;
&#x9;&#x9;..&#x9;&#x9;
&#x9;&#x9;&#x3C;External_CustID__C&#x3E;0000001238&#x3C;/External_CustID__C&#x3E;
&#x9;&#x9;&#x3C;Store&#x3E;
&#x9;&#x9;&#x9;&#x3C;id&#x3E;...&#x3C;/id&#x3E;
&#x9;&#x9;&#x9;..
&#x9;&#x9;&#x3C;/Store&#x3E;
&#x9;&#x3C;/Customer__c&#x3E;
&#x3C;/queryResponse&#x3E;
            </code></pre><!--kg-card-end: html--><p>Target - the combine of Source 1 and Source 2</p><!--kg-card-begin: html--><pre><code class="lang-xml">
&#x3C;?xml version=&#x22;1.0&#x22; encoding=&#x22;UTF-8&#x22;?&#x3E;
&#x3C;SubmitedOrder&#x3E;
&#x9;&#x3C;customerId&#x3E;0000001238&#x3C;/customerId&#x3E;
&#x9;&#x3C;Customer__c&#x3E;
&#x9;&#x9;&#x3C;id&#x3E;...&#x3C;/id&#x3E;
&#x9;&#x9;..&#x9;&#x9;
&#x9;&#x9;&#x3C;External_CustID__C&#x3E;0000001238&#x3C;/External_CustID__C&#x3E;
&#x9;&#x9;&#x3C;Store&#x3E;
&#x9;&#x9;&#x9;&#x3C;id&#x3E;...&#x3C;/id&#x3E;
&#x9;&#x9;&#x9;..
&#x9;&#x9;&#x3C;/Store&#x3E;
&#x9;&#x3C;/Customer__c&#x3E;
                    &#x3C;serial&#x3E;1234554321&#x3C;/serial&#x3E;
&#x9;&#x3C;date&#x3E;2019-09-06T14:38:00.000&#x3C;/date&#x3E;
&#x9;&#x3C;items&#x3E;
&#x9;&#x9;&#x3C;item&#x3E;
&#x9;&#x9;&#x9;&#x3C;code&#x3E;64&#x3C;/code&#x3E;
&#x9;&#x9;&#x9;&#x3C;color&#x3E;Y&#x3C;/color&#x3E;
&#x9;&#x9;&#x9;&#x3C;inklevel&#x3E;24&#x3C;/inklevel&#x3E;
&#x9;&#x9;&#x3C;/item&#x3E;
&#x9;&#x9;&#x3C;item&#x3E;
&#x9;&#x9;&#x9;&#x3C;code&#x3E;64&#x3C;/code&#x3E;
&#x9;&#x9;&#x9;&#x3C;color&#x3E;B&#x3C;/color&#x3E;
&#x9;&#x9;&#x9;&#x3C;inklevel&#x3E;10&#x3C;/inklevel&#x3E;
&#x9;&#x9;&#x3C;/item&#x3E;
&#x9;&#x3C;/items&#x3E;
&#x3C;/SubmitedOrder&#x3E;
</code></pre><!--kg-card-end: html--><p><strong>The GCP Receiver channel</strong>, it creates a new event after the order packaged by producing an event message to Cloud Pub/Sub.</p><p>As an agreement in the order workflow, all event messages are stored in JSON format and compressed with GZIP. So, the GCP receiver channel will do these things to make it uniform.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/GCP_Adapter_ReceiverChannel.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><h2 id="send-order">Send Order</h2><p>The flow of the command Send Order. Before sending out the order we store it in BigQuery - another serverless service from Google, then we can keep track of the order status as well as analyze and have a statistic in a later time.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/CPI_SendOrder.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><p>Consuming and producing event messages from Cloud Pub/Sub using Advantco GCP adapter is similar steps above, with compress/uncompress GZIP and conversion between XML and JSON.</p><p>Beside the Cloud Pub/Sub the adapter also supports many other services such as BigQuery, Datastore, Spanner, Dataflow, Storage and Google Drive. Here is the list of services with authentication methods supported by the adapter.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/Services_AuthenticationMethods.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><p>We use BigQuery to store the order in the flow.</p><figure class="kg-card kg-image-card"><img src="https://quachtd.com/content/images/2019/09/GCP_Adapter_BigQuery.png" class="kg-image" alt="Event-Driven Integration with GCP serverless services"></figure><h2 id="summary">Summary</h2><p>We did not go much detail on the implementation but we see many benefits from the Google serverless services in the project implementation, no worrying of server management, stability, scalability… we can start the project from day one.</p><p>And the most important is the Advantco GCP adapter played a key role in this project, it speeded up the implementation with the functions it offers.</p><h2 id="references">References</h2><p>Advantco GCP Adapter<br><a href="https://advantco.com/product/adapter/google-cloud-platform-adapter-for-sap-pi-po">https://advantco.com/product/adapter/google-cloud-platform-adapter-for-sap-pi-po</a></p><p>Cloud Pub/Sub<br><a href="https://cloud.google.com/pubsub/">https://cloud.google.com/pubsub/</a></p><p>BigQuery<br><a href="https://cloud.google.com/bigquery/">https://cloud.google.com/bigquery/</a></p>]]></content:encoded></item></channel></rss>