31 July 2023
20:30
I like to use Make (formerly Integromat) to avoid writing code, but after a few attempts at implementing data deserialization and reserialization, and parsing, I gave up. It's too primitive, and they no longer allow me to write custom functions. So, I'm porting my partial solution to Temporal, which is similar to what I have in Make, but with code. It basically allows me to manage tasks and workflows through a predefined communication protocol using gRPC.
My task is simple: I need to receive data from the Evo API (through webhooks) and push it to the Siigo API. Both solutions are similar to CRMs, but Evo is the user-facing solution.
I have a workflow in Make called "processSales" that takes the Evo sale ID through a webhook. I need to POST to a Siigo endpoint when this event occurs.
The first step is to fetch the Evo sale using its ID. To do that, I need to create a workflow that dispatches a single activity (so far) to fetch the sale. I'm working on that using PureScript on top of the Temporal TypeScript SDK.
https://github.com/klarkc/evo-siigo
20:33
The first version isn't working yet. I managed to dispatch the request, but the JSON isn't parsing for some reason. Also, this first attempt is bloated with unsafe code because I'm still trying to figure out how Temporal works. However, I have a general understanding of it. It's pretty powerful and safe because it isolates the context of each activity and workflow from each other.
1 August 2023
13:52
Today, I fixed the Evo authentication issue. The reason it wasn't working was that I was trying to parse an undefined body due to the lack of HTTP authentication. I had to build base64 encodings for the headers and made small changes to the API to adopt this dependency. I successfully got a response from the Evo API. Now, I'll try to parse the response to a PureScript type.
3 August 2023
10:34
I managed to parse the data into the Sale type, and it's working nicely now. The code still isn't perfect, but I'm more concerned with delivering the task right now. Today, I'll continue with other endpoints and maybe add Siigo endpoints.
21:58
I reworked the solution by creating a monad stack and internal state to store context information in the workflow. With that in place, I could use the loaded sale to fetch the customer.
11 August 2023
00:43
The monad stack has become too limited to manage environment dependencies. I'm building free monads (I extracted a common functor for both workflows and activities). Now, I want to build a way to proxy the dependencies in the free monad.
12 August 2023
21:17
I struggled a bit to deal with the Promise type. It took a few refactorings until I figured out how to fit things together. I'm composing WorkflowBuilder and Builder (lifting from Builder to WorkflowBuilder). I wrote a minimal example of parsing and encoding in "processSale". Now, I'll return to the proxy problem and see how I'll create ActivityBuilder.
21:21
After that, the next step is to encode environment things as commands, maybe in an EnvF.
12 August 2023
00:03
I finally finished the proxy. I did a rework on the underlying monad to Aff (because Promise is really hard to work with). Now, it's time to return to the environment.
22:43
Oh yeah, today I built a few more activities, and we're moving forward. I had a problem with functions in workflows (I forgot that workflows can only be pure functions, except for console logs), so our "env monad" becomes a little less useful. But it's there; I called it Temporal.Platform.OperationF.
22:49
And this is the activity that does authentication on Siigo. Maybe I should have used an interceptor for that, but given that I don't have much time, I'm going this way.
21 August 2023
11:50
Oh yeah, I forgot to update. We're close to reaching our goal. I'm doing a general refactor of module names because I messed up and mixed Node's modules with non-Node modules, which is causing bugs because workflows need to be deterministic. No big deal, just a whole naming refactor. After that, I can finish the first POST in Siigo's server.
22 August 2023
19:50
Look at that beauty:
addrFromCsvRow :: SearchSiigoAddressesParams -> Array String -> Maybe SiigoAddress
addrFromCsvRow p r = let eq_ s = guard <<< caselessMatch s in do
cityName <- r!! 2
cityName `eq_` p.cityName
stateName <- r!! 1
stateName `eq_` p.stateName
countryName <- r!! 0
countryCode <- r!! 3
( countryName `eq_` p.countryName
<|> countryCode `eq_` p.countryCode
)
stateCode <- r!! 4
cityCode <- r!! 5
pure { cityName
, stateName
, countryName
, countryCode: toUpper countryCode
, stateCode
, cityCode
}
I love it so much!
19:53
By the way, that refactor was just fine, no problems at all. I successfully POSTed a new invoice on Siigo, which I guess was the first goal. But I forgot that I should also add a customer (before the invoice, actually), so I'm doing that now. That's why I need this function to transform a CSV table row into a record that I can use to build up the customer data.
19:56
I have until Friday to finish this up, so let's rush things a bit. After creating the customer, I'll need to add Evo products to Siigo automatically (before creating the invoice), so they are kept in sync.
23 August 2023
20:15
What a productive day! We're closer than ever. The customer has been created successfully. What a journey; I loved it. So now, we're only missing the service creation, and then we're done. I'm thinking about what I'll do with this; I probably should add a license and publish Temporal.*
as a library. But I can't work for free right now; maybe in the next integration project, I can embed the cost of turning this into a public library.
20:17
By the way, addrFromCsvRow
had bugs, and I fixed them. I also added a performance improvement, as we don't need to process all array items; we can return as soon as we find a value.
25 August 2023
The project has been delivered, and everything is working like a charm. Now, I just need to deploy it. That's where Nix breaks; there's no place for flakes in cloud providers.