<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><atom:link href="https://www.lambdaschmiede.com" rel="self" type="application/rss+xml"></atom:link><title>lambdaschmiede Blog</title><link>https://www.lambdaschmiede.com</link><description>Ein Blog von lambdaschmiede GmbH</description><managingEditor>tim.zoeller@lambdaschmiede.com</managingEditor><category>Technologie</category><docs>https://cyber.harvard.edu/rss/rss.html</docs><generator>clj-rss</generator><item><title>Lightweight Processes with Camunda and Zeebe</title><author>Tim Zöller (tim.zoeller@lambdaschmiede.com)</author><description>With the transition from Camunda 7 to 8, the process engine transforms from a library that can be integrated into applications to a comprehensive platform with many components. While exploring the recommended setup, we encounter Helm charts and deployments with eight or more components, some of which are only available for enterprise customers in production. This article demonstrates how lightweight process applications can be created using the open-source core component Zeebe.</description><guid isPermaLink="false">lightweight-processes-with-camunda-and-zeebe</guid><link>https://www.lambdaschmiede.com/en/blog/2024-02-19/lightweight-processes-with-camunda-and-zeebe</link><pubDate>Mon, 19 Feb 2024 19:47:32 +0000</pubDate><content:encoded><![CDATA[<h2>A New Architecture</h2><p>It's rare for a new major version of software to bring about such a significant paradigm shift: while the Camunda Platform in version 7 was fully integrable into Java applications, Camunda 8 is now based on Zeebe, an independent component with which our code communicates via gRPC. Instead of representing the state of business processes in a database, the operations of the processes are stored in an event log. The goal of the Camunda developers was to create a process engine that is infinitely scalable horizontally. As a result, Camunda 8 can process a significantly higher throughput than Camunda 7, which could only handle as many requests as its central, relational database.</p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="70228b58-1289-4ef0-89c3-d9ee23ff3241"><div class="result-streaming markdown prose w-full break-words dark:prose-invert dark"><h2>The Complexity of Camunda Platform 8</h2></div></div></div><p>A frequently discussed point about the new platform is that the complexity of an average Camunda installation is higher than it was for the old engine. A productive deployment for Camunda 7 included the engine itself, a relational database, optionally a task list for processing user tasks, and a cockpit for inspecting running processes. Except for the database, we could bundle all these components in a single Java application, which we could deploy using familiar methods.</p><p><img src="https://strapi.lambdaschmiede.com/uploads/Camunda_7_arch_1c0a81183e.png" alt="The architecture diagram of Camunda 7, as described in the previous text" srcset="https://strapi.lambdaschmiede.com/uploads/thumbnail_Camunda_7_arch_1c0a81183e.png 245w,https://strapi.lambdaschmiede.com/uploads/small_Camunda_7_arch_1c0a81183e.png 500w,https://strapi.lambdaschmiede.com/uploads/medium_Camunda_7_arch_1c0a81183e.png 750w," sizes="100vw" width="750px"></p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="53c8bc84-9042-41b9-a3d0-631362965c3f"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>Since the "new" Zeebe engine does not store the state of process instances in a database but is only responsible for orchestrating the associated events and delegating them to job workers who perform the actual processing with code, there is no API to query the state of the process instances. It is up to us to reconstruct the state from the event logs and make it queryable. Tools for this are the so-called exporters, plugins in the Zeebe engine, which transmit the events to Kafka, Redis, Elasticsearch, or other storage technologies where they can be aggregated. This approach is used by Camunda Platform 8 itself: The data used by the task list and the "Cockpit" successor "Operate" are stored in an Elasticsearch repository, which is part of the standard deployment. These components all require maintenance and care, not to mention the consumption of resources.</p><p>Another downside: The graphical interfaces "Tasklist" and "Operate" are no longer under an open-source license and may not be used in production without a license (and thus for free) (although we generally would not recommend operating business-critical open-source components in production without a maintenance contract).</p></div></div></div><p><img src="https://strapi.lambdaschmiede.com/uploads/Camunda_8_arch_8cbf9cec50.png" alt="The architecture diagram of Camunda 8, as described in the previous text" srcset="https://strapi.lambdaschmiede.com/uploads/thumbnail_Camunda_8_arch_8cbf9cec50.png 240w,https://strapi.lambdaschmiede.com/uploads/small_Camunda_8_arch_8cbf9cec50.png 500w,https://strapi.lambdaschmiede.com/uploads/medium_Camunda_8_arch_8cbf9cec50.png 750w," sizes="100vw" width="750px"></p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="8882772d-662a-4d64-be09-0c4d65691137"><div class="markdown prose w-full break-words dark:prose-invert dark"><h2>A Process Engine for Large Corporations?</h2></div></div></div><p>The changes made with the Camunda 7 Community Edition have caused uncertainty among its users. Not everyone has technical requirements for a process engine that justify managing so many components and maintaining a Kubernetes cluster. The alternative use of the in-house SaaS product, Camunda Cloud, might make sense for smaller companies, but here the costs scale with the executed process instances, so a use case should be carefully calculated. The break-even point, at which self-hosting becomes worthwhile, is likely only surpassed with a considerable number of process instances per hour.</p><p>But what about smaller projects that could benefit from process orchestration and the use of BPMN, but would no longer be profitable with the use of the SaaS offering or the operation of more complex infrastructure? In the following example, we will explore this question by building and operating a lightweight process.</p><h2>Example: A Reminder Bot for Social Networks</h2><p>A "Reminder-Bot" is a great example of a lightweight process. It involves an account that is mentioned in a message with the addition "in x days," and after this period, responds with a post as a reminder. As a BPMN process, this could look something like this:</p><p><img src="https://strapi.lambdaschmiede.com/uploads/reminder_process_e490ec3d06.png" alt="A BPMN 2.0 diagram which shows the process which is described in the following text" srcset="https://strapi.lambdaschmiede.com/uploads/thumbnail_reminder_process_e490ec3d06.png 245w,https://strapi.lambdaschmiede.com/uploads/small_reminder_process_e490ec3d06.png 500w,https://strapi.lambdaschmiede.com/uploads/medium_reminder_process_e490ec3d06.png 750w," sizes="100vw" width="750px"></p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="e8a21855-093f-4102-8414-18fcd3eae643"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>From the process model, the desired process flow can be transparently read: We wait for a user to request a reminder and then start the process. First, we try to interpret the message. Can we understand a time period from the text, or not? If this is not the case, we respond with a message to the original post indicating that we could not understand any instructions. However, if we were able to understand, we briefly confirm the set reminder and then wait for the calculated date.</p><p>Now, one of two scenarios can occur: Either the desired date arrives and we write the reminder as requested. Or we are instructed to "cancel" the reminder. In this case, we also confirm this and end the process in a different target status.</p></div></div></div><h2>Operation of the Process Engine</h2><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="5721c7e6-7b86-4160-9e9f-1143beef11dc"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>To illustrate the parameters and required file paths or volumes, we have derived a Docker-Compose file from the Camunda documentation, which contains nothing but Zeebe:</p></div></div></div><pre><code class="language-yaml">services:
  zeebe: # https://docs.camunda.io/docs/self-managed/platform-deployment/docker/#zeebe
    image: camunda/zeebe:8.4.1
    container_name: zeebe
    ports:
      - "26500:26500"
      - "9600:9600"
    environment:
      - ZEEBE_BROKER_DATA_DISKUSAGECOMMANDWATERMARK=0.998
      - ZEEBE_BROKER_DATA_DISKUSAGEREPLICATIONWATERMARK=0.999
      - "JAVA_TOOL_OPTIONS=-Xms512m -Xmx512m"
    restart: always
    healthcheck:
      test: [ "CMD-SHELL", "timeout 10s bash -c ':&gt; /dev/tcp/127.0.0.1/9600' || exit 1" ]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 30s
    volumes:
      - ./zeebe-data:/usr/local/zeebe/data</code></pre><p>For the container, two ports are mapped: 26500 for gRPC communication and 9600 for the startup and ready check from outside. A volume maps the data from <code>/usr/local/zeebe/data</code> to the host folder <code>zeebe-data</code>, where the broker's event log is stored. From the memory configuration <code>-Xmx512m</code>, which defines a maximum heap of 512MB, we can see that the engine does not have high memory requirements. However, for use cases with higher volume, this might not be sufficient. The broker is started with a <code>docker-compose up</code> command.</p><h2>Project Setup with Spring Boot</h2><p>We use Spring Boot for our server-side Java application, which interacts with the Zeebe broker. As a Maven dependency, we add the Camunda Spring Boot Starter, which configures the job workers for us and controls their lifecycle:</p><pre><code class="language-java">&lt;dependency&gt;
    &lt;groupId&gt;io.camunda.spring&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-camunda&lt;/artifactId&gt;
    &lt;version&gt;8.4.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre><p><text x="-9999" y="-9999"></text></p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="e910eafb-a9f6-4d6f-83e3-125efb4926b6"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>In the <code>application.properties</code> file, we define the URL of our Zeebe broker. Since we are in a development environment, we need to disable SSL with the <code>plaintext</code> property - in a production environment, we would recommend setting up a connection over TLS:</p></div></div></div><pre><code class="language-plain">zeebe.client.broker.gateway-address=localhost:26500
