Blog

2022

02/06/2022 - Redesigning the course

I'm starting over with setting up the course, using Kubernetes as a starting point. By now I have the necessary experience with it. I have also practiced using Istio as a service mesh. Meanwhile, there is also Cilium, which is also a service mesh and works with eBPF technology. I have yet to figure out how to use that.

The first chapters, setting up the modules, remain the same. In the pods, I now use TCP/IP sockets for communication. In the notebook workflow.ipynb you can see how it works.

2021

08/27/2021 - MicroK8s example

The MicroK8s-based application is ready, see https://github.com/rbontekoe/ar. Clone the repository and follow the instruction at the beginning of the Julia notebook client.ipynb. Julia notebook can be installed with "add IJulia".

I have decided to handle the communication with ip sockets, something that is easy to build with Julia.

06/22/2021 - MicroK8s

I have been working with MicroK8s for a while now. MicroK8s means containers that communicate with each other and are managed by Kubernetes. The convenience of Kubernetes is that you can deploy, update and delete your application with the same YAML file. This is the reason why I am going to redesign the course.

04/21/2021 - Configuring the Jenkins-Slave Container

I wanted to automatically update my projects and decided do the Udemy course Learn DevOps: CI/CD with Jenkins using Pipelines and Docker by Eduward Viaene.

The beauty of the course is that all Jenkins software runs in Docker containers. I had some problems with configuring the Jenkins-slave container on a second system. I documented the steps in chapter 19 Configuring the Jenkins-Slave Container.

The chapter is still under development!

02/22/2021 - Install Ubuntu Server with WiFi

Last week I installed Ubuntu server 20.04 on an old laptop. Unfortunately, the installation did not ask for a wifi connection. It had to be done manually. I found the next link: How to Enable Wi-Fi on Ubuntu Server 20.04 without a Wired Ethernet Connection.

I copied the next files to a USB stick:

  • http://mirrors.kernel.org/ubuntu/pool/main/w/wpa/wpasupplicant2.9-1ubuntu4amd64.deb
  • http://mirrors.kernel.org/ubuntu/pool/main/libn/libnl3/libnl-route-3-2003.4.0-1amd64.deb
  • http://mirrors.kernel.org/ubuntu/pool/main/p/pcsc-lite/libpcsclite11.8.26-3amd64.deb

After installing the WPA Supplicant Package files, I modified the /etc/netplan/00-installer-config.yaml configurarion file with a fixed ip-address 192.168.1.36 as follows:

# This is the network config written by 'subiquity'
network:
  ethernets: {}
  wifis:
    wlp12s0:
      dhcp4: no
      dhcp6: no
      optional: true
      addresses: [192.168.1.36/24]
      gateway4: ip-gateway
      nameservers:
        addresses: [1.1.1.1, 8.8.8.8]
      access-points:
        "network_ssid_name":
          password: "**********"
  version: 2
  renderer: networkd

Then I activated the wifi connection with sudo netplan apply and rebooted the server.

01/29/2021 - Completed the course until chapter 15

I'm done with the course now that I've also finished Chapter 15. Will go through all the activities and exercises one more time.

I also decided to use notebooks. The convenience is that the student can also experiment. In chapter 14, there are two, ar.ipynb to create the data files, and in website.ipynb, the student will learn how to use Bukdu as a web framework. Clone TestAppliAR and get started right away. The prerequisite is that the two Docker containers testsshd and testsshd2 have already been created.

In Chapter 15, the student will create a new container gateway in which one sets up the website.

2020

12/09/2020

In section 1, I added chapter 7 about creating documentation for the module Accounts.jl.

Lately, I have been busy with Genie.jl. Genie is a web framework that I want to use as a user interface for the AppliAR.jl, the accounts receivable module.

The next article I read this day, pupils learn more effectively through stories than activities, got me thinking.

The BAWJ course is strongly based on activities. I chose to do this to be able to look back on how I did something. But how do I apply storytelling with abstract (in contrast to human-based) examples for this course? That is what I am going to look into.

10/19/2020 - Add Currying and Piping Example to Accounts.jl

