From 920ffaf9155a1f5bb358326123c04747a03a9997 Mon Sep 17 00:00:00 2001 From: Adam Tetz Date: Mon, 1 Sep 2025 17:46:46 +0200 Subject: [PATCH 1/5] Update todo's --- todo.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/todo.md b/todo.md index 614fdd5..cd8f29f 100644 --- a/todo.md +++ b/todo.md @@ -25,4 +25,8 @@ - My role & tools used (e.g. “Designed a serverless ETL pipeline using AWS Lambda and Step Functions”) - Impact or outcome (e.g. “Reduced data latency by 80%”) - Blog + - Intro to integration-sandbox + - Solve problem with Fluxygen + - Solve problem with Azure Logic Apps + - Solve problem with n8n ~~- Contact~~ \ No newline at end of file From 881f706badeec594261256669e97e3950554d9f1 Mon Sep 17 00:00:00 2001 From: Adam Tetz Date: Fri, 5 Sep 2025 08:57:35 +0200 Subject: [PATCH 2/5] add support for mermaid. --- _src/_includes/base.njk | 6 ++++++ _src/css/main.css | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/_src/_includes/base.njk b/_src/_includes/base.njk index 282be16..7bd356a 100644 --- a/_src/_includes/base.njk +++ b/_src/_includes/base.njk @@ -19,6 +19,12 @@ src="https://kit.fontawesome.com/7991abe048.js" crossorigin="anonymous" > + + +
diff --git a/_src/css/main.css b/_src/css/main.css index bdd99f0..286c270 100644 --- a/_src/css/main.css +++ b/_src/css/main.css @@ -139,3 +139,12 @@ h1 { #download-container { text-align: center; } + +pre.mermaid { + text-align: center; + background: transparent !important; + background-color: transparent !important; + border: none !important; + padding: 1rem; /* adjust as needed */ + font-family: inherit; /* removes monospace font if you don't want it */ +} From 731c0855f5efff87a8e997d25c1b7242645c4684 Mon Sep 17 00:00:00 2001 From: Adam Tetz Date: Fri, 5 Sep 2025 08:58:10 +0200 Subject: [PATCH 3/5] add intro post for integration sandbox. --- .obsidian/app.json | 1 + .obsidian/appearance.json | 1 + .obsidian/core-plugins.json | 33 ++++ .obsidian/workspace.json | 180 ++++++++++++++++++ _site/approach/index.html | 10 +- _site/blog/index.html | 19 +- _site/bundle.css | 9 + _site/contact/index.html | 10 +- _site/index.html | 10 +- .../Integration-sandbox intro/index.html | 154 +++++++++++++++ _site/posts/mra-beeline/index.html | 50 ++--- _site/posts/webdesign/index.html | 16 +- _site/sitemap.xml | 5 + _site/utils/myrouteapp-to-beeline/index.html | 18 +- _src/posts/Integration-sandbox intro.md | 92 +++++++++ 15 files changed, 566 insertions(+), 42 deletions(-) create mode 100644 .obsidian/app.json create mode 100644 .obsidian/appearance.json create mode 100644 .obsidian/core-plugins.json create mode 100644 .obsidian/workspace.json create mode 100644 _site/posts/Integration-sandbox intro/index.html create mode 100644 _src/posts/Integration-sandbox intro.md diff --git a/.obsidian/app.json b/.obsidian/app.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.obsidian/app.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.obsidian/appearance.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json new file mode 100644 index 0000000..0faa60d --- /dev/null +++ b/.obsidian/core-plugins.json @@ -0,0 +1,33 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "footnotes": false, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": true, + "bases": true, + "webviewer": false +} \ No newline at end of file diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json new file mode 100644 index 0000000..2b850c8 --- /dev/null +++ b/.obsidian/workspace.json @@ -0,0 +1,180 @@ +{ + "main": { + "id": "3c053f5e9ba9fcd7", + "type": "split", + "children": [ + { + "id": "7604a62f01a5dbd4", + "type": "tabs", + "children": [ + { + "id": "f18e9a143aa6270d", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "_src/posts/Integration-sandbox intro.md", + "mode": "source", + "source": true + }, + "icon": "lucide-file", + "title": "Integration-sandbox intro" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "71b39e76eb38cb3b", + "type": "split", + "children": [ + { + "id": "159de406aeb8a955", + "type": "tabs", + "children": [ + { + "id": "2f8d30cb43a8d5d3", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "be3264e11b7fbcf5", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "17fa8c09b3e2a8aa", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "3b0c2253e0eae889", + "type": "split", + "children": [ + { + "id": "6d1f19fff52ebbca", + "type": "tabs", + "children": [ + { + "id": "7dacc165ccc6163e", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "_src/posts/Integration-sandbox intro.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks for Integration-sandbox intro" + } + }, + { + "id": "8f4622f8fd558276", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "_src/posts/Integration-sandbox intro.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links from Integration-sandbox intro" + } + }, + { + "id": "bc73d9195d7e2852", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "8fad864e22e081bc", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "_src/posts/Integration-sandbox intro.md", + "followCursor": false, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-list", + "title": "Outline of Integration-sandbox intro" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false, + "bases:Create new base": false + } + }, + "active": "f18e9a143aa6270d", + "lastOpenFiles": [ + "_src/posts/mra-beeline.md", + "_src/posts/Integration-sandbox intro.md", + "_site/posts/Integration-sandbox intro/index.html", + "_site/posts/Integration-sandbox intro", + "_src/posts/webdesign.md" + ] +} \ No newline at end of file diff --git a/_site/approach/index.html b/_site/approach/index.html index 5a9e16d..f6328d2 100644 --- a/_site/approach/index.html +++ b/_site/approach/index.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,13 @@ - + + + +
diff --git a/_site/blog/index.html b/_site/blog/index.html index ffe1c45..c780bd2 100644 --- a/_site/blog/index.html +++ b/_site/blog/index.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,13 @@ - + + + +
@@ -27,7 +33,14 @@
+
+

+ How I used a MyRouteApp GPX with my Beeline moto II

May 28, 2025

diff --git a/_site/bundle.css b/_site/bundle.css index 1c63c07..74f4532 100644 --- a/_site/bundle.css +++ b/_site/bundle.css @@ -140,6 +140,15 @@ h1 { text-align: center; } +pre.mermaid { + text-align: center; + background: transparent !important; + background-color: transparent !important; + border: none !important; + padding: 1rem; /* adjust as needed */ + font-family: inherit; /* removes monospace font if you don't want it */ +} + @charset "UTF-8";/*! * Pico CSS ✨ v2.1.1 (https://picocss.com) * Copyright 2019-2025 - Licensed under MIT diff --git a/_site/contact/index.html b/_site/contact/index.html index 6c45927..e59d160 100644 --- a/_site/contact/index.html +++ b/_site/contact/index.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,13 @@ - + + + +
diff --git a/_site/index.html b/_site/index.html index f1d47eb..8d96915 100644 --- a/_site/index.html +++ b/_site/index.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,13 @@ - + + + +
diff --git a/_site/posts/Integration-sandbox intro/index.html b/_site/posts/Integration-sandbox intro/index.html new file mode 100644 index 0000000..79b34e3 --- /dev/null +++ b/_site/posts/Integration-sandbox intro/index.html @@ -0,0 +1,154 @@ + + + + + + I built a sandbox to test integration platforms. + + + + + + + + + +
+ +
+ +
+
+ +
+
+ +
+

+ I built a sandbox to test integration platforms. +

+

September 5, 2025

+
+

Say you're in the market for a new integration solution and you want to try a few out before committing. Nearly every platform offers demos or trials. But what then? How are you going to decide whether to fully invest (time, money, training) based on a limited trial experience that may not reflect real-world usage?

+

In my experience working with clients, demos are polished to look good, but nothing beats hands-on experience. For trials to succeed you need something meaningful to test. Setting up proper test environments often requires at least VPN access, permissions for other environments or cloud services and IT approvals. This can be challenging and time consuming. So it's tempting to fall back on 'foo', 'bar' examples or the Pokemon API. But will this paint a clear enough picture?

+

The integration sandbox

+

This challenge has led me to build an integration sandbox. The sandbox provides the mock endpoints to test against, so I can test integration flows immediately. My goal was to evaluate how platforms handle common integration patterns:

+
    +
  • Receiving and sending messages via APIs/webhooks
  • +
  • Data transformation and mapping
  • +
  • Conditional routing
  • +
  • Batch processing
  • +
  • Scheduling
  • +
  • Error handling
  • +
  • Authentication
  • +
+

By testing these features I expect to gain insight into a platform's general usability:

+
    +
  • Learning Curve: How quickly can someone become productive?
  • +
  • Developer Experience: How pleasant is the platform for day-to-day work? Think of debugging, data mapping, error messages, documentation.
  • +
  • Implementation Speed: Time from trial start to working integration.
  • +
  • Security Basics: Authentication handling, endpoint security, secrets management
  • +
+

Note: This leaves out performance and scalability. Any serious performance testing would require enterprise-scale infrastructure and realistic data volumes beyond this evaluation's scope.

+

Use case

+

To test these features in a real world (but somewhat simplified) example, I thought of a use case in Transport and Logistics. Specifically the integration between a Shipper and a Broker.

+

Imagine you are a Shipper with a TMS that needs to send orders to a Carrier. The Carrier requires all communication to go through their preferred Broker (visibility platform). +The integration platform sits in the middle, translating the TMS data to the Broker and vice versa.

+
+	sequenceDiagram
+	participant TMS as TMS / Shipper
+	participant IP as Integration platform
+	participant VP as Broker / Visibility platform
+	
+	box transparent Sandbox
+	participant TMS
+	end
+	box transparent Sandbox
+	participant VP
+	end
+
+	TMS->>IP: New shipment
+	IP->>VP: Create order
+	VP->>IP: New event
+	IP->>TMS: Create event
+
+

The sandbox mocks both the TMS and Broker ends of the integration use case and has REST API endpoints to authenticate, seed, trigger, get and create either TMS shipments or Broker events. It's the job of the integrator to make both mock systems work together. Here's an example of a process flow that you can integrate:

+
+flowchart TD
+A@{ shape: circle, label: "start" } --> B
+B@{ shape: rect, label: "get new shipments" } --> C
+subgraph for each shipment
+	C@{shape: lean-r, label: "transform to order"} --> D
+	D@{shape: rect, label: "post order"} --> E
+	E@{shape: rect, label: "log result"}
+end
+E --> F@{shape: diam, label: "success?"}
+		F --> |Yes| G@{shape: framed-circle, label: "End"}
+		F --> |No| H@{shape: rect, label: "Handle errors"}
+ 
+
+
    +
  1. Scheduler starts the process
  2. +
  3. Get new shipments from the /tms/shipments endpoint
  4. +
  5. Split shipments payload into a sequence of single shipments (for each) +
      +
    1. Perform a data mapping to the broker format
    2. +
    3. Create the order with the /broker/order endpoint
    4. +
    5. Log the result
    6. +
    +
  6. +
  7. Check the aggregated results for errors and handle if necessary.
  8. +
+

Technical

+

I designed the sandbox with simplicity in mind. It should also be easy to maintain and test for a single developer. I wanted to run it in a container and have the possibility to deploy and use it anywhere. At this stage I'm not really concerned about high performance.

+

The mock APIs are built with Python and FastAPI. I chose FastAPI because it goes hand in hand with Pydantic dataclasses and has a complete set of features like security, easy serialisation and deserialisation of json and the automatic generation of swagger docs. The TMS and Broker endpoints both use different JSON payloads that are generated using the Faker library. The generated data is saved in a SQLite database so that I can later validate the incoming transformations against a set of business rules. Users will get a corresponding HTTP response code with the result of their requests. If something fails users get detailed error messages.

+

Get started

+

Want to try it yourself? The sandbox is available as a Docker image: +docker run -d -p 8000:8000 atetz/integration-sandbox:latest

+

Once running, you can access the API documentation at http://localhost:8000/docs and start building your integration flows immediately. The mapping specifications can be found in the repo! +I also have it running in AWS Lightsail with minimal effort.

+

What's next?

+

In the next weeks I'm going to put it to the test with Fluxygen, Azure Logic Apps and n8n.

+

What do you think? I'd love to hear your thoughts, experiences, or even just a quick hello!

+ +
+
+
+
+ +
+
© Adam Tetz 2025
+
+ + diff --git a/_site/posts/mra-beeline/index.html b/_site/posts/mra-beeline/index.html index ed77b37..1c2c139 100644 --- a/_site/posts/mra-beeline/index.html +++ b/_site/posts/mra-beeline/index.html @@ -1,14 +1,20 @@ - + - How I used a MyRouteApp gpx with my Beeline moto II + How I used a MyRouteApp GPX with my Beeline moto II - + + + +
@@ -50,34 +56,34 @@

- How I used a MyRouteApp gpx with my Beeline moto II + How I used a MyRouteApp GPX with my Beeline moto II

May 28, 2025

In a hurry? Click here for a TLDR -

Testing out my new Beeline Moto II motorbike navigation, I ran into some compatibility issues with my routes created with the MyRouteApp. Upon inspecting both files I noticed a difference in the gpx file structure. Since a gpx is defined in XML I created small tool using JavaScript and XSLT to convert the MyRouteApp file to a Beeline compatible file. You can find the tool here

+

While testing out my new Beeline Moto II motorbike navigation, I ran into some compatibility issues with my routes created with MyRouteApp. Namely, losing the turn-by-turn navigation while going off track. My short roadside frustration turned into a deep dive into GPX files and how to integrate the MyRouteApp format with my Beeline. Upon inspecting both files, I noticed a difference in the GPX file structure. Since a GPX is defined in XML, I decided to make a tool in vanilla JavaScript and XSL that will transform the file for me. And since there are other users with the same issue, I thought it would be nice to share my solution and make it available to anyone that can benefit from it. You can find the tool here

Intro

Triumph Bonneville T120One of my favourite ways to enjoy the French outdoors is by riding my motorbike. I make it a sport to create a twisty route with nice elevations and new viewpoints that preferably pass a couple of picnic spots along the way. My preferred method was creating a route in MyRouteApp and using my QuadLock mounted phone for navigation. And this worked quite well.

But, I wanted a more minimalistic device in my cockpit and I liked the idea of having my phone in my pocket instead of on my bike in case of an emergency. So after some "very deep research" on YouTube and Google, I naturally found (or was influenced towards...) the Beeline Moto II.

-

Fast forward to unboxing and using the Beeline. I was super hyped. I exported my gpx file from MyRouteApp and hit the road. And all went surprisingly well.

+

Fast forward to unboxing and using the Beeline. I was super hyped. I exported my GPX file from MyRouteApp and hit the road. And all went surprisingly well.

Problem

Beeline device without navigationUntil.... I hit into a familiar yellow French sign named déviation (a diversion)! As soon as I got off the track, my Beeline showed a white arrow further diving into a black abyss. No roads were visible, no directions, only a white arrow and a dotted line showing how far I was off the track.

What on earth? Why is my newest piece of navigation technology not navigating? So I stopped, grabbed my phone and opened the Beeline app. Skipping a waypoint wasn't an option because I only had 1 waypoint. Hmm... I opened my maps app and after memorizing some villages I got back on track and my turn by turn navigation was restored. Sweet!

On my way home my inner problem solver was already working. Did I export my file wrong? Did I forget to check a box on importing?

-

Soon I learned that other users had similar issues combining MyRouteApp and Beeline, and that their forum topics hit a dead end. They noted that their route seemed to be converted to a track, and only had a start- and end-point. I also learned that it was impossible to import a Beeline gpx in the MyRouteApp. I tried a couple of things on the MyRouteApp end without success. Then on the Beeline support page I found an article on importing and exporting a gpx with this note:

+

Soon I learned that other users had similar issues combining MyRouteApp and Beeline, and that their forum topics hit a dead end. They noted that their route seemed to be converted to a track, and only had a start- and end-point. I also learned that it was impossible to import a Beeline GPX in the MyRouteApp. I tried a couple of things on the MyRouteApp end without success. Then on the Beeline support page I found an article on importing and exporting a GPX with this note:

Please note: you can only edit GPX-imported routes within the Beeline app if you are using the "Waypoints only" import mode. You can learn more about that mode in the article listed above.
— Beeline support
-

Waypoints only import mode? I didn't see that option at all! But surely my gpx had waypoints? During the creation of my route I added 30 or so...

+

Waypoints only import mode? I didn't see that option at all! But surely my GPX had waypoints? During the creation of my route I added 30 or so...

You might be thinking: Why aren't you using the Beeline app anyway? While the Beeline app comes with a route creation functionality, I find the feature set of MyRouteApp superior. I want to skip dirt roads, maximize twisty roads, maximize elevation, toggle different points of interest along the way like petrol stations etc.

-

Carrying on with my problem, I created a small test route in the Beeline app and exported a gpx file. Since the gpx file is actually a xml file, I shouldn't have any trouble figuring out what the differences are.

-

This is a snippet of the Beeline gpx export without the xml declaration and namespaces:

+

Carrying on with my problem, I created a small test route in the Beeline app and exported a GPX file. Since the GPX file is actually a XML file, I shouldn't have any trouble figuring out what the differences are.

+

This is a snippet of the Beeline GPX export without the XML declaration and namespaces:

.....
 <!-- The route waypoints -->
   <wpt lat="47.765573749816014" lon="4.5727777081385605"/>
@@ -93,7 +99,7 @@ 

Problem

<rtept lat="47.76614" lon="4.57222"/> .....
-

Alright, now let's have a look at the MyRouteApp gpx that I'm importing:

+

Alright, now let's have a look at the MyRouteApp GPX that I'm importing:

.....
 <rte>
         <name>test</name>
@@ -117,10 +123,10 @@ 

Problem

A few things are going on here:

    -
  • The Beeline gpx has waypoint <wpt> nodes while the MyRouteApp has not.
  • -
  • The MyRouteApp gpx provides more information in the route segment <rte>.
  • -
  • The MyRouteApp gpx has a track segment <trk>.
  • -
  • The Beeline gpx <rte> segment suspiciously looks a lot like the MyRouteApp <trkseg> because the coordinates are very close to each-other.
  • +
  • The Beeline GPX has waypoint <wpt> nodes while the MyRouteApp has not.
  • +
  • The MyRouteApp GPX provides more information in the route segment <rte>.
  • +
  • The MyRouteApp GPX has a track segment <trk>.
  • +
  • The Beeline GPX <rte> segment suspiciously looks a lot like the MyRouteApp <trkseg> because the coordinates are very close to each-other.

Both seem to have a different interpretation of the GPX 1.1 Schema Documentation. Looking at the definitions I note 3 things:

    @@ -131,13 +137,13 @@

    Problem

    Given the definitions and examples above. I find that the Beeline app should be using the rteType instead of individual waypoints for a route. Because that is what it's designed for. Also, the trkType is meant for tracks and it seems Beeline is using rteType for that.

    As a good user, I obviously raised a ticket with Beeline, providing as much details as possible. I was then gracefully thanked for my suggestions and informed that my feedback was forwarded up the chain. Great! But knowing that technical feedback like this often gets dismissed as subjective interpretation rather than standards compliance, I knew I had to work on a solution in the meantime.

    Solution

    -

    Knowing the differences between the formats, the workaround was relatively straightforward: I only had to transform the MyRouteApp gpx to a Beeline gpx. To make my MyRouteApp gpx compatible with the Beeline app I decided to:

    +

    Knowing the differences between the formats, the workaround was relatively straightforward: I only had to transform the MyRouteApp GPX to a Beeline GPX. To make my MyRouteApp GPX compatible with the Beeline app I decided to:

    • transform the MyRouteApp <rtept> nodes to <wpt> nodes.
    • transform the MyRouteApp <trkseg> to a <rte>.

    My first test file was hacked together using some good old copy, paste search and replaces.

    -

    Et voila! Upon importing my newly created gpx I was greeted by another option: "Points de cheminement uniquem...". +

    Et voila! Upon importing my newly created GPX I was greeted by another option: "Points de cheminement uniquem...". Which translates to the "Waypoints only import mode" mentioned by Beeline above. Phone screenshot P.S. Pardon my French, I set my phone to French as a part of my language learning process.

    @@ -145,16 +151,16 @@

    Sound great? There's just one caveat

    This methods imports the waypoints added in the MyRouteApp but will re-calculate the route in between them. If you want the Beeline to calculate the same or near similar route, I advise you to take an extra 15 min to add waypoints on every main road change. My approach looked like this:

    MRA screenshot

    Automated solution

    -

    Obviously I wasn't planning on manually editing gpx files every time I wanted to use one of my routes. +

    Obviously I wasn't planning on manually editing GPX files every time I wanted to use one of my routes. So I decided to make a tool that will transform the file for me. And since there are other users with the same issue, I thought it would be nice to share my solution and make it available to anyone that can benefit from it. The solution is simple:

      -
    • A small webform takes a gpx file as input and let's the user download the transformed result.
    • -
    • The transformation is done by a script written in JavaScript that executes a XSLT (eXtensible Stylesheet Language Transformations). For those unfamiliar but curious: Check out this Introduction on XSLT.
    • +
    • A small webform takes a GPX file as input and let's the user download the transformed result.
    • +
    • The transformation is done by a script written in JavaScript that executes a XSLT (eXtensible Stylesheet Language Transformations). For those unfamiliar but curious: Check out this Introduction on XSLT.
    • This all runs within the users browser. Which means I only have to host the static files and don't need worry about running a service.
    -

    You can find the result here

    +

    You can find the result here

    Thats it!

    -

    My short road side frustration turned into a deep dive into gpx files and how to integrate the MyRouteApp format with my Beeline. While I hope that Beeline will eventually improve their compatibility, in the meantime my tool will provide a practical solution. If you're facing similar issues, give the tool a try and let me know how it works for your routes. Happy riding!

    +

    My short road side frustration turned into a deep dive into GPX files and how to integrate the MyRouteApp format with my Beeline. While I hope that Beeline will eventually improve their compatibility, in the meantime my tool will provide a practical solution. If you're facing similar issues, give the tool a try and let me know how it works for your routes. Happy riding!

diff --git a/_site/posts/webdesign/index.html b/_site/posts/webdesign/index.html index 62669db..7f3fb98 100644 --- a/_site/posts/webdesign/index.html +++ b/_site/posts/webdesign/index.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,13 @@ - + + + +
@@ -55,11 +61,11 @@

May 26, 2025

My goal was to make a simple page that would enable me to write something about myself and what I do. And it should also be a spot where I could share my ideas. I heard about Github pages before and I wanted to give it a go. Naturally I found loads of resources naming jekyll.

-

I spun up a repo and tried it out with a simple theme. Soon I noticed that once I wanted to make some changes to the theme, it required some effort to understand and override what the prevous designer intended. Should I just have picked something and call it a day? Maybe.

+

I spun up a repo and tried it out with a simple theme. Soon I noticed that once I wanted to make some changes to the theme, it required some effort to understand and override what the previous designer intended. Should I just have picked something and call it a day? Maybe.

But there was also a feeling lingering. I wanted something more minimalistic.... -When I was a teen my "hobby" used to be designing websites. Designing a template in a graphic program, slicing it up in 1px images and using HTML + CSS2. +When I was a teenager my "hobby" used to be making websites. Designing a template in a graphic program, slicing it up in 1px images and using HTML + CSS2. Nowadays we have HTML5 and CSS3. I heard great stories about grid and flexbox. Which meant the days of fighting <div> are long gone..

-

So I decided I'd roll my own. With the help of some usefull libraries/tools:

+

So I decided I'd roll my own. With the help of some useful libraries/tools:

  • PicoCSS for a out of the box CSS that gives me a nice set of defaults to work with
  • 11ty as a simple static site generator so that I can generate content based on Markdown files.
  • diff --git a/_site/sitemap.xml b/_site/sitemap.xml index f285581..dbc12f8 100644 --- a/_site/sitemap.xml +++ b/_site/sitemap.xml @@ -36,4 +36,9 @@ 2025-05-28T12:49:23.310Z + + /posts/Integration-sandbox intro/ + 2025-09-05T00:00:00.000Z + + \ No newline at end of file diff --git a/_site/utils/myrouteapp-to-beeline/index.html b/_site/utils/myrouteapp-to-beeline/index.html index 7380bd1..26a640f 100644 --- a/_site/utils/myrouteapp-to-beeline/index.html +++ b/_site/utils/myrouteapp-to-beeline/index.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,13 @@ - + + + +
    @@ -63,10 +69,10 @@

    MyRouteApp to Beeline converter

    Sound great? There's one caveat!

    - This methods imports the waypoints added in the MyRouteApp but _will - re-calculate_ the route in between them. If you want the Beeline to - calculate the same or near similar route, I advise you to take an extra 15 - min to add waypoints on every main road change. My approach looked like + This methods imports the waypoints added in the MyRouteApp but + will re-calculate the route in between them. If you want the Beeline + to calculate the same or near similar route, I advise you to take an extra + 15 min to add waypoints on every main road change. My approach looked like this:
    MRA screenshot diff --git a/_src/posts/Integration-sandbox intro.md b/_src/posts/Integration-sandbox intro.md new file mode 100644 index 0000000..275cd70 --- /dev/null +++ b/_src/posts/Integration-sandbox intro.md @@ -0,0 +1,92 @@ +--- +title: I built a sandbox to test integration platforms. +date: 2025-09-05 +--- +Say you're in the market for a new integration solution and you want to try a few out before committing. Nearly every platform offers demos or trials. But what then? How are you going to decide whether to fully invest (time, money, training) based on a limited trial experience that may not reflect real-world usage? + +In my experience demos are polished to look good, but nothing beats hands-on experience. For trials to succeed you need something meaningful to test. Setting up proper test environments often requires at least VPN access, permissions for other environments or cloud services and IT approvals. This can be challenging and time consuming. So it's tempting to fall back on 'foo', 'bar' examples or the Pokemon API. But will this paint a clear enough picture? + +### The integration sandbox +This challenge has led me to build an [integration sandbox](https://github.com/atetz/integration-sandbox). The sandbox provides the mock endpoints to test against, so I can test integration flows immediately. My goal was to evaluate how platforms handle common integration patterns: +- Receiving and sending messages via APIs/webhooks +- Data transformation and mapping +- Conditional routing +- Batch processing +- Scheduling +- Error handling +- Authentication + +By testing these features I expect to gain insight into a platform's general usability: +- Learning Curve: How quickly can someone become productive? +- Developer Experience: How pleasant is the platform for day-to-day work? Think of debugging, data mapping, error messages, documentation. +- Implementation Speed: Time from trial start to working integration. +- Security Basics: Authentication handling, endpoint security, secrets management + +*Note: This leaves out performance and scalability. Any serious performance testing would require enterprise-scale infrastructure and realistic data volumes beyond this evaluation's scope.* + +### Use case +To test these features in a real world (but somewhat simplified) example, I thought of a use case in _Transport and Logistics_. Specifically the integration between a __Shipper__ and a __Broker__. + +Imagine you are a Shipper with a TMS that needs to send orders to a Carrier. The Carrier requires all communication to go through their preferred Broker (visibility platform). +The integration platform sits in the middle, translating the TMS data to the Broker and vice versa. + +

    +	sequenceDiagram
    +	participant TMS as TMS / Shipper
    +	participant IP as Integration platform
    +	participant VP as Broker / Visibility platform
    +	
    +	box transparent Sandbox
    +	participant TMS
    +	end
    +	box transparent Sandbox
    +	participant VP
    +	end
    +
    +	TMS->>IP: New shipment
    +	IP->>VP: Create order
    +	VP->>IP: New event
    +	IP->>TMS: Create event
    +
    + +The sandbox mocks both the TMS and Broker ends of the integration use case and has REST API endpoints to authenticate, seed, trigger, get and create either TMS shipments or Broker events. It's the job of the integrator to make both mock systems work together. Here's an example of a process flow that you can integrate: + +
    +flowchart TD
    +A@{ shape: circle, label: "start" } --> B
    +B@{ shape: rect, label: "get new shipments" } --> C
    +subgraph for each shipment
    +	C@{shape: lean-r, label: "transform to order"} --> D
    +	D@{shape: rect, label: "post order"} --> E
    +	E@{shape: rect, label: "log result"}
    +end
    +E --> F@{shape: diam, label: "success?"}
    +		F --> |Yes| G@{shape: framed-circle, label: "End"}
    +		F --> |No| H@{shape: rect, label: "Handle errors"}
    + 
    +
    + +1. Scheduler starts the process +2. Get new shipments from the /tms/shipments endpoint +3. Split shipments payload into a sequence of single shipments (for each) + 1. Perform a data mapping to the broker format + 2. Create the order with the /broker/order endpoint + 3. Log the result +4. Check the aggregated results for errors and handle if necessary. + +### Technical +I designed the sandbox with simplicity in mind. It should also be easy to maintain and test for a single developer. I wanted to run it in a container and have the possibility to deploy and use it anywhere. At this stage I'm not really concerned about high performance. + +The mock APIs are built with Python and [FastAPI](https://fastapi.tiangolo.com/). I chose FastAPI because it goes hand in hand with Pydantic dataclasses and has a complete set of features like security, easy serialisation and deserialisation of json and the automatic generation of swagger docs. The TMS and Broker endpoints both use different JSON payloads that are generated using the [Faker](https://faker.readthedocs.io/en/master/) library. The generated data is saved in a SQLite database so that I can later validate the incoming transformations against a set of business rules. Users will get a corresponding HTTP response code with the result of their requests. If something fails users get detailed error messages. + +### Get started +Want to try it yourself? The sandbox is available as a Docker image: +`docker run -d -p 8000:8000 atetz/integration-sandbox:latest` + +Once running, you can access the API documentation at `http://localhost:8000/docs` and start building your integration flows immediately. The mapping specifications can be found in the [repo](https://github.com/atetz/integration-sandbox/tree/main/docs/integrations)! +I also have it running in AWS Lightsail with minimal effort. + +### What's next? +In the next weeks I'm going to put it to the test with [Fluxygen](https://fluxygen.com/), [Azure Logic Apps](https://azure.microsoft.com/en-us/products/logic-apps/) and [n8n](https://n8n.io/). + +What do you think? I'd love to [hear your thoughts](https://data-integration.dev/contact/), experiences, or even just a quick hello! \ No newline at end of file From af7ca4e23173adc692eed5b85d50a8217c9d6d80 Mon Sep 17 00:00:00 2001 From: Adam Tetz Date: Fri, 5 Sep 2025 09:02:15 +0200 Subject: [PATCH 4/5] add .obsidian to gitignore. --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 844b1cc..0ac3294 100644 --- a/.gitignore +++ b/.gitignore @@ -87,4 +87,6 @@ lerna-debug.log # System Files .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db + +*.obsidian* \ No newline at end of file From 0c3c55cc18faf07fd57d6a532efdf67376324acb Mon Sep 17 00:00:00 2001 From: Adam Tetz Date: Fri, 5 Sep 2025 09:04:39 +0200 Subject: [PATCH 5/5] removed obsidian files. --- .obsidian/app.json | 1 - .obsidian/appearance.json | 1 - .obsidian/core-plugins.json | 33 ------- .obsidian/workspace.json | 180 ------------------------------------ 4 files changed, 215 deletions(-) delete mode 100644 .obsidian/app.json delete mode 100644 .obsidian/appearance.json delete mode 100644 .obsidian/core-plugins.json delete mode 100644 .obsidian/workspace.json diff --git a/.obsidian/app.json b/.obsidian/app.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.obsidian/app.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.obsidian/appearance.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json deleted file mode 100644 index 0faa60d..0000000 --- a/.obsidian/core-plugins.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "file-explorer": true, - "global-search": true, - "switcher": true, - "graph": true, - "backlink": true, - "canvas": true, - "outgoing-link": true, - "tag-pane": true, - "footnotes": false, - "properties": false, - "page-preview": true, - "daily-notes": true, - "templates": true, - "note-composer": true, - "command-palette": true, - "slash-command": false, - "editor-status": true, - "bookmarks": true, - "markdown-importer": false, - "zk-prefixer": false, - "random-note": false, - "outline": true, - "word-count": true, - "slides": false, - "audio-recorder": false, - "workspaces": false, - "file-recovery": true, - "publish": false, - "sync": true, - "bases": true, - "webviewer": false -} \ No newline at end of file diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json deleted file mode 100644 index 2b850c8..0000000 --- a/.obsidian/workspace.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "main": { - "id": "3c053f5e9ba9fcd7", - "type": "split", - "children": [ - { - "id": "7604a62f01a5dbd4", - "type": "tabs", - "children": [ - { - "id": "f18e9a143aa6270d", - "type": "leaf", - "state": { - "type": "markdown", - "state": { - "file": "_src/posts/Integration-sandbox intro.md", - "mode": "source", - "source": true - }, - "icon": "lucide-file", - "title": "Integration-sandbox intro" - } - } - ] - } - ], - "direction": "vertical" - }, - "left": { - "id": "71b39e76eb38cb3b", - "type": "split", - "children": [ - { - "id": "159de406aeb8a955", - "type": "tabs", - "children": [ - { - "id": "2f8d30cb43a8d5d3", - "type": "leaf", - "state": { - "type": "file-explorer", - "state": { - "sortOrder": "alphabetical", - "autoReveal": false - }, - "icon": "lucide-folder-closed", - "title": "Files" - } - }, - { - "id": "be3264e11b7fbcf5", - "type": "leaf", - "state": { - "type": "search", - "state": { - "query": "", - "matchingCase": false, - "explainSearch": false, - "collapseAll": false, - "extraContext": false, - "sortOrder": "alphabetical" - }, - "icon": "lucide-search", - "title": "Search" - } - }, - { - "id": "17fa8c09b3e2a8aa", - "type": "leaf", - "state": { - "type": "bookmarks", - "state": {}, - "icon": "lucide-bookmark", - "title": "Bookmarks" - } - } - ] - } - ], - "direction": "horizontal", - "width": 300 - }, - "right": { - "id": "3b0c2253e0eae889", - "type": "split", - "children": [ - { - "id": "6d1f19fff52ebbca", - "type": "tabs", - "children": [ - { - "id": "7dacc165ccc6163e", - "type": "leaf", - "state": { - "type": "backlink", - "state": { - "file": "_src/posts/Integration-sandbox intro.md", - "collapseAll": false, - "extraContext": false, - "sortOrder": "alphabetical", - "showSearch": false, - "searchQuery": "", - "backlinkCollapsed": false, - "unlinkedCollapsed": true - }, - "icon": "links-coming-in", - "title": "Backlinks for Integration-sandbox intro" - } - }, - { - "id": "8f4622f8fd558276", - "type": "leaf", - "state": { - "type": "outgoing-link", - "state": { - "file": "_src/posts/Integration-sandbox intro.md", - "linksCollapsed": false, - "unlinkedCollapsed": true - }, - "icon": "links-going-out", - "title": "Outgoing links from Integration-sandbox intro" - } - }, - { - "id": "bc73d9195d7e2852", - "type": "leaf", - "state": { - "type": "tag", - "state": { - "sortOrder": "frequency", - "useHierarchy": true, - "showSearch": false, - "searchQuery": "" - }, - "icon": "lucide-tags", - "title": "Tags" - } - }, - { - "id": "8fad864e22e081bc", - "type": "leaf", - "state": { - "type": "outline", - "state": { - "file": "_src/posts/Integration-sandbox intro.md", - "followCursor": false, - "showSearch": false, - "searchQuery": "" - }, - "icon": "lucide-list", - "title": "Outline of Integration-sandbox intro" - } - } - ] - } - ], - "direction": "horizontal", - "width": 300, - "collapsed": true - }, - "left-ribbon": { - "hiddenItems": { - "switcher:Open quick switcher": false, - "graph:Open graph view": false, - "canvas:Create new canvas": false, - "daily-notes:Open today's daily note": false, - "templates:Insert template": false, - "command-palette:Open command palette": false, - "bases:Create new base": false - } - }, - "active": "f18e9a143aa6270d", - "lastOpenFiles": [ - "_src/posts/mra-beeline.md", - "_src/posts/Integration-sandbox intro.md", - "_site/posts/Integration-sandbox intro/index.html", - "_site/posts/Integration-sandbox intro", - "_src/posts/webdesign.md" - ] -} \ No newline at end of file