zeebe.client.security.plaintext=true</code></pre><p>With the help of this Spring Boot integration, we can also define which process instances should be deployed from the classpath when our application starts. This is done using the <code>@Deployment</code> annotation, which we can add to any configuration classes.</p><pre><code class="language-java">@Configuration
@Deployment(resources = "reminder-process.bpmn")
public class ZeebeConfig {
}</code></pre><p><text x="-9999" y="-9999"></text></p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="5a461010-7ca0-4595-9db8-1a5273d61138"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>In our example, we specify the filename of our BPMN definition directly. However, it's also possible to use wildcards to include, for example, all BPMN files or certain files with a specific naming scheme.</p><h2>Starting the Reminder Process</h2><p>We want to start a new process instance as soon as our account is mentioned in a posting. To interact with the engine, we need an instance of <code>ZeebeClient</code>, which we can receive through dependency injection thanks to the Spring Boot integration. In the example, we define the Java record ProcessInput, which contains the required input variables for starting a process instance.</p></div></div></div><pre><code class="language-java">@Autowired
private final ZeebeClient zeebeClient;

record ProcessInput(String content,
                    String statusId,
                    String visibility,
                    String account) {};

public Long startReminderProcess(Status status) {
   var processInput = new ProcessInput(status.content(), 
                                       status.id(), 
                                       status.visibility(),
                                       status.account().acct());

   var result = zeebeClient
           .newCreateInstanceCommand()
           .bpmnProcessId("Process_Reminder")
           .latestVersion()
           .variables(processInput)
           .send()
           .join();

   return result.getProcessInstanceKey();
}</code></pre><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="b74d2489-8d89-437d-bd62-88809ffdf358"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>Guided by a multi-stage builder, we now create a process instance, which is derived from the process definition with the ID <code>Process_Reminder</code>. We want to start this in the latest deployed version and pass the mapped input variables. The call to <code>send()</code> sends the command asynchronously to the Zeebe Engine via gRPC; with <code>join()</code>, we wait for the successful execution to use the ID of the newly created process instance as the return value of the method.</p><h2>Executing Program Logic with Job Workers</h2><p>For our business process to be more than just a visual diagram and actually orchestrate our application, we need to define a type at service tasks. This type is used by so-called job workers to identify the type of task to be processed.</p></div></div></div><figure class="image"><img src="https://strapi.lambdaschmiede.com/uploads/grafik_9500ddf1aa.png" alt="A service task, selected in the Camunda modeler. In the properties panel, the task definition is set to &quot;parseDate&quot;" srcset="https://strapi.lambdaschmiede.com/uploads/thumbnail_grafik_9500ddf1aa.png 245w, https://strapi.lambdaschmiede.com/uploads/small_grafik_9500ddf1aa.png 500w, https://strapi.lambdaschmiede.com/uploads/medium_grafik_9500ddf1aa.png 750w, https://strapi.lambdaschmiede.com/uploads/large_grafik_9500ddf1aa.png 1000w" sizes="100vw" width="1000"></figure><p><text x="-9999" y="-9999"></text></p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="dfb4598f-bc61-4d5d-815d-0ba01cd50adc"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>In the following example of a Java job worker, we use the <code>@JobWorker</code> annotation from the previously mentioned Spring Boot Starter to establish the connection to the service task. In the background, our application polls the engine via gRPC to determine if there are any jobs to be processed.</p></div></div></div><pre><code class="language-java">public record ParseRequestResponse(boolean dateUnderstood, 
                                   ZonedDateTime reminderDate) {};