I was wondering how I could realize piping in Julia. It turns out that you have to use currying to do that. On the page Implementing Currying, I found an example. You can find my implementation on Example 2: Currying and Piping.

Yesterday, I discovered that VSCode runs on a Chromebook with an ARM processor (I have an Acer R13). Atom does not support ARM. However, I am not impressed by the performance, but it works. Especially the languageserver.jl has problems getting started.

10/13/2020 - VSCode for Julia

The Juno team joined the VSCode for Julia team. Juno.jl will be now in maintenance mode only. More: VSCode for Julia.

One feature I particularly loved was the debugger. I have decided to adapt the course to VSCode for Julia.

09/09/2020 - Solved the Triple Back Ticks Problem

If you want to display triple back ticks in a text block enclose it between four back ticks. I updated chapter 4.

I decided to extend section 1 with a chapter about Documenter.jl the reason I searched again for a solution of the back ticks.

08/20/2020 - Finished Section 1

Finished section 1 Experimenting with Modules. Reviewed chapters one till six. I did the activities and exercises. Looks good.

The next step is section 2 The Accounts Receivable Module.

08/11/2020 - Added an Easier Example

Today, I 'finished' the Accounts.jl module. I am happy that I switched to an easier example of the activities and exercise.

08/03/2020 - Installed Samsung Portable SSD T5

Received my Samsung Portable SSD T5 and installed Ubuntu 20.04 on it. It is very fast.

I also rewrote the appendix with up-to-date installation instructions. I changed the name Appendix into Installation instructions. See.

07/15/2020 - Rewrite the course

Added documentation to AppliAR. In 4. Example the code that uses Docker containers and actors. Now ready to rewrite the course and incorporate the actors.

07/10/2020 - Rocket.jl

I played last week with Rocket.jl, a package where you can work with actors. During the time I worked with Akka and Scala, I became familiar with the idea of actors.

Actors send messages to other actors, which are stored in their mailbox and then processed one by one. The advantage is that the actors are completely separated from each other and can manage their data.

The results were positive, so I consider to set up the application the way I did with Akka.

06/25/2020 - Domain, API, and Infrastructure as sub-modules

A sub-module is a defined unit where you define what the input is and what someone else can use. Would it have an advantage to define the Domain, API and Infrastructure shell as a sub module as well? I implemented it in AppliAR.

05/11/2020 - Registered the packages AppliSales and AppliGeneralLedger

Last Friday, I have registered my first packages at JuliaRegistries/General:

  • AppliSales
  • AppliGeneralLedger

To register the packages you use https://juliahub.com/ui/Packages, using the Registering Packages page. You have to wait for three days before you can add the packages to a project.

I had to do this because AppliSales and AppliGeneralLedgers are dependencies in AppliAR.jl and I wanted to make use of Travis CI. Travis CI requires that it can add the tests (in runtests.jl) from the official Julia registry.

Now I am considering to partially rewrite the course to incorporate my newly acquired knowledge.

05/07/2020 - PkgTemplates.jl

After watching the video Developing Julia Packages I decided to use PkgTemplates.jl as the starting point for package development.

I created the package AppliAR.jl (AR = Account Receivable) with the code from AppliInvoicing.jl, a name I will replace.

Also, I recreated the supporting packages AppliSales.jl and AppliGeneral.jl.

Overall, I believe that what I learned from using PkgTemplates makes the application more professional. Unfortunately, I have to rewrite parts of the course.

In AppliMaster.jl you find the files test_local_channels_2.jl and client.jl to test the application.

# test_local_channels2.jl

using Pkg
Pkg.activate(".")

# remove old stuff
cmd = `rm test_invoicing.sqlite test_ledger.txt test_journal.txt`
run(cmd)

# enable distrbuted computing
using Distributed
@info("Enable distributed computing")

# this should be the next step
np = addprocs(4; exeflags=`--project=$(Base.active_project())`)
#np = addprocs([("rob@192.168.2.77:2222", :auto)]; exeflags=`--project=$(Base.active_project())`)
@info("number of processes is $(length(np))")

# activate the packages (before the processes are created)
@everywhere begin
    using AppliSales
    using AppliGeneralLedger
    using AppliAR
end;

@info("Distributed computing enabled")

# get the tasks and dispatcher
include("./api/api.jl")

# start the dispatcher
rx = dispatcher()
@info("Dispatcher started")

# start application remote
include("client.jl")

# display aging report
using DataFrames

sleep(10)
r = DataFrame(report())
println("\nUnpaid invoices\n============")
println(r)

04/17/2010 - Using socket for remote communication

I have defined Task_4 in myfunctions2.jl of AppliMaster. The task listens on port 8000. When data is detected, it is deserialized. The string START will initiate the process. Sending a BankStatement will create the paid invoices.

First start test_local_channels_2.jl. Then run the next code in a separate Julia session.

using Sockets
using Serialization
using AppliGeneralLedger, AppliInvoicing

io = connect("<ip target laptop>", 8000)

# start application
serialize(io, "START")

# send bankstatements
stms = AppliInvoicing.read_bank_statements("./bank.csv")
serialize(io, stms)

03/27/2020 - Extending Julia LOAD_PATH

II wanted to add a new feature to AppliInvoicing: reporting e.g., an aging report. It should live in a submodule of AppliInvoicing. First, I created a branch dev:

$ git branch dev

$ git checkout dev

$ atom .

Next, I created the file Reporting.jl and created a dummy module Reporting:

module Reporting

const PATH_DB = "./invoicing.sqlite"

using Dates

using AppliInvoicing

aging() = begin
    unpaid_invoices = AppliInvoicing.retrieve_unpaid_invoices(PATH_DB)
    list = []
    for invoice in unpaid_invoices
        date = invoice.meta.date
        println(date, " - ", Dates.today())
    end
end # aging

end # module

After that, I added the function report() to api.jl and modified the file AppliInvoicing.jl:

module AppliInvoicing

greet() = print("Hello World!")

export create, process, retrieve_unpaid_invoices, read_bank_statements, report

# first, link to the current model
include("./infrastructure/infrastructure.jl")

# next, submodule Reporting
include("Reporting.jl")
using .Reporting: aging

end # module

To test the module, I used AppliMaster. I didn't want all the time to upload AppliInvoicing to GitHub, so I extended the Julia LOAD_PATH. After removing the AppliInvoicing package, I added the next line to test_local_channels.jl:

# define the local path for AppliInvoicing
@everywhere push!(LOAD_PATH, "/home/rob/julia-projects/tc/AppliInvoicing")

03/25/2020 - Holy traits pattern implemented in dispatcher

The Holy traits pattern is described in the book Design Patterns and Best Practices with Julia. It can replace if-else constructs. Old situation dispatcher logic. With the new situation, we avoid troubles in the future when we use more Appli-packages. See current infrastructure, 6. Testing the application. The Holy traits pattern makes it more clear:

# Holy traits pattern definition => can be moved to the domain layer
abstract type Dispatcher end
struct T0 <: Dispatcher end # AppliMaster
struct T1 <: Dispatcher end # AppliInvoicing
struct T2 <: Dispatcher end # AppliGeneralLedger
struct T3 <: Dispatcher end # AppliInvoicing

# the data types on rx-channel
Dispatcher(::Type{<: String}) = T0()
Dispatcher(::Type{<: Array{AppliSales.Order, 1}}) = T1()
Dispatcher(::Type{<: Array{AppliGeneralLedger.JournalEntry,1}}) = T2()
Dispatcher(::Type{<: Array{AppliInvoicing.BankStatement,1}}) = T3()
# end Trait definition

function dispatcher()
    rx = Channel(32)

    # instantiate tasks
    tx0 = task_0(rx) # get orders from Sales
    tx1 = task_1(rx) # process the orders
    tx2 = task_2(rx) # process the journal entries
    tx3 = task_3(rx) # process the unpaid invoices

    # implementate Holy traits pattern
    dispatch(x::T) where {T} = dispatch(Dispatcher(T), x)

    dispatch(::T0, x) = put!(tx0, x)
    dispatch(::T1, x) = put!(tx1, x)
    dispatch(::T2, x) = put!(tx2, x)
    dispatch(::T3, x) = put!(tx3, x)

    @async while true
        if isready(rx)
            value = take!(rx)
            @info("Dispatcher received $(typeof(value))")
            dispatch(value)
        else
            wait(rx)
        end
    end

    return rx
end # dispatcher

03/20/2020 - Design Patterns and Best Practices with Julia

Recently I bought the book Design Patterns and Best Practices with Julia. I can recommend the book. After reading the chapter Modules, Packages, and Data Type Concepts, I decided to set up an abstract data tree, because it can give you a quick overview of your application. The branches are the abstract data types and the leaves the concrete data types. I chose Domain as an abstract root type.

Using the function subtypetree(Domain), I get an excellent overview of the data structure:

julia> subtypes(Invoice)
2-element Array{Any,1}:
 PaidInvoice  
 UnpaidInvoice

julia> subtypetree(Domain)
Domain
    Invoice
        PaidInvoice
        UnpaidInvoice
    Payment
        BankStatement
    Structure
        BodyItem
            OpentrainingItem
        Header
        MetaInvoice

julia> fieldnames(PaidInvoice)
(:id, :meta, :header, :body, :stm)

julia> fieldnames(UnpaidInvoice)
(:id, :meta, :header, :body)

See the Invoicing module documentation for more details.

Chapter 11 was not updated. I also got a message Page build failure.

The solution was to use relative links to switch between pages, e.g. ../chapter10/#.-Create-a-Dockerfile-in-the-folder-test_ssh-1.

03/11/2020 - Pages.jl and HTTP.jl

I did some tests with Pages.jl and HHTP.jl, The result is promising. The solution is to send a file name over an Http connection and use scp to retrieve the file from the Raspberry Pi for further processing.

The test

Julia 1.3.1 running on my laptop in a Ubuntu docker container as server. The variable result will contain the file name:

julia> using Pages

julia> using JSON

julia> @async Pages.start()
Task (runnable) @0x00007fb2c0ee3340

julia> result = ""
""

julia> Endpoint("/test", POST) do request::HTTP.Request
           data = String(request.body)
           global result = data
           response = JSON.json(Dict(:data => data))
       end
Endpoint(Dict{Symbol,HTTP.Handlers.RequestHandlerFunction}(:POST => HTTP.Handlers.RequestHandlerFunction{var"#11#12"}(var"#11#12"())), "/test")

Julia 1.3.1 running on my Raspberry Pi in a Ubuntu docker container as client:

julia> using HTTP

julia> r = HTTP.request("POST", "http://xxx.xxx.xxx.xxxxxxx:8000/test", [], "12345.jpg")
HTTP.Messages.Response:
"""
HTTP/1.1 200 OK
Transfer-Encoding: chunked

{"data":"12345.jpg"}"""

03/10/2020 - SQLite problem Raspberry Pi

There is a problem with making a connection from a docker container on my Ubuntu laptop to a container on a Raspberry Pi 3+. I posted a question on discourse.

See Addprocs gives connection refused to a docker container on raspberry pi

Now I consider using HTTP.jl (or Pages.jl) to inform the application to retrieve an image from my Raspi with scp.

03/04/2020 - Documentation AppliInvoicing module

I have completed the documentation for ApplInvoicing.jl

03/03/2020

Created documentation for the module AppliGeneralLedger.

Master code runs successfully in a Docker container.

02/27/2020 - dispatcher is working

SQLite gave problems with AppliGeneralLedger. I suspected it had something to do with multi-user issues. So I decided to experiment with Jula's open/read/write file functions. The result was very positive, so I changed the code in AppliGeneralLedger.

The test with remote channels went well. AppliMaster contains the code.

02/20/2020 - addproc(4)

In the past, I used Julia v1.1.0 and SQLite v0.8.x. I use a local channel with a dispatcher. It routes the data to the right task, which runs a Julia function remote. The application ran smoothly. The problem started when I switched to SQLite.jl v1.0.1. First, I thought that SQLite was causing the problem. However, the sample code uses only the AppliSales module, which doesn’t use SQLite.

I posted the problem on discourse.julialang.org: Distributed computing, not found package error. @pfitzseb gave the solution:

Try

addprocs(n_procs; exeflags=`--project=$(Base.active_project())`)

instead – the currently active environment doesn’t propagate to processes started by addprocs by default.

Now the application runs smoothly again. Thank you, `@pfitzseb.

02/07/2020

Chapter 4, 5 en 6 are ready for a final check.

02/05/2020

While working on the AppliMaster module, I got yesterday the impression that I should always be working with the local package repository, ] active .:

  • Adding packages will update Project.toml, the file with the dependencies, and update the local Manifest.toml file. When you delete Manifest.toml, you have to add the packages again.

  • When cloning a project works without adding the packages again.

So I removed AppliInvoicing, AppliGeneralLedger, and AppliSales from the general repository. This morning I started AppliMaster in Atom. My code in test_remote_channels.jl didn't work anymore. The two Databases invoicing.sqlite and ledger.sqlite were not created.

I added the packages AppliInvoicing, AppliGeneralLedger, and AppliSales again to the general repository, and my code was running well. The only explanation I could think of: remote code only looks at the general repository.

I need to review the documentation on this subject.

02/02/2020

The pages test_remote_channels.jl and myfunctions.jl, I have embellished with @info. The flow of the data has now become more explicit. The code is on github: test_remote_channels.jl.

Working with channels runs smoothly. I only run the functions that perform the operations on a different core. The while true loops for task_1 and task_2 tasks run locally. Should I also run these loops remotely?

So I had to renumber the subsequent chapters.

02/01/2020

Today I copied the blog from the AppliGate website to The course environment.

Things are going well with the course. The following modules are now ready(!):

  • AppliMaster - the basic module.
  • AppliInvoicing - the application itself.
  • AppliSales - sending test orders to AppliInvoicing.
  • AppliGeneralLedger - to process journal entries from AppliInvoicing.

In the last couple of weeks, I have been experimenting with the communication between the different parts. For the time being, I decided to work with Julia's channels. The implementation is in AppliMaster, testremotechannels.jl.

2019

12/05/2019

Last week I managed to establish an SSH connection between two containers. This week I could finish Chapter 9, Create the Container, more or less. The next step is to test a channel connection between two containers.

11/15/2019

Almost ready to link www.appligate.nl to rbontekoe.github.io. We have to change the CNAME record at the provider from appligate.appspot.com to rbontekoe.github.io.

We also started with writing the course material for the online training BAWJ.

10/21/2019

Why not build a website with Documenter.jl? We did it today. When you go to AppliGate, you enter our new site. In the meantime, we got more experience with Julia. E.g., we resized Rob's picture with Images.jl. It was straightforward.

julia> using Images

julia> img = load("rob.png")

julia> img2 = imresize(img, ratio=0.5)

julia> save("rob2.png", img2)

9/17/2019

Reading Think Julia I discovered the package Serialization. While playing with it, I realized that it could solve my CQRS and Event Sourcing problem. With the method serialize, you can create a text representation of an object, that you can store. With deserialize, you do the reverse. Serialize writes to an IOStream, and deserialize reads from it. An IOStream can be an IOBuffer or a file. It makes Event Sourcing easier.

I also looked at channels. You can use channels to communicate between processes. For the time being, I will use inter CPU-core communication. Later on, I extend it to remote nodes. I only have to expand the methods of createSubscriber and createPublisher.

9/10/2019

Last week I was busy implementing the database methods. I realized that the domain objects I wanted to keep must have an identification code. So Subscriber, Publisher, and Message got an id. To create the id, I used the hash function with a name and time as a parameter. It had consequences for the documentation. In the example code, I used the tag 'jldoctest,' so Documenter could test the example code during the generation of the manual. Because the ids keep changing, I had to give up. Too bad, because I found that a strong point of Documenter.jl. I replaced the test tag 'jldoctest' with the language tag 'julia.' Maybe there is something to do against continually changing values.

Infected by the CQRS thought, I decided to add changed domain objects to the database instead of modifying an existing record.