Advent of Code

I love December. Christmas, New Year, a week or 2 holidays, a look back at the year’s achievements and… AdventOfCode. AdventOfCode is an annual programming challenge consisting of puzzles that can be solved in any programming language. The event is aimed at people with different levels of programming skills and offers an opportunity to improve our skills and learn new techniques. Challenges are launched daily during the month of December until the 25th, at Christmas. There are always 2 problems and for each one solved you get a star.

I’ll be honest and say that until 2022 I solved some challenges but I was never consistent. This year I decided to take it seriously and by the 8th I had collected all 16 stars. And as I said above, I’m seeing it as a learning opportunity: every problem has an example with a solution, so here I am doing TDD, the classic red-green-refactor cycle; every day you download a text file as input and solve for each of the stars, so here I am with an automated template in node that generates a common structure of files and folders; Problems can be solved in several ways, so here I am calculating the performance of my solution and optimising; I’m publishing my answers day by day, so here I am commenting on my code so that my intentions are clear; As there is one issue per day, so here I am looking at each day as a release, I automated the generation of releases on Github using release-please.

My strategy every day has been the most naive, I try to solve everything by brute force. When it takes too long, I stop and think of something better. If it’s acceptable (well, it’s quite subjective here, anything running in less than 3 minutes, ok), I send the answers and at the end of the day I revisit the problem and reflect on whether there’s a better way to do it. For example, one day it was necessary to calculate paths using a map with instructions. My first approach was with a loop, after solving it I realised that I could use the least common multiple of the different paths. Nice!

Let’s see if I can keep up with the issues, it’s been a lot of fun, I highly recommend it!

Arguments and results

After returning from API Days in London, I’m studying design patterns for APIs. The last paper I read was “Arguments and Results” by James Noble (1), published in 1997 and still extremely relevant. Part of my current work, developing a data warehouse, is to optimize the transport of huge volumes of data. And if I want to use APIs, how can I do this efficiently? There is a consensus that REST API’s are not suitable in high-volume scenarios and studying this topic I arrived at Noble.

The article discusses patterns for object protocols and for this there are two groups:

  • How to send objects (Arguments): Arguments object, Selector object and Curried object
  • How to receive objects (Results): Result object, Future object and Lazy object

Patterns

  • Arguments object: This pattern is utilised to streamline a method’s signature by consolidating common arguments and objects, and modifying the method signature to accept this consolidated object instead. It brings several advantages, you make your domain explicit; your customers can use what they have at hand (the objects, instead of separated values); and you are able to separate the processing logic from your business logic

From

    void Send-FixedIncome(EntityName, IssueDate, MaturityDate, PrincipalAmount, ...) { }

To

    void Send-FixedIncome(FixedIncome) { }

“adding an eleventh argument to a message with ten arguments is qualitatively quite different to adding a second argument to a unary message”

  • Selector object: When you have methods with quite similar arguments, this patterns introduces a selector that enables you to pick one of them. Noble mentioned you could a enum, maybe using GoF a Flyweight would do the trick.

From

    void CalculateHomeLoanAmount(HomeLoan, Collateral) { }
    void CalculateHomeLoanAmount(HomeLoan) { }
    void CalculateAutoLoanAmount(AutoLoan) { }
    void CalculatePersonalLoanAmount(PersonalLoan) { }

To

    void CalculateAmount(LoanType, Loan) { } //Loan would be the interface for all loan types

“Protocols (APIs) where many messages perform similar functions are often difficult to learn and to use, especially as the similarity is often not obvious from the protocol’s documentation”

  • Curried object: from the functional programming world, currying breaks a function with several arguments into smaller functions, where each one of those functions takes some (usually one) of the original arguments; these functions are called in sequence then. Noble introduces this pattern for a scenario where the caller should not need to supply all arguments (e.g. constants), the method can supply on their behalf. In GoF terms, it acts as a proxy.

From

    void SettleDebt(Loan, ParcelsToPay) { } # no. of parcels can be retrieved from the loan 

To

    int GetOpenParcels(Loan) { }
    void SettleDebt(Loan) { () => Settle(Loan) } # in Settle no. of parcels is retrieved

“These kinds of arguments increase the complexity of a protocol. The protocol will be difficult to learn, as programmers must work out which arguments must be changed, and which must remain constant.”

  • Results object: For complex scenatios, one might need several method calls to generate the expected result. Your results object should be a single one with everything on it. This pattern can be seen as the flip side of the Curried Object pattern. One aspect worth mentioning is when those several calls mean several systems. In this case. a result object helps to reduce the coupling and acts as an abstraction around them.

“Perhaps the computation returns more than on object”

  • Future object: This pattern is employed when we need to execute a time-consuming operation and perform other tasks while waiting for the operation to complete. In essence, we aim to process a result asynchronously and then likely invoke a callback upon completion.

” Sometimes you need to ask a question, then do something else while waiting for the answer to arrive”

  • Lazy object: Sometimes you need to supply a result, though it is not 100% sure whether the method will be called. The advantage of this is that we don’t get data unnecessarily.

“Some computations can best be performed immediately but the computation’s result may never be needed”

All in all, these concepts are quite widespread. Any mainstream programming language has support for async methods (Future object) and lazy evaluation (Lazy object). Moreover, good OO design makes ‘Arguments object’ and ‘Seletor object’ quite naturally appear. Even though it was a good back-to-basics article to remind me the importance of good design in my API methods.

(1) https://www.researchgate.net/publication/2733549_Arguments_and_Results

API Days

This week I was in London attending the ApiDays event. Once a year I participate in an external event, in addition to studying alone, it is important to meet professional colleagues and talk about what happens in the trenches. To my surprise, AI was not the central topic, I watched around 30 lectures and only 4 were specifically about AI. Two major themes at the event:

API Governance: Emphasis given to the API lifecycle: definition->design->development-> testing->publication->operation. What I’ll take home:

  • The importance of documentation is the central point of your API, whether consumed by developers or read by robots.
  • The role of patterns in design. This is a growing market and your consumers expect you to follow OpenAPI, AsyncAPI, Semantic Versioning, HTTP response codes, Protocol Buffers definition language.
  • Your operation must provide freedom of choice, do not assume a cloud provider, do not force a gateway, be open.

Democratization of APIs: Here there are 2 views that are converging, on the one hand experts say that we should develop APIs thinking about devops and gitops. This vision places great importance on the governance aspects mentioned in the previous item; In addition, this vision highlights interoperability and composability as essential attributes for modern APIs. On the other hand, if we look at the most common composition of companies, only 10% are in the technology area (Gartner research shown in one of the lectures); We have 49% end users and 41% classified as business technologists. These 41% create technology or analysis solutions based on the solutions that the IT areas provide. They coined the term ‘post-API economy world’ to embrace these people who should have easy access to APIs. The simpler it is, the easier it will be for innovative products to emerge from the available information provided. This second vision focuses on open and public APIs, ecosystems and marketplaces.

I will mention 2 tools that I tested at the event and found fantastic:

Superface.ai: OK, there are API catalogs, we know the concept of observability, how do we deal with APIs that are similar but have different formats? Think of wttr.in and OpenWeatherMap, both of which allow you to see if it’s going to rain in London today. But the similarity ends there, it is necessary to write code for each of them. Furthermore, if one of them is unavailable, you are responsible for routing the order. Superface deals with this: (1) you say what your contract is (2) Superface has autonomous agents that discover APIs consistent with your contract and maps your contract to the contract of the APIs it found. (ok, there’s AI here🙂)

Postman mocking servers: I often use Postman to test APIs, and I discovered that you can prototype your API in it. Design your methods, specify contracts from examples, and Postman creates documentation and an endpoint. When I wanted to think about an API, I used httpbin, this is much cooler.

Cypress, new kid on the block

Selenium has been my go-to tool for UI test automation for a while, and for good reason. It was the front-runner, proving its worth time and time again. I’ve used it extensively but lately, I’ve leaned towards Cypress. I made a course on Cypress (all of the exercises can be found in my GH), I’ve become thoroughly convinced that it presents a more efficient and reliable alternative.

When it comes to analysing the differences between Selenium and Cypress, a few key factors stand out. Selenium supports multiple languages including Java, Python, C#, and others while Cypress is purely JavaScript. This could initially seem limiting but considering most web applications are now JavaScript-based, Cypress becomes a natural choice. Selenium tests run outside the browser while Cypress runs directly inside the browser. This lets Cypress take control of the entire automation process including network traffic, timers, and even the loading of JavaScript code. It’s this inner control that promises stability – a promise Selenium often can’t keep due to its reliance on numerous third-party factors. The possibility to test your automation with debugger options in the browser is game-changer, I spend a lot of time understanding my scripts using the console, styles and network tabs, I am sure FE developers are all very familiar with them.

Cypress provides a simple and comprehensive API to interact directly with the DOM, allowing you to write simple, effective tests, take a look:

describe('DOM Interactions Test', function() {
  it('Fills and submits a form', function() {
    cy.visit('https://www.example.com/form') // Cypress loads the webpage

    cy.get('input[name="firstName"]').type('John') // Cypress finds the input field firstName and types 'John' into it
    cy.get('input[name="lastName"]').type('Doe') // Cypress finds the input field lastName and types 'Doe' into it

    cy.get('button[type="submit"]').click() // Cypress clicks the submit button
  })
})

Compare this with Selenium

WebDriver driver = new ChromeDriver();

driver.get("https://www.example.com/form"); // Open URL 

WebElement firstName = driver.findElement(By.name("firstName"));
firstName.sendKeys("John"); // Fill the firstName input

WebElement lastName = driver.findElement(By.name("lastName"));
lastName.sendKeys("Doe"); // Fill the lastName input

WebElement submitButton = driver.findElement(By.tagName("button")); 
submitButton.submit(); // Submit the form

Cypress let you interact with CSS selectors!

Another key advantage of Cypress is its ability to handle asynchronous operations very easily.

describe('Asynchronous Handling Test', function() {
  it('Waits for an element to be visible', function() {
    cy.visit('https://www.example.com') // Cypress loads the webpage

    cy.get('#asyncButton', { timeout: 10000 }) // Cypress waits for the element with id 'asyncButton' to become visible for up to 10 seconds
      .should('be.visible') // Asserts that the element should be visible
      .click() // Clicks the button once it's visible
  })
})

Dealing with API’s is also a breeze:

describe("Todo List API Interaction", () => {
  // Define the base URL of your API
  const apiUrl = "https://example-api.com";

  beforeEach(() => {
    // Intercept the API call and load a JSON payload for the response
    cy.intercept("GET", `${apiUrl}/todos`, { fixture: "todos.json" }).as("getTodos");
    // Visit the application or a specific page where the API call is made
    cy.visit("/todo-list");
  });

  it("should fetch todos from the API and display them", () => {
    // Perform any actions that trigger the API call (e.g., click a button to fetch todos)
    cy.get("#fetch-todos-btn").click();

    // Wait for the API call to complete and the response to be displayed
    cy.wait("@getTodos");

    // Assert that the correct API endpoint was called and the response was handled properly
    cy.get(".todo-item").should("have.length", 3); // Assuming the response contains 3 todo items
  });
});

What to me is the big advantage is my caveat: interacting with CSS selectors demands that you be careful to choose those that are least likely to change and break your tests. Still, I only plan to use Cypress for my UI tests.

Bloom Filters

One of my favorite data structures to use for efficient search operations is the Bloom filter, I just came across this cool demo, check it out.

Named after its creator, Burton Howard Bloom, a Bloom filter is a space-efficient probabilistic data structure designed to answer a simple question: “Is this item in the set?”. Unlike other data structures, a Bloom filter trades off accuracy for speed and space efficiency, meaning it might sometimes return false positives but never false negatives.

The Bloom filter works by using a bit vector of size ‘m’ and ‘k’ hash functions. When an element is added to the filter, it is passed through all ‘k’ hash functions, which return ‘k’ indexes to set to ‘1’ in the bit vector. To check whether an element is in the filter, the same ‘k’ hash functions are applied. If all ‘k’ indexes in the bit vector are ‘1’, the filter returns ‘yes’; otherwise, it returns ‘no’. A ‘yes’ answer can mean either the element is definitely not in the set (true negative) or it might be in the set (false positive), but a ‘no’ answer always means the element is not in the set (true negative). Note that when implementing, you should think carefully about how many hash functions to use and what the acceptable rate of false positives is.

Bloom filters are widely used in software applications where the cost of false positives is less critical than the benefits of speed and space efficiency. Some of these applications include spell checkers, network routers, databases, and caches. For instance, Google’s Bigtable uses Bloom filters to reduce the disk lookups for non-existent rows or columns, significantly improving its performance. Medium uses Bloom filters to avoid recommending articles a user has already read. I have a password generator application and use a Bloom Filter to check if a password has already been used.