@JobWorker(type = "parseDate")
public ParseRequestResponse parseRequest(
	@Variable String content) {
	
    var matcher = PATTERN.matcher(content);
    if(matcher.find()) {
        var result = matcher.group(1);
        return new ParseRequestResponse(true, 
                      				    ZonedDateTime.now()
                                          .plusDays(Long.parseLong(result)));
    } else {
        return new ParseRequestResponse(false, null);
    }
}</code></pre><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="8260eef8-cd92-4338-a4ed-b9a6acd506bf"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>When this is the case, our method is called. The parameter String content listed in the signature is derived from the process variables and passed to the method. We attempt to extract a mention of days using regular expressions from the content and add these days to the current date to determine the date for the reminder. The response to the process engine is returned as the method's return value. In our case, we have defined a local Java record that contains two variables: a <code>boolean</code> value that represents whether the request was understood at all, and a <code>ZonedDateTime</code>, which holds the calculated date of the reminder. The Camunda Spring integration maps each field of the response object to a process variable after the method call, mapping the field names to variable names. Thus, in this example, we introduce two new process variables: <code>dateUnderstood</code> and <code>reminderDate</code>.</p><p>The principle is very similar when directly interacting with posts on the social network. Here too, we register a job worker with the engine, which listens to the assigned task. When this task is reached by the engine within a process instance, the method annotated with <code>@JobWorker</code> is called, and the variables are mapped into the method's parameters.</p></div></div></div><pre><code class="language-java">public record SuccessOutput(String reminderStatusId) {};

@JobWorker(type = "confirmCancellation")
public SuccessOutput confirmCancellation(
						@Variable String statusId,
                        @Variable String visibility,
                        @Variable String account) {
                        
    var message = MESSAGE_TEMPLATE.formatted(account, 
                                             "Na gut, der Timer ist abgebrochen");
    var result = mastodonService.replyToStatus(statusId, 
                                               message, 
                                               visibility);
    return new SuccessOutput(result.id());
}</code></pre><p><text x="-9999" y="-9999"></text></p><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="297e7057-8cfa-425d-bd69-291a0c840dfe"><div class="markdown prose w-full break-words dark:prose-invert dark"><p>This worker receives the information on which status to reply to via <code>statusId</code>, the visibility in which the status was written via <code>visibility</code>, and to which account to reply with <code>account</code>. The status ID is needed to attach the post to the correct predecessor, the account to mention the user in it, and the visibility of the original post to reply in the same visibility - after all, we don't want to respond publicly visible to a private mention. With this information, we assemble the message from the (not listed) message template and send it to a server of the decentralized social network.</p><p>The same applies to the job workers for the other tasks, which are not described in detail here, but are listed for the sake of completeness.</p></div></div></div><pre><code class="language-java">@JobWorker(type = "confirmReminder")
public SuccessOutput confirmReminder(
						@Variable String statusId,
                        @Variable ZonedDateTime reminderDate,
                        @Variable String visibility,
                        @Variable String account) {
    var message = MESSAGE_TEMPLATE.formatted(account, 
                                             "Hey, alles klar! Ich erinnere dich am %s".formatted(reminderDate));
    var result = mastodonService.
    				replyToStatus(statusId, message, visibility);
    return new SuccessOutput(result.id());
}

@JobWorker(type = "respondDateNotUnderstood")
public void replyNotUnderstood(
				@Variable String statusId,
                @Variable String visibility,
                @Variable String account) {
                
    var message = MESSAGE_TEMPLATE.formatted(account, 
    										 "Hi, das habe ich leider nicht verstanden. Schreibe mir 'in x Tagen' oder einfach 'x Tage'");
    mastodonService.replyToStatus(statusId, message, visibility);
}

@JobWorker(type = "remindUser")
public void remind(@Variable String statusId,
                   @Variable String visibility,
                   @Variable String account) {
    var message = MESSAGE_TEMPLATE.formatted(account, "Hi, hier ist deine Erinnerung!");
    mastodonService.replyToStatus(statusId, message, visibility);
}</code></pre><div class="flex flex-grow flex-col max-w-full"><div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="cb1620ea-cec4-4e85-b548-8fca7043bf6d"><div class="markdown prose w-full break-words dark:prose-invert dark"><h2>Limitations</h2><p>From the example, we can see that smaller applications can be implemented with the "new" Camunda engine without operating a Kubernetes cluster for the infrastructure. Besides our program logic in a Spring Boot application, we initially only need the Zeebe broker, which could also serve several applications simultaneously. However, an important element of process engines is missing in our example: Insight into the running process engine. How can we see which process instances are in what state, or view and resolve incidents? With our setup: Not at all. As mentioned at the beginning, it is not possible to query current states from the Zeebe broker via gRPC; we can only react to events from the engine. In the full Camunda 8 platform, these events are transmitted to an Elasticsearch instance using the Elasticsearch exporter, which manages a queryable state. The Tasklist and the Operate application (formerly: Cockpit), which are based on this state, can only be used productively with a paid enterprise license. From within the community, the <a target="_blank" rel="noopener noreferrer" href="https://github.com/camunda-community-hub/zeebe-simple-monitor">Simple Zeebe Monitor</a> was developed as an open solution, providing basic functions for insight and interaction with process instances. The state of the process instances is stored in Kafka, Hazelcast, or Redis for this purpose and must also be transmitted with an appropriate Zeebe exporter. So, we again need additional infrastructure for this.</p><h2>Conclusion</h2><p>Even for smaller software projects, the new process engine behind Camunda 8, Zeebe, can be of interest. We certainly do not have to manage the complexity of the full Camunda 8 deployment. Nor is it necessary to use the Camunda Cloud or acquire a Camunda 8 Enterprise license to use Zeebe. If we are willing to maintain some additional infrastructure, we can even enable monitoring and administration of process instances with the Simple Zeebe Monitor. While this may be a viable path for small projects, hobby or open-source applications, it is less likely to be suitable for the migration of Camunda 7 applications that are in productive use in companies under the Community Edition or use user tasks.</p></div></div></div><div class="mt-1 flex justify-start gap-3 empty:hidden"><div class="text-gray-400 flex self-end lg:self-center justify-center lg:justify-start mt-0 -ml-1 visible">&nbsp;</div></div><div class="flex">&nbsp;</div>]]></content:encoded></item><item><title>Mobile Navigation Menus without JavaScript</title><author>Paul Hempel (paul.hempel@lambdaschmiede.com)</author><description>To make our company website as accessible as possible, we wanted to present central functions without JavaScript. For this reason, we explored the question of whether a mobile navigation menu can be implemented using only HTML and CSS.</description><guid isPermaLink="false">mobile-navigation-menus-without-java-script</guid><link>https://www.lambdaschmiede.com/en/blog/2023-10-25/mobile-navigation-menus-without-java-script</link><pubDate>Wed, 25 Oct 2023 11:28:49 +0000</pubDate><content:encoded><![CDATA[<div class="raw-html-embed"><video width="240" autoplay="true" loop="true">
  <source src="https://strapi.lambdaschmiede.com/uploads/Responsive_Menu_without_JS_Example_981d4598b0.webm">
</video></div><h2>The Core Implementation&nbsp;</h2><p>The <a target="_blank" rel="noopener noreferrer" href="https://gist.github.com/lambdaschmied2/fe226a473bf938f5691031b34aea775d">complete code</a> can be found as a Gist on GitHub.</p><p>On the HTML side, we keep the navigation quite simple. In the <code>&lt;nav&gt;</code> section, there are two lists, one of which is enclosed by a collapsible details block for mobile view:</p><pre><code class="language-html">&lt;nav&gt;
&lt;!-- desktop --&gt;
&lt;ul&gt;
 &nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;/li&gt;
 &nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="/"&gt;Blog&lt;/a&gt;&lt;/li&gt;
 &nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="/"&gt;Contact&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- mobile --&gt;
&lt;details&gt;
 &nbsp;&nbsp;&nbsp;&lt;summary aria-label="open mobile menu"&gt;&lt;/summary&gt;
 &nbsp;&nbsp;&nbsp;&lt;ul&gt;
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;/li&gt;
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="/"&gt;Blog&lt;/a&gt;&lt;/li&gt;
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;li&gt;&lt;a href="/"&gt;Contact&lt;/a&gt;&lt;/li&gt;
 &nbsp;&nbsp;&nbsp;&lt;/ul&gt;
&lt;/details&gt;
&lt;/nav&gt;</code></pre><p><br>On desktop, we don't want to see the <code>details</code> tag and its contents, while in the mobile view, we don't want the desktop menu:</p><pre><code class="language-css">nav&gt;details {
 &nbsp;&nbsp;&nbsp;display: none;
}

@media (max-width: 991px) {
 &nbsp;&nbsp;&nbsp;/* hide desktop menu */
 &nbsp;&nbsp;&nbsp;nav&gt;ul {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;display: none !important;
 &nbsp;&nbsp;&nbsp;}
 &nbsp;&nbsp; 
 &nbsp;&nbsp;&nbsp;/* show mobile menu */
 &nbsp;&nbsp;&nbsp;nav&gt;details {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;display: block !important;
 &nbsp;&nbsp;&nbsp;}
}</code></pre><p><strong>Done?!</strong></p><p>Our solution now meets all the technical requirements for a responsive menu. Visually, we want to further enhance the result in the next section.</p><h2>Adding a Hamburger Menu Icon</h2><p>Most of the time, we see a hamburger menu icon and a close icon on mobile versions of websites. Of course, we don't want to miss this here - and it should be implemented using only CSS:</p><pre><code class="language-css">/* custom image for mobile menu */
nav &gt; details &gt; summary::after {
 &nbsp;&nbsp;&nbsp;content: '';
 &nbsp;&nbsp;&nbsp;background-image: url(img/menu.svg);
 &nbsp;&nbsp;&nbsp;background-size: 2rem 2rem;
 &nbsp;&nbsp; 
 &nbsp;&nbsp;&nbsp;display: inline-block;
 &nbsp;&nbsp;&nbsp;width: 2rem;
 &nbsp;&nbsp;&nbsp;height: 2rem;
}

/* alternative image for open mobile menu */
nav &gt; details[open] &gt; summary::after {
 &nbsp;&nbsp;&nbsp;background-image: url(img/close.svg);
}

/* icons used: https://ionic.io/ionicons */</code></pre><p>&nbsp;</p><p>The <code>details[open]</code> attribute allows us to check whether <code>&lt;details&gt;</code> is open or closed. When it's open, in our case, we replace the background image. Since we work with the <code>::after</code> pseudo-element, we want to ensure that the icon doesn't take up the whole page. By combining <code>background-size</code>, <code>display: inline-block;</code>, <code>width: 2rem;</code> and <code>height: 2rem;</code>, we can ensure that the icon has our desired size.</p><h2>Animations</h2><p>Since we can't select parent nodes with CSS, we use a little trick that adds a subtle animation. Animations aren't strictly necessary, but since, in my opinion, this isn't an obvious solution, let's briefly look at what's possible.</p><pre><code class="language-css">nav&gt;details&gt;summary {
 &nbsp;&nbsp;&nbsp;[...]
 &nbsp;&nbsp;&nbsp;/* slight transition */
 &nbsp;&nbsp;&nbsp;transition: margin-bottom 150ms ease-out;
}

/* add margin for slight transition */
nav&gt;details[open]&gt;summary {
 &nbsp;&nbsp;&nbsp;margin-bottom: 1rem;
}</code></pre><p>Since we can't access a parent element with CSS selectors (yet?) to animate it as a whole, we add a small <code>margin-bottom</code> in the open state of the <code>dialog</code>, which grows from <code>0rem</code> to <code>1rem</code> in 150ms using the <a target="_blank" rel="noopener noreferrer" href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition"><code>transition</code> attribute</a>.</p><h2>Custom Layouts</h2><p>The exact layout is subject to individual requirements. Therefore, this article won't go into further detail. Nonetheless, we have enriched the <a target="_blank" rel="noopener noreferrer" href="https://gist.github.com/lambdaschmied2/fe226a473bf938f5691031b34aea775d">Gist</a> with code for a proper layout variant as an example.</p><h2>Visual Variations</h2><h3>A Fixed Header</h3><p>The existing code lets the navigation bar scroll with the entire website. However, if it should always be visible, we can fixate it as follows:</p><pre><code class="language-css">header {
 &nbsp;&nbsp;&nbsp;background-color: aliceblue;
 &nbsp;&nbsp;&nbsp;display: flex;
 &nbsp;&nbsp;&nbsp;justify-content: space-between;
 &nbsp;&nbsp; 
 &nbsp;&nbsp;&nbsp;/** toggle fixed header **/
 &nbsp;&nbsp;&nbsp;position: fixed;
 &nbsp;&nbsp;&nbsp;width: 100%;
}
main {
 &nbsp;&nbsp;&nbsp;/* don't forget to increase the top padding with fixed navbar */
 &nbsp;&nbsp;&nbsp;padding: 4rem 1rem;
}</code></pre><h3>A Fixed Header in Full-Screen</h3><p>So far, the <code>header</code> in its expanded state is only as large as its content. If this isn't desired and it should instead fill the entire page, we can influence it as follows:</p><pre><code class="language-css">nav&gt;details&gt;ul {
 &nbsp;&nbsp;&nbsp;flex-direction: column;
 &nbsp;&nbsp;&nbsp;align-items: end;
 &nbsp;&nbsp;&nbsp;margin: 0;
 &nbsp;&nbsp; 
 &nbsp;&nbsp;&nbsp;/** toggle full page menu **/
 &nbsp;&nbsp;&nbsp;height: 100vh;
}</code></pre><h3>One Known Issue</h3><p>Due to the absence of so-called "Focus-Trapping", the page in the background can continue to scroll. This can only be prevented by JavaScript (as of 2023). Therefore, we decided to use a fixed header for <a target="_blank" rel="noopener noreferrer" href="https://lambdaschmiede.com">our website</a>, which isn't full-screen.</p>]]></content:encoded></item><item><title>Invoking Javascript from GraalVM using Java</title><author>Tim Zöller (tim.zoeller@lambdaschmiede.com)</author><description>When developing our website and this blog, it was important to us that they function even without JavaScript. One challenge we faced was the syntax-highlighting for source code blocks in blog posts – most common libraries for this purpose are primarily in JavaScript. In this article, we’ll show how we executed this library on the backend using the polyglot features of GraalVM.</description><guid isPermaLink="false">invoking-javascript-from-graal-vm-using-java</guid><link>https://www.lambdaschmiede.com/en/blog/2023-09-10/invoking-javascript-from-graal-vm-using-java</link><pubDate>Sun, 10 Sep 2023 18:17:44 +0000</pubDate><content:encoded><![CDATA[<h2>The Problem Statement</h2><p>Our core expertise lies in Java and other JVM languages such as Clojure or Kotlin. Hence, the decision to implement our website with Clojure and run it on a JVM was an easy one. Using a full-fledged Content Management System (CMS) would have meant more maintenance and care on our part since our website should only present core information about our company. The decision for server-side rendering of the HTML files was also made for simplicity: the mostly static pages don't require dynamic content in the browser, so sending a large amount of JavaScript files to the viewer's browser, which would slow down page construction, seemed unjustified.</p><p>However, this doesn't explain why our website should function entirely without JavaScript. After all, JavaScript on the website can enhance the user experience. Nathaniel provides good reasons for this decision in his blog post <a target="_blank" rel="noopener noreferrer" href="https://endtimes.dev/why-your-website-should-work-without-javascript/">"Why your website should work without JavaScript"</a>. Data from the last three years suggest that 1% of all devices visiting a website do not execute JavaScript. Only a fifth of these devices intentionally avoid it, perhaps because users have disabled JavaScript or use a NoScript browser extension. The other 0.8% don't execute JavaScript due to errors: outdated browsers, misconfigured browser extensions, or security settings on business devices. Websites offering IT content should assume that the number of devices with intentionally disabled JavaScript is higher than 0.2% of all devices.</p><p>This blog aims to be primarily technical. We will write articles about software development and architecture and enrich them with detailed code examples. Syntax highlighting is crucial for the readability of this code. This is evident in the following code block, the read method from the <code>java.io.CharArrayReader</code> class:</p><pre><code class="language-java">@Override
public int read(CharBuffer target) throws IOException {
    synchronized (lock) {
        ensureOpen();

         if (pos &gt;= count) {
            return -1;
        }

        int avail = count - pos;
        int len = Math.min(avail, target.remaining());
        target.put(buf, pos, len);
        pos += len;
        return len;
    }
}</code></pre><p>The color scheme instantly helps the eye grasp the structure of the code sample. Language keywords are marked in a consistent color, as are class and method names. This is reminiscent of the highlighting in development environments that software developers use most of the time. Such highlighting usually occurs in the frontend, using tools like <a target="_blank" rel="noopener noreferrer" href="https://prismjs.com/">Prism.js</a> or <a target="_blank" rel="noopener noreferrer" href="https://highlightjs.org/">highlight.js</a>. These are lightweight and could be easily integrated into this website. However, they add the syntax highlighting only after loading the HTML resource by searching for &lt;code&gt; tags and applying their logic to the content. Apart from a delay in rendering, this also means that the syntax highlighting wouldn't be applied in browsers with disabled or non-functional JavaScript, reducing the website's usability on affected devices.</p><p>For these reasons, we want to perform the syntax highlighting on the server. Searching for suitable Java libraries was disappointing: Apart from many dead links to dev.java.net and Google Code, there were only a few smaller libraries supporting few languages and with very limited usage. Needing a solution that will continue to support new programming languages in the future, we decided to execute the JavaScript on the server side using Truffle.</p><h2>What is GraalVM?</h2><p><a target="_blank" rel="noopener noreferrer" href="https://www.graalvm.org/">GraalVM</a> is a universal virtual machine developed by Oracle. It was designed to efficiently execute and integrate various programming languages. GraalVM supports a wide range of languages, including Java, JavaScript, Python, Ruby, R, C, C++, and WebAssembly. Its main goal is to enhance the performance of languages on a shared platform and offer developers the ability to seamlessly combine different languages. We want to leverage this capability to invoke prism.js from Java (or Clojure). However, among Java developers, GraalVM is primarily known not for its polyglot features but for its ability to compile Java applications with Native Image into native binaries.</p><h2>Setting up the VM and Environment</h2><p>The following example uses GraalVM Community Edition version 20.0.2; it should be set up as the JVM in the context (IDE or $PATH). Since the runtime environment for JavaScript is not installed by default, it must first be installed using the GraalVM Updater:</p><pre><code class="language-plain">&gt; gu install js

Downloading: Component catalog from www.graalvm.org
Processing Component: Graal.js
Processing Component: ICU4J
Processing Component: TRegex
Additional Components are required:
    ICU4J (org.graalvm.icu4j, version 23.0.1), required by: Graal.js (org.graalvm.js)
    TRegex (org.graalvm.regex, version 23.0.1), required by: Graal.js (org.graalvm.js)
Downloading: Component js: Graal.js from github.com
Downloading: Component org.graalvm.icu4j: ICU4J from github.com
Downloading: Component org.graalvm.regex: TRegex from github.com
Installing new component: TRegex (org.graalvm.regex, version 23.0.1)
Installing new component: ICU4J (org.graalvm.icu4j, version 23.0.1)
Installing new component: Graal.js (org.graalvm.js, version 23.0.1)
Refreshed alternative links in /usr/bin/</code></pre><p>Since we deliver our application in a containerized form, this step is also necessary in the Dockerfile:</p><pre><code class="language-docker">FROM ghcr.io/graalvm/graalvm-community:20.0.2
COPY target/uberjar/website.jar /website/app.jar
EXPOSE 3000
RUN ["gu", "install", "js"]
CMD ["java", "-jar", "/website/app.jar"]</code></pre><p>It's worth noting that it's not absolutely necessary to run the entire application on GraalVM. <a target="_blank" rel="noopener noreferrer" href="https://www.graalvm.org/latest/reference-manual/js/RunOnJDK/">As described here</a>, it's also possible to run the JavaScript runtime on a "traditional" JVM.</p><h2>Calling the Library</h2><p>After the lengthy introduction, the actual invocation of the code is quite straightforward. First, we create a new context using a Builder Pattern. This provides us with a JavaScript runtime environment:</p><pre><code class="language-java">import org.graalvm.polyglot.Context;
import org.graalvm.ployglot.Source;

private Context buildContext() {
  return Context.newBuilder("js").build();
}</code></pre><p>It's advisable to initialize the context once and then reuse it. Thus, at this entry and initialization point, we can also load prism.js from a file (or better, a resource for a production application):</p><pre><code class="language-java">import org.graalvm.polyglot.Context;
import org.graalvm.ployglot.Source;

private Context buildContext() {
  var context = Context.newBuilder("js").build();
  context.eval(Source.newBuilder("js", new File("/path/to/prism.js")).build());
  return context;
}</code></pre><p>With this initialized context, we can now work to add syntax highlighting to text for display in HTML:</p><pre><code class="language-java">private String highlightSyntax(Context context, String language, String content) {
  var command = "Prism.highlight('%s', Prism.languages.%s, '%s');".formatted(content, language, language);
  return context.eval("js", command).asString();
}</code></pre><p>We use the created context to execute the formatted JavaScript code and return the result of the command as a Java string. To highlight the code, we use the highlight function, <a target="_blank" rel="noopener noreferrer" href="https://prismjs.com/docs/Prism.html#.highlight">the documentation for which can be found here</a>. It quickly becomes apparent that we are generating the script command via string manipulation, thereby potentially opening a door for script injection. Inputs should, therefore, be composed of data we control ourselves or be extremely well validated.</p><p>By running a quick test, we can verify that our syntax highlighting works as intended:</p><pre><code class="language-java">String javaCode = "private static final Integer BEST_NUMBER = 42;";
var highlightedCode = highlightSyntax(context, "java", javaCode);
System.out.println(highlightedCode);</code></pre><pre><code class="language-html">&lt;span class="token keyword"&gt;private&lt;/span&gt; 
&lt;span class="token keyword"&gt;static&lt;/span&gt; 
&lt;span class="token keyword"&gt;final&lt;/span&gt; 
&lt;span class="token class-name"&gt;Integer&lt;/span&gt; 
&lt;span class="token constant"&gt;BEST_NUMBER&lt;/span&gt; 
&lt;span class="token operator"&gt;=&lt;/span&gt; 
&lt;span class="token number"&gt;42&lt;/span&gt;
&lt;span class="token punctuation"&gt;;&lt;/span&gt;</code></pre><p>The symbols have been provided with CSS classes and can now be highlighted with CSS.</p><div class="flex flex-grow flex-col gap-3 max-w-full"><div class="min-h-[20px] flex flex-col items-start gap-3 overflow-x-auto whitespace-pre-wrap break-words"><div class="markdown prose w-full break-words dark:prose-invert light"><h2>Avoiding parallel access</h2><p>The code may seem straightforward, but especially in the backend of a website, which ideally handles many simultaneous requests, it poses risks. When simulating several hundred parallel user sessions, the following exception was triggered in our code:</p><pre><code class="language-plain">java.lang.IllegalStateException: Multi-threaded access requested by thread Thread[http-nio-8778-exec-1,5,main] but is not allowed for language(s) js.</code></pre><p>The JavaScript Runtime of GraalVM, like many others, does not support multithreading. If our application accesses the context from two different threads, it results in the aforementioned error. <a target="_blank" rel="noopener noreferrer" href="https://github.com/oracle/graaljs/blob/master/graal-js/src/com.oracle.truffle.js.test.threading/src/com/oracle/truffle/js/test/threading/ConcurrentAccess.java">GraalVM provides an extensive toolset</a> to synchronize these accesses.</p></div></div></div><h2>Decorating the code in the HTML source with Clojure</h2><p>We write our blog entries in a headless CMS. The content is delivered to our frontend, which you, dear readers, are currently looking at, as HTML. This means that we don't have easy access to the raw source code in the article, so we must apply Prism.js manually to it. While we've previously clarified the use of JavaScript on GraalVM with Java code, we'll now demonstrate further steps using Clojure code, which we use for this blog in this exact (or a similar) manner. Firstly, we need to initialize the script engine with prism.js. We create this context in the respective namespace using defonce and delay. With delay, we ensure that the engine is created only when it's actually used, so after an application restart when syntax highlighting occurs for the first time. This ensures that the initialization doesn't block during application startup, but the trade-off is that the first rendering might take an extra 200-300ms.</p><div class="bg-black rounded-md mb-4"><pre><code class="language-clojure">(defonce js-context
         (delay (doto
                  (.build (Context/newBuilder (into-array ["js"])))
                  (.eval
                    (.build
                      (Source/newBuilder "js"
                                         (clojure.java.io/resource "prism.js")))))))</code></pre></div><p>The code, sadly peppered with Java Interop syntax to work with Java syntax from Clojure, operates just as in the Java example above.</p><p>There are also no major surprises in executing the actual JavaScript syntax:</p><pre><code class="language-clojure">(defn highlight-code [language code]
  (let [context @js-context]
    (locking context
      (.asString
        (.eval context
               "js"
               (format "Prism.highlight('%s', Prism.languages.%s, '%s');" 
                       code 
                       language 
                       language))))))</code></pre><p>Again, the code looks very familiar to us. Three things are noteworthy: The <code>@</code> before <code>js-context</code> dereferences our lazily initialized context. If this has not happened by this point, it is initialized here. Furthermore, we use Clojure's locking function to prevent parallel access to the <code>js-context</code>. On the other hand, we might want to scratch our heads a bit here: We use Java-Interop code from Clojure to ultimately execute JavaScript, which one might find amusing.</p><p>To finally apply our syntax highlighting to all code blocks in our articles, we need to find them in the HTML, extract, and replace them with the new, annotated code. Fortunately, we don't have to do this manually; we can rely on <a target="_blank" rel="noopener noreferrer" href="https://github.com/cgrand/enlive">enlive</a>, a Clojure library based on <a target="_blank" rel="noopener noreferrer" href="https://jsoup.org/">JSoup</a>:</p><pre><code class="language-clojure">(defn highlight-code-in-article [content]
  (apply str
         (-&gt; content
             (html/html-snippet)
             (html/transform [:code]
                             (fn [selected-node]
                               (update-in selected-node [:content]
                                          (fn [[content]]
                                            (if-let [language-class (get-in selected-node [:attrs :class])]
                                              (html/html-snippet (highlight-code (clojure.string/replace language-class #"language-" "") content))
                                              content)))))
             (html/emit*))))</code></pre><p>The highlight-code-in-article function takes the entire article text. This text is loaded into enlive with <code>html/html-snippet</code> and converted into a Clojure data structure. The function <code>html/transform</code> then searches for all <code>&lt;code&gt;</code> elements in this structure with the selector <code>:code</code> and applies an anonymous function, which we'll extract for better readability:</p><pre><code class="language-clojure">(fn [selected-node]
  (update-in selected-node [:content]
             (fn [[content]]
               (if-let [language-class (get-in selected-node [:attrs :class])]
                 (html/html-snippet (highlight-code (clojure.string/replace language-class #"language-" "") content))
                 content))))</code></pre><p>Prism.js returns a string to us, so using enlive and <code>html/html-snippet</code>, we must convert it back into a Clojure data structure before embedding it in the HTML. The result is exactly what you see on this page: the code is already decorated with CSS classes by the server.</p><h2>Discussion</h2><p>Is the result worth the effort? We believe so. We've achieved our goal of leveraging a sophisticated syntax-highlighting library in the backend. We use Java, or rather JVM tools, and the library itself. It should be noted that the execution speed of this solution is not particularly fast. With 5 to 6 code blocks, an extra 200-300ms can be added if the JavaScript context has been previously initialized. This is not a concern in our use case, as we cache the articles and don't need to process them every time they're delivered. If this were different, we'd probably look for a more performant solution.</p><p>The use cases for GraalVM's polyglot features are numerous. Besides JavaScript, Ruby, Python, and R are particularly interesting. The ability to have these languages operate with Java on the same VM is exciting and could open up new possibilities, especially in the field of statistical evaluations.</p>]]></content:encoded></item></channel></rss>