Skip to main content

The QCAT Standard: Generating the QR Code



In this guide, we’ll explore how to generate the QCAT QR code by creating a custom TLV (Tag-Length-Value) structure in Kotlin. TLV encoding is commonly used in QR codes, smart cards, and other systems where data must be serialized into a compact, structured format. We'll also discuss encoding techniques, including converting date-times, durations, and ASCII values into hex format.

Overview of the Code

This Kotlin code focuses on generating a TLV structure that encodes various data fields such as a ticket ID, creator ID, creation timestamp, and a validity period into a hex-based format. Finally, it converts the TLV structure into a Base64 encoded string for easy transmission.

The structure involves the following major steps:

  • Hex Conversion: Converting data types like date-time, durations, and ASCII strings to hexadecimal.
  • TLV Encoding: Wrapping each piece of data with a tag, its length, and value.
  • Serialization: Serializing the data and converting it to a byte array for encoding.
  • Base64 Encoding: Final step where the TLV structure is Base64 encoded.
We will be using the sample AF Payments Inc. for minimum required fields needed to generate a QR Code for this guide. If you’d like to learn more or check it in details, you may refer to their Github.

Also, to make the guide a bit shorter, we will be directly using their sample signature for building the TLV structure. To learn more on the signature creation you may refer the following guides:

TLV Sealed Class

This sealed class defines two types of TLV objects: SimpleTLV for basic key-value pairs and ConstructedTLV for nested TLV structures. Both types implement a serialize() function, returning a formatted hex string representing the TLV data.

sealed class TLV(val tag: String?, val length: String?) {
    abstract fun serialize(): String
}

class SimpleTLV(tag: String?, length: String?, val value: String) : TLV(tag, length) {
    override fun serialize(): String {
        if (tag != null && length != null) {
            return String.format("%s%s%s", tag, length, value)
        } else {
            return String.format("%s", value)
        }
    }
}

class ConstructedTLV(tag: String? = null, length: String? = null, val value: List<TLV>) : TLV(tag, length) {
    override fun serialize(): String {
        val nestedValues = value.joinToString("") { it.serialize() }
        if (tag != null && length != null) {
            return String.format("%s%s%s", tag, length, nestedValues)
        } else {
            return String.format("%s", nestedValues)
        }
    }
}

Key Functions

Duration To Hex String

This set of functions converts an ISO 8601 duration (e.g., "PT15M" for 15 minutes) into its equivalent hex representation. The conversion is done by parsing the duration, converting it to seconds, and then representing that value in hexadecimal.

fun String.toISO8601Duration(): Duration {
    return Duration.parse(this)
}

fun Duration.toHexString(): String {
    return this.seconds.toString(16).uppercase().padStart(4, '0')
}

Date to Hex String

This set of functions converts an ISO 8601 date-time string into Unix timestamp (seconds since epoch) and then converts it into hex format. The date-time is parsed into an OffsetDateTime, and its epoch second value is then converted into hex.

fun String.toISO8601DateTime(): OffsetDateTime {
    return OffsetDateTime.parse(this, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

fun OffsetDateTime.toHexString(): String {
    return this.toEpochSecond().toString(16).uppercase()
}

Integer to Hex String

The function converts an integer to an uppercase hexadecimal string, ensuring even length by prepending a "0" if needed.

fun Int.toHexString(): String {
    return this.toString(16).uppercase().let { if (it.length % 2 != 0) "0$it" else it }
}

ASCII to Hex String

The function converts an ASCII string to its hexadecimal representation.

fun String.toHexString(): String {
    return this.map { "${it.code.toString(16).uppercase()}" }.joinToString("") 
}

Hex String to Byte Array

The function converts a hexadecimal string into a byte array. It checks that the input string has an even length (as each byte is represented by two hex characters) and ensures all characters are valid hexadecimal digits. Each pair of hex characters is combined into a byte, and the result is returned as a byte array.

fun String.hexStringToByteArray(): ByteArray {
    require(this.length % 2 == 0) { "hex string must have an even length" }

    val len = this.length
    val data = ByteArray(len / 2)
    var i = 0

    while (i < len) {
        val firstChar = Character.digit(this[i], 16)
        val secondChar = Character.digit(this[i + 1], 16)
        
        require(firstChar != -1 && secondChar != -1) { "invalid hex character at index $i" }
        
        data[i / 2] = ((firstChar shl 4) + secondChar).toByte()

        i += 2
    }
    return data
}

Size/Length to Hex String

This set of functions handles encoding the length of a TLV entry, accounting for different lengths. If the length is under 128 bytes, a single-byte encoding is used. For longer lengths, multi-byte encoding is applied.

fun ByteArray.sizeToHexString(): String {
    return encodeLengthToHexString(this.size)
}

fun Int.lengthToHexString(): String {
    return encodeLengthToHexString(this)
}

private fun encodeLengthToHexString(length: Int): String {
    val bytes = when {
        length < 128 -> byteArrayOf(length.toByte()) // 1-byte length encoding
        length <= 255 -> byteArrayOf(0x81.toByte(), length.toByte()) // 2-byte length encoding
        else -> byteArrayOf(0x82.toByte(), (length shr 8).toByte(), length.toByte()) // 3-byte length encoding
    }
    
    return bytes.joinToString("") { String.format("%02X", it) }
}

To understand more about this implementation you may check out my guide below:

Create TLV Structure

This functionality builds the entire TLV structure by defining fields like ticket id, creator id, creation time, and validity period. Each field is converted to a TLV format, and the lengths of the values are encoded as per TLV rules.

object FieldTag {
    const val PAYLOAD_FORMAT_INDICATOR = "85"
    const val APPLICATION_TEMPLATE = "61"
    const val ADF = "4F"
    const val APPLICATION_SPECIFIC_TRANSPARENT_TEMPLATE = "63"
    
    const val TICKET_ID = "C1"
    const val CREATOR_ID = "C2"
    const val CREATION_TIME = "C3"
    const val VALIDITY_PERIOD = "C4"
    
    const val SIGNATURE = "DE"
}

fun createTLVStructure(
    ticketId: Int = 1250184,
    creatorId: Int = 275,
    creationTime: String = "2019-09-11T19:38:30+08:00",
    validityPeriod: String = "PT15M",
): TLV {
    val ticketIdHex = ticketId.toHexString()
    val creatordIdHex = creatorId.toHexString()
    val creationTimeHex = creationTime.toISO8601DateTime().toHexString()
    val validityPeriodHex = validityPeriod.toISO8601Duration().toHexString()
    val signatureHex = "023034021859527B7951E77EB6CB250149FFA2006B1A415297D13AA48A021840986DC05DB2235088DB4599389823A324842E73A635B3FD"
    
    val adf = "QCAT01"
    val adfHex = adf.toHexString()

    val appSpecTransTemplate = 
        listOf( 
            SimpleTLV(
                tag = FieldTag.TICKET_ID, 
                length = ticketIdHex.hexStringToByteArray().sizeToHexString(), 
                value = ticketIdHex
            ), 
            SimpleTLV(
                tag = FieldTag.CREATOR_ID, 
                length = creatordIdHex.hexStringToByteArray().sizeToHexString(), 
                value = creatordIdHex
            ), 
            SimpleTLV(
                tag = FieldTag.CREATION_TIME, 
                length = creationTimeHex.hexStringToByteArray().sizeToHexString(), 
                value = creationTimeHex
            ), 
            SimpleTLV(
                tag = FieldTag.VALIDITY_PERIOD, 
                length = validityPeriodHex.hexStringToByteArray().sizeToHexString(), 
                value = validityPeriodHex
            ), 
            SimpleTLV(
                tag = FieldTag.SIGNATURE, 
                length = signatureHex.hexStringToByteArray().sizeToHexString(), 
                value = signatureHex
            ), 
        )
  
    val appTemplate = 
        listOf(
            SimpleTLV(
                tag = FieldTag.ADF, 
                length = adfHex.hexStringToByteArray().sizeToHexString(), 
                value = adfHex
            ), 
            ConstructedTLV(
                tag = FieldTag.APPLICATION_SPECIFIC_TRANSPARENT_TEMPLATE, 
                length = appSpecTransTemplate.sumOf { it.serialize().length / 2 }.lengthToHexString(), 
                value = appSpecTransTemplate
            ) 
        )  
    
    val payloadFormatIndicator = "CPV01"
    val payloadFormatIndicatorHex = payloadFormatIndicator.toHexString()
    
    return ConstructedTLV(null, null, listOf(
        SimpleTLV(
            tag = FieldTag.PAYLOAD_FORMAT_INDICATOR, 
            length = payloadFormatIndicatorHex.hexStringToByteArray().sizeToHexString(), 
            value = payloadFormatIndicatorHex
        ), 
        ConstructedTLV(
            tag = FieldTag.APPLICATION_TEMPLATE, 
            length = appTemplate.sumOf { it.serialize().length / 2 }.lengthToHexString(), 
            value = appTemplate
        ) 
    ))
}

Full Code Example

Here’s a complete code demonstrating the use of all the key functions in which you can try using the Kotlin Playground

import kotlin.test.*
import java.util.Base64
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.time.Duration

fun main() {
    val tlv = createTLVStructure()
    val serializedTLV = tlv.serialize()
    val base64EncodedData = Base64.getEncoder().encodeToString(serializedTLV.hexStringToByteArray())

    println("Serialized TLV: $serializedTLV")
    println("QR Code Data: $base64EncodedData")

    // Generate QR code using the chosen library
}

sealed class TLV(val tag: String?, val length: String?) {
    abstract fun serialize(): String
}

class SimpleTLV(tag: String?, length: String?, val value: String) : TLV(tag, length) {
    override fun serialize(): String {
        if (tag != null && length != null) {
            return String.format("%s%s%s", tag, length, value)
        } else {
            return String.format("%s", value)
        }
    }
}

class ConstructedTLV(tag: String? = null, length: String? = null, val value: List<TLV>) : TLV(tag, length) {
    override fun serialize(): String {
        val nestedValues = value.joinToString("") { it.serialize() }
        if (tag != null && length != null) {
            return String.format("%s%s%s", tag, length, nestedValues)
        } else {
            return String.format("%s", nestedValues)
        }
    }
}

fun String.toISO8601Duration(): Duration {
    return Duration.parse(this)
}

fun Duration.toHexString(): String {
    return this.seconds.toString(16).uppercase().padStart(4, '0')
}

fun String.toISO8601DateTime(): OffsetDateTime {
    return OffsetDateTime.parse(this, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

fun OffsetDateTime.toHexString(): String {
    return this.toEpochSecond().toString(16).uppercase()
}

fun Int.toHexString(): String {
    return this.toString(16).uppercase().let { if (it.length % 2 != 0) "0$it" else it }
}

fun String.toHexString(): String {
    return this.map { "${it.code.toString(16).uppercase()}" }.joinToString("") 
}

fun String.hexStringToByteArray(): ByteArray {
    require(this.length % 2 == 0) { "hex string must have an even length" }

    val len = this.length
    val data = ByteArray(len / 2)
    var i = 0

    while (i < len) {
        val firstChar = Character.digit(this[i], 16)
        val secondChar = Character.digit(this[i + 1], 16)
        
        require(firstChar != -1 && secondChar != -1) { "invalid hex character at index $i" }
        
        data[i / 2] = ((firstChar shl 4) + secondChar).toByte()

        i += 2
    }
    return data
}

fun ByteArray.sizeToHexString(): String {
    return encodeLengthToHexString(this.size)
}

fun Int.lengthToHexString(): String {
    return encodeLengthToHexString(this)
}

private fun encodeLengthToHexString(length: Int): String {
    val bytes = when {
        length < 128 -> byteArrayOf(length.toByte()) // 1-byte length encoding
        length <= 255 -> byteArrayOf(0x81.toByte(), length.toByte()) // 2-byte length encoding
        else -> byteArrayOf(0x82.toByte(), (length shr 8).toByte(), length.toByte()) // 3-byte length encoding
    }
    
    return bytes.joinToString("") { String.format("%02X", it) }
}

object FieldTag {
    const val PAYLOAD_FORMAT_INDICATOR = "85"
    const val APPLICATION_TEMPLATE = "61"
    const val ADF = "4F"
    const val APPLICATION_SPECIFIC_TRANSPARENT_TEMPLATE = "63"
    
    const val TICKET_ID = "C1"
    const val CREATOR_ID = "C2"
    const val CREATION_TIME = "C3"
    const val VALIDITY_PERIOD = "C4"
    
    const val SIGNATURE = "DE"
}

fun createTLVStructure(
    ticketId: Int = 1250184,
    creatorId: Int = 275,
    creationTime: String = "2019-09-11T19:38:30+08:00",
    validityPeriod: String = "PT15M",
): TLV {
    val ticketIdHex = ticketId.toHexString()
    val creatordIdHex = creatorId.toHexString()
    val creationTimeHex = creationTime.toISO8601DateTime().toHexString()
    val validityPeriodHex = validityPeriod.toISO8601Duration().toHexString()
    val signatureHex = "023034021859527B7951E77EB6CB250149FFA2006B1A415297D13AA48A021840986DC05DB2235088DB4599389823A324842E73A635B3FD"
    
    val adf = "QCAT01"
    val adfHex = adf.toHexString()

    val appSpecTransTemplate = 
        listOf( 
            SimpleTLV(
                tag = FieldTag.TICKET_ID, 
                length = ticketIdHex.hexStringToByteArray().sizeToHexString(), 
                value = ticketIdHex
            ), 
            SimpleTLV(
                tag = FieldTag.CREATOR_ID, 
                length = creatordIdHex.hexStringToByteArray().sizeToHexString(), 
                value = creatordIdHex
            ), 
            SimpleTLV(
                tag = FieldTag.CREATION_TIME, 
                length = creationTimeHex.hexStringToByteArray().sizeToHexString(), 
                value = creationTimeHex
            ), 
            SimpleTLV(
                tag = FieldTag.VALIDITY_PERIOD, 
                length = validityPeriodHex.hexStringToByteArray().sizeToHexString(), 
                value = validityPeriodHex
            ), 
            SimpleTLV(
                tag = FieldTag.SIGNATURE, 
                length = signatureHex.hexStringToByteArray().sizeToHexString(), 
                value = signatureHex
            ), 
        )
  
    val appTemplate = 
        listOf(
            SimpleTLV(
                tag = FieldTag.ADF, 
                length = adfHex.hexStringToByteArray().sizeToHexString(), 
                value = adfHex
            ), 
            ConstructedTLV(
                tag = FieldTag.APPLICATION_SPECIFIC_TRANSPARENT_TEMPLATE, 
                length = appSpecTransTemplate.sumOf { it.serialize().length / 2 }.lengthToHexString(), 
                value = appSpecTransTemplate
            ) 
        )  
    
    val payloadFormatIndicator = "CPV01"
    val payloadFormatIndicatorHex = payloadFormatIndicator.toHexString()
    
    return ConstructedTLV(null, null, listOf(
        SimpleTLV(
            tag = FieldTag.PAYLOAD_FORMAT_INDICATOR, 
            length = payloadFormatIndicatorHex.hexStringToByteArray().sizeToHexString(), 
            value = payloadFormatIndicatorHex
        ), 
        ConstructedTLV(
            tag = FieldTag.APPLICATION_TEMPLATE, 
            length = appTemplate.sumOf { it.serialize().length / 2 }.lengthToHexString(), 
            value = appTemplate
        ) 
    ))
}

Alternatively, you can opt not to use the .hexStringToByteArray().sizeToHexString() approach, see below full code example for reference:

import kotlin.test.*
import java.util.Base64
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.time.Duration

fun main() {
    val tlv = createTLVStructure()
    val serializedTLV = tlv.serialize()
    val base64EncodedData = Base64.getEncoder().encodeToString(serializedTLV.hexStringToByteArray())

    println("Serialized TLV: $serializedTLV")
    println("QR Code Data: $base64EncodedData")

    // Generate QR code using the chosen library
}

sealed class TLV(val tag: String?, val length: String?) {
    abstract fun serialize(): String
}

class SimpleTLV(tag: String?, length: String?, val value: String) : TLV(tag, length) {
    override fun serialize(): String {
        if (tag != null && length != null) {
            return String.format("%s%s%s", tag, length, value)
        } else {
            return String.format("%s", value)
        }
    }
}

class ConstructedTLV(tag: String? = null, length: String? = null, val value: List<TLV>) : TLV(tag, length) {
    override fun serialize(): String {
        val nestedValues = value.joinToString("") { it.serialize() }
        if (tag != null && length != null) {
            return String.format("%s%s%s", tag, length, nestedValues)
        } else {
            return String.format("%s", nestedValues)
        }
    }
}

fun String.toISO8601Duration(): Duration {
    return Duration.parse(this)
}

fun Duration.toHexString(): String {
    return this.seconds.toString(16).uppercase().padStart(4, '0')
}

fun String.toISO8601DateTime(): OffsetDateTime {
    return OffsetDateTime.parse(this, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

fun OffsetDateTime.toHexString(): String {
    return this.toEpochSecond().toString(16).uppercase()
}

fun Int.toHexString(): String {
    return this.toString(16).uppercase().let { if (it.length % 2 != 0) "0$it" else it }
}

fun String.toHexString(): String {
    return this.map { "${it.code.toString(16).uppercase()}" }.joinToString("") 
}

fun String.hexStringToByteArray(): ByteArray {
    require(this.length % 2 == 0) { "hex string must have an even length" }

    val len = this.length
    val data = ByteArray(len / 2)
    var i = 0

    while (i < len) {
        val firstChar = Character.digit(this[i], 16)
        val secondChar = Character.digit(this[i + 1], 16)
        
        require(firstChar != -1 && secondChar != -1) { "invalid hex character at index $i" }
        
        data[i / 2] = ((firstChar shl 4) + secondChar).toByte()

        i += 2
    }
    return data
}

fun Int.lengthToHexString(): String {
    val bytes = when {
        this < 128 -> byteArrayOf(this.toByte()) // 1-byte length encoding
        this <= 255 -> byteArrayOf(0x81.toByte(), this.toByte()) // 2-byte length encoding
        else -> byteArrayOf(0x82.toByte(), (this shr 8).toByte(), this.toByte()) // 3-byte length encoding
    }
    
    return bytes.joinToString("") { String.format("%02X", it) }
}

object FieldTag {
    const val PAYLOAD_FORMAT_INDICATOR = "85"
    const val APPLICATION_TEMPLATE = "61"
    const val ADF = "4F"
    const val APPLICATION_SPECIFIC_TRANSPARENT_TEMPLATE = "63"
    
    const val TICKET_ID = "C1"
    const val CREATOR_ID = "C2"
    const val CREATION_TIME = "C3"
    const val VALIDITY_PERIOD = "C4"
    
    const val SIGNATURE = "DE"
}

fun createTLVStructure(
    ticketId: Int = 1250184,
    creatorId: Int = 275,
    creationTime: String = "2019-09-11T19:38:30+08:00",
    validityPeriod: String = "PT15M",
): TLV {
    val ticketIdHex = ticketId.toHexString()
    val creatordIdHex = creatorId.toHexString()
    val creationTimeHex = creationTime.toISO8601DateTime().toHexString()
    val validityPeriodHex = validityPeriod.toISO8601Duration().toHexString()
    val signatureHex = "023034021859527B7951E77EB6CB250149FFA2006B1A415297D13AA48A021840986DC05DB2235088DB4599389823A324842E73A635B3FD"
    
    val adf = "QCAT01"
    val adfHex = adf.toHexString()

    val appSpecTransTemplate = 
        listOf( 
            SimpleTLV(
                tag = FieldTag.TICKET_ID, 
                length = (ticketIdHex.length / 2).lengthToHexString(), 
                value = ticketIdHex
            ), 
            SimpleTLV(
                tag = FieldTag.CREATOR_ID, 
                length = (creatordIdHex.length / 2).lengthToHexString(), 
                value = creatordIdHex
            ), 
            SimpleTLV(
                tag = FieldTag.CREATION_TIME, 
                length = (creationTimeHex.length / 2).lengthToHexString(), 
                value = creationTimeHex
            ), 
            SimpleTLV(
                tag = FieldTag.VALIDITY_PERIOD, 
                length = (validityPeriodHex.length / 2).lengthToHexString(), 
                value = validityPeriodHex
            ), 
            SimpleTLV(
                tag = FieldTag.SIGNATURE, 
                length = (signatureHex.length / 2).lengthToHexString(), 
                value = signatureHex
            ), 
        )
  
    val appTemplate = 
        listOf(
            SimpleTLV(
                tag = FieldTag.ADF, 
                length = (adfHex.length / 2).lengthToHexString(), 
                value = adfHex
            ), 
            ConstructedTLV(
                tag = FieldTag.APPLICATION_SPECIFIC_TRANSPARENT_TEMPLATE, 
                length = appSpecTransTemplate.sumOf { it.serialize().length / 2 }.lengthToHexString(), 
                value = appSpecTransTemplate
            ) 
        )  
    
    val payloadFormatIndicator = "CPV01"
    val payloadFormatIndicatorHex = payloadFormatIndicator.toHexString()
    
    return ConstructedTLV(null, null, listOf(
        SimpleTLV(
            tag = FieldTag.PAYLOAD_FORMAT_INDICATOR, 
            length = (payloadFormatIndicatorHex.length / 2).lengthToHexString(), 
            value = payloadFormatIndicatorHex
        ), 
        ConstructedTLV(
            tag = FieldTag.APPLICATION_TEMPLATE, 
            length = appTemplate.sumOf { it.serialize().length / 2 }.lengthToHexString(), 
            value = appTemplate
        ) 
    ))
}

The code might not be perfect, so feel free to make any adjustments you prefer. If you’d like to share your modifications with the community, please don’t hesitate to leave a comment below!

Conclusion

This Kotlin implementation demonstrates how to create a TLV structure, serialize it, and encode it in Base64. The code can be adapted for various use cases, such as generating QR codes, working with smart cards, or handling serialized data in payment systems.

By converting various data types (like dates, durations, and ASCII strings) into their hexadecimal representations, and combining them into a structured format, this TLV encoder can be used in many real-world applications requiring structured data serialization.

If you’d like to learn more or get involved in the QCAT ecosystem, feel free to check out my guides on QCAT specification below or check out AF Payments Inc.'s GitHub for the latest updates and contribution guidelines.

Don't forget to subscribe to my blog so you never miss out on my latest guides and content!

DISCLAIMER

The information provided in this article is for informational purposes only and does not constitute professional advice. While efforts have been made to ensure accuracy, AF Payments Inc. reserves the right to update or modify the QCAT standard and related materials at any time. Use of the QCAT standard is subject to the terms of its license agreement, and any implementation must adhere to AFPI's guidelines and licensing requirements. For the latest details and official documentation, please refer to AF Payments Inc.'s authorized channels.

Comments

Popular posts from this blog

Understanding Number Systems: Decimal, Binary, and Hexadecimal

In everyday life, we use numbers all the time, whether for counting, telling time, or handling money. The number system we’re most familiar with is the   decimal system , but computers use other systems, such as   binary   and   hexadecimal . Let’s break down these number systems to understand how they work. What is a Number System? A number system is a way of representing numbers using a set of symbols and rules. The most common number systems are: Decimal (Base 10) Binary (Base 2) Hexadecimal (Base 16) Each system has a different “base” that tells us how many unique digits (symbols) are used to represent numbers. Decimal Number System (Base 10) This is the system we use daily. It has  10 digits , ranging from  0 to 9 . Example: The number  529  in decimal means: 5 × 1⁰² + 2 × 1⁰¹ + 9 × 1⁰⁰ =  500 + 20 + 9 = 529 Each position represents a power of 10, starting from the rightmost digit. Why Base 10? Decimal is base 10 because it has 10 digits...

How to Monetize Your API as an Individual Developer While Hosting on Your Own Server?

In the API economy, cloud services like AWS, Google Cloud, and Azure offer many conveniences, such as scaling and infrastructure management. However, some developers prefer more control and autonomy, opting to host their APIs on personal servers. Whether for cost efficiency, data privacy, or customization, hosting your own API comes with both advantages and challenges. But, even without cloud platforms, there are effective ways to monetize your API. This guide will explore how individual developers can successfully monetize their APIs while hosting them on their own servers. Why Host Your API on Your Own Server? Hosting your own API gives you full control over the infrastructure and potentially lower long-term costs. Here’s why some developers choose this approach: Cost Control : Instead of paying ongoing cloud fees, you may opt for a one-time or lower-cost hosting solution that fits your budget and resource needs. Data Ownership : You have full control over data, which is critical if ...

The Weight of Responsibility: A Developer’s Journey to Balance Passion and Reality

For the past several years, Eddie has been on a steady climb in his career as a developer, but recently, he found himself at a crossroads — caught between the weight of his responsibilities and the desire to pursue his true passions. His journey began with a three-month internship as a web developer, which led to nearly four years in an application developer role. After that, he spent almost a year as a systems associate, managing tasks across systems analysis, quality assurance, and business analysis. Eventually, he returned to full-time software development for another two years before transitioning into more complex roles. For over a year, he worked as a multi-role software developer and database administrator before stepping into his current position as a senior software developer, database administrator, and cloud administrator — occasionally handling security tasks as well. Now, with over 8 years of professional experience, he also leads a small team of developers, which has been...

The Hidden Costs of Overdesign and Bad Practices in API Systems

In software development, simplicity and clarity are often sacrificed in favor of overly complex solutions. While it can be tempting to add more features and intricate designs to ensure robustness, overdesign and poor practices can have significant consequences. They frustrate developers, lead to inefficiencies, increase costs, and put unnecessary strain on system resources.  A recent example involving a team that has faced challenges with complexity highlights the pitfalls of such an approach. Overdesign: The Problem of Too Much Complexity Overdesign occurs when systems are built with more complexity than necessary. This might manifest in bloated APIs, convoluted data flows, or excessive checks and processes that don’t add substantial value. The goal is often to anticipate future problems, but this approach typically results in cumbersome systems that are difficult to maintain and scale. In one case, a company found itself paying a hefty price just to host two API services and a po...

Selenium for Beginners: What, Where, When, and Why to Use It in Automated Testing

In today’s software development landscape, automated testing has become essential for delivering robust applications efficiently. Among various automated testing tools,   Selenium   stands out as one of the most widely used and beginner-friendly options. As you embark on your journey into automated testing, it’s crucial to understand the   what, where, when, and why   of using Selenium. In this guide we will run through these essentials and help you decide if Selenium is the right tool for you. What is Selenium? Selenium  is an open-source framework used primarily for automating web browsers. It enables developers and testers to write scripts that interact with websites, simulating actions like clicking buttons, filling out forms, and navigating pages, which allows for comprehensive automated testing. Selenium supports multiple programming languages, including Python, Java, C#, and JavaScript, making it flexible for teams with different coding preferences. Key C...