Discussions about Technology within a Caribbean Context.

Programming Matthew Budram Programming Matthew Budram

Introduction to Dart 3.x

Dart was intended to be a language to supplant JavaScript on the web. Dart made up for a lot of the shortcomings of JavaScript including static types and recently better null protection. Microsoft beat Google to the punch and its use of TypeScript on the web has proliferated a large portion of web-based applications.

Dart would have died if Flutter didn’t save it from its fate and chances are if you are here reading this blog post you are more interested in building Flutter applications than running Dart on the web. Don’t get me wrong, Dart has amazing features which personally I would have loved in other languages including Just-In-Time compilation to help with it’s lightning fast reloads when debugging.

During this introduction, take some time to explore and experiment with the various features of the language using DartPad. It is a user-friendly and freely accessible platform that enables you to promptly receive feedback as you test out different aspects of the language.

Data Types

The declaration of a data type is pretty much similar syntactically to most languages including JavaScript and TypeScript. The basic structure of the declaration is as follows:

type identifier = value;

In Dart the primitive/built-in data types are:

  • Numbers (int, double)

  • Strings (String)

  • Booleans (bool)

  • Records ((value1, value2))

  • Lists (List, also known as arrays)

  • Sets (Set)

  • Maps (Map)

  • Runes (Runes; often replaced by the characters API)

  • Symbols (Symbol)

The identifier or variable name has a few rules:

  • Special characters such as underscores, exclamation marks and spaces are not allowed in variable names

  • Must not be preceded by a number

  • keywords are not allowed

Type Inference

Dart, despite being a statically typed language, allows for some type inference. This can be seen in the code block below:

var value = "My Name is John";
print(value);  // outputs -> My Name is John

This is perfectly acceptable and the compiler will make accomodations for the value variable to be treated as a String.

var value = "My Name is John";
print(value);
  
value = 20;

If we attempt to assign a different type value to the previously assigned string the compiler will respond with the following error:

compileDDC
main.dart:5:11: Error: A value of type 'int' can't be assigned to a variable of type 'String'.
  value = 20;
          ^

Variables

Variables store references and a fundamental to all programming languages. These references are integral to making decisions within your app.

Null Safety

Dart protects developers from themselves. The dart compiler is able to detect and stop compilation in the event that an expression may evaluate to null. Ask any JavaScript developer; one of the worst things to happen is a null reference during runtime. Dart enables null safety with 3 main changes:

  • Explicitly specifying that a variable is “nullable”. This means if a value has the potential to evaluate to null, then it is nullable. This is accomplished by adding a ? character to the end of the type declaration.
    String? name // name can be null or string

  • Variables must be initialized before using them.

  • Calls to an object’s methods cannot be made with a nullable type.

If a variable is nullable there will be additional work expected of the developer when attempting to access the value. Examine the following function:

void main() {
  int? value;
  print(value + 2);
}

The compiler would emit the following error during compilation:

compileDDC
main.dart:3:15: Error: Operator '+' cannot be called on 'int?' because it is potentially null.
  print(value + 2);

As good practice, it forces developers to check first if the value is null before attempting an operation with a null value. To fix this issue the following adjustments are made:

void main() {
  int? value;
  if(value != null){
    print(value + 2);
  }
}

Late variables

In many cases, a variable needs to be set later after being declared. Some of these situations include:

  • Initialization is expensive and takes some time/effort

  • Value is unavailable at the time it is declared

Dart accommodates these cases by allowing developers to use the keyword late and tell the compiler to initialize when needed. Example:

// This is the program's only call to readThermometer().
late String temperature = readThermometer(); // Lazily initialized.

In the example, if the temperature variable is never used, then the expensive readThermometer() function is never called.

final and const

For the Java Developers amongst us, these concepts should be familiar. For variables that never change values we have two options in Dart:

  • final allows us to set a variable to a value once and only once.
    final name = “Bob”; // Without a type annotation (implicit type of String)
    final String nickname = “Bobby”; // With a type
    If we attempted to set the values above after the first initialization the compiler will fail.

  • const allows us to set a variable to a value during compilation and are therefore implicitly final.
    const bar = 1000000; // Unit of pressure (dynes/cm2)
    const double atm = 1.01325 * bar; // Standard atmosphere

Data Structures

Most programming languages support a set of basic, but powerful constructs that allow for the temporary storage of information that is intended to be used to manipulate its contents and later present the details.

Lists

Each language presents their own implementation of some collection of ordered objects. In the case of Dart, we introduce lists. They are denoted by a set of square brackets []:

var list = [100, 122, 334];
print(list.length);		// prints 3
print(list[1]);			// prints 122
list[1] = 500;			// assigns the value 500 to the item in position 1
print(list[1]);			// prints 500

Note:

var list = [100, 122, 334]; is inferred to be a list of integers! You won’t be able to add a string to this list.

Items in a list are ordered based on their position, starting with 0. So in the example above, the item at position 0 is 100. Items can be inserted into the list by using the add method and removed by using the remove method. Example:

var list = [100, 122, 334];
print(list.length);			// prints 3
list.add(567);
print(list.length);			// prints 4
print(list[3]);				// prints 567
list.remove(100);
print(list);				// prints [122, 334, 567]

Sets

A set in Dart is an unordered collection.

var categories = {'Programming', 'Life Lessons', 'Life Hacks', 'Shopping'};

Set<String> locations = {};
locations.add('Kingston');
locations.addAll({'Miami', 'Port of Spain'});
print(categories);		// prints {Programming, Life Lessons, Life Hacks, Shopping}
print(locations);		// prints {Kingston, Miami, Port of Spain}
print(locations.length);	// prints 3

Maps

A map is a collection of key-value pairs where the key appears only once in the collection. It allows us to store associations between a key and a value, while providing convenient methods.

Map <int, String> studentRecords = {};
studentRecords[12939] = "John Doe";
studentRecords[33312] = "Mary Jane";
studentRecords[11112] = "Clark Kent";
print(studentRecords[12939]);		// prints 'John Doe'
print(studentRecords.length);		// prints 3
studentRecords.remove(33312);
print(studentRecords);			// prints {12939: John Doe, 11112: Clark Kent}

Control Logic

In any language the ability to control the flow of operations based on your needs is essential. Dart, like most modern languages, has features that are quite standard in this regard. This includes branching and looping and they allow for the manipulation of the flow of the program within your code.

Looping

At times we need to repeat a set of operations and these language structures allow for this behaviour in a controlled way.

For Loops

There are a few ways of accomplishing a for loop. The following is the most common:

for (initilizer; condition; incrementer) {
  // operations
}
  • The initializer portion of the for loop takes the variable(s) that are to be initialized to manage the loop.

  • The condition is what tells the code to continue if it evaluates to true. The code will end when the condition evaluates to false.

  • the incrementer allows us to change a value on each iteration of the loop

So to make sense of this, the follow example of making code count from 1 to 100 is shown:

for(int i = 0;i < 100; i++) {
    print(i+1);
}

Another common use of a for loop is to perform an operation on a collection of items. The syntax is slightly different from the previous example:

for(final item in collection) {
    // perform operations on item
}

In the syntax above:

  • item is an individual item within a list where all items are homogenous

  • collection is a Iterable type like a List or Set

In practice the for loop will look something like this:

for (final candidate in candidates) {
  candidate.interview();
}

While and Do-While Loops

A while loop iterates, but evaluates a condition before the loop:

int i = 0;
while(!bin.isFull()) {
    bin.add(i++);
}

A do-while loop evaluates the condition after the loop:

do {
  printLine();
} while (!atEndOfPage());

Branching

This includes if and switch statements that allow for some operations to happen if some criteria is met. The if statement takes the following format:

if (condition) {
    // do something
} else {
    // do something else
}

The condition section of the statement above is any logical expression that evaluates to true or false. These are examples of an if statement.

if (number < 0) {
    print("Number is negative");
} else if (number < 100) {
    print("Number is positive but less than 100");
} else {
    print("Number is positive but greater or equal to 100");
}

The if statement also has a shorter form of being written, take note of the absence of curl brackets:

if (isRaining())
    closeWindows();

switch Statements evaluates a value against a number of cases. If a case matches the value held in the variable then it will execute specific section of code:

switch(value) {
    case value1:
        // doSomething();
        break;
    case value2:
        // doSomethingElse();
        break;
    default:
        // doSomethingIfNoMatch();
        break;
}

In the syntax above, you will notice the use of break. This tells the computer to exit the switch statement and continue executing the code outside of the switch block. The absence of the break statement will cause the computer to continue to execute the code for subsequent cases.

Also take note of the default case. This allows us to accomodate for values that are not accounted for, such as unexpected values or unknown cases. The code below demonstrates a use-case of a robot of some kind being told to walk:

var opCode = "WALK";
switch (opCode) {
    case "KNEEL":
        executeKneel();
        break;
    case "WALK":
        executeWalk();
        break;
    case "SIT":
        executeSit();
        break;
    case "SLEEP":
           executeSleep();
           break;
    default:
        executeConfused();
        break;
}

break and continue

Let’s also showcase the use of break outside of switch statements. Breaks are useful to interrupt the continued flow of code. It can be used to jump out of loops such as the example below:

while(true) { // continues indefinitely
    if(shutDownRequested()) {
        break;
    } else {
        processIncomingRequests();
    }
}

This allows the computer to continue processing incoming requests until it receives some shutdown request at which point the computer will exit the loop.

Consider the case where a system must interview all candidates that have 5 or more years experience for a particular job. The system is given a list of candidates and it must interview each within the list that matches the criteria:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

In this case if the candidate has less than 5 years, the computer will not execute the candidate.interview(); portion of the code and will instead continue and skip to the next loop iteration.

Functions

A function can be thought of as a block of code that solves a specific task. To simplify, it helps make code easier to read and manage by keeping it neat, so developers can concentrate on specific code sections. There are several variations of functions including anonymous functions, but we will touch on a few basic ideas.

void main() {
    print(isEven(2));
}
bool isEven(int number) {
    return (number % 2 == 0);
}

In the example above there are two functions, main and isEven. You will observe that the main function has the keyword void preceding the function name. This tells the compiler that the function returns nothing. The isEven function though has bool preceding the function name. This tells the compiler that the function returns a boolean type value.

A function must have a return type defined.

The second thing to observe are the items in the parentheses. The main function has no “parameters” and the isEven function has one “parameter” called number of type int.

A function can have any number of parameters.

The third and final observation with this example is that in the isEven function the keyword return makes an appearance. It tells the compiler that it must return the result of evaluating the proceeding statement. In the example the statement is a comparison that checks if the number leaves no remainder after being divided by two then it evaluates to true.

If the function has a return type other than void, then the return statement must return a value of that type.

An application will always have reference to the main() function. It is the entrypoint to the application and kicks off all other processes within your app and always returns void.

bool isEven(int number) => (number % 2 == 0);

The line above is actually another way of writing the same function previously described. It is a shorthand form referred to as arrow syntax that makes writing very small functions a little shorter and can be more convenient.


Named Parameters

void setText({bool? bold, bool? italics, bool? underlined}) {
    // does something
}

The function setText has a different set of parameters in comparison to the examples seen in previous examples. These are called named parameters and they are optional unless explicitly marked as required. You will notice that the type indicator has a question mark to indicate they may be null as they are optional. To call the function and specify the arguments you can do the following:

setText(bold: false, underlined: true);  // bold = false, italics = null and underlined = true

To tell the compiler a default value instead of handling potential null values, you can do the following in the parameters definition:

void setText({bool bold = false, bool italics = false, bool underlined = false}) {
    // does something
}

setText(bold: true); // bold = true, italics = false and underlined = false

What if you want to force your developers to add a named parameter and supply a value? We have the following option, note however that in the example, language can still be null:

const setLanguage({required String? language}){
    ...
}

Positional Parameters

The positional parameters can also be marked as optional. Consider the following example:

String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

In this example, it allows us to call the function say in any of the following ways:

say("Johnny","I am Johnny Five and I am alive");
say("Mark", "Hello from the other side!", "Android Phone");
Read More
Matthew Budram Matthew Budram

Designing Backend APIs in One Shot: Best Practices for Efficiency and Maintainability

When working on a sprint with specific objectives and the need for quick functionality delivery that can be reworked later, it's important to consider the implications of your choices. While your scrum master and project team may appreciate this approach, it can lead to unnecessary rework for both you and your team.

Instead, how about designing your APIs once and forgetting about them? Here are some guidelines that will help you stay ahead of the curve, allowing you to relax and enjoy the benefits of remote work during the next sprint.

When working on a sprint with specific objectives and the need for quick functionality delivery that can be reworked later, it's important to consider the implications of your choices. While your scrum master and project team may appreciate this approach, it can lead to unnecessary rework for both you and your team.

Instead, how about designing your APIs once and forgetting about them? Here are some guidelines that will help you stay ahead of the curve, allowing you to relax and enjoy the benefits of remote work during the next sprint.

Effective Resource Names

We often come across textbook examples of resource names like "Accounts" or "Customers," which generally work well in practice. For instance, when creating a customer, we use the endpoint POST /customers and provide the necessary details in the request body.

Avoid the trap of building functionality with narrow focus, resulting in endpoints like POST /createCustomer. As you delve deeper into your product backlog, you may find yourself creating unnecessary endpoints like POST /fetchCustomer.

Stick to the CRUD (CREATE READ UPDATE DELETE) pattern:

  • Fetching customers -> GET /customers

  • Fetching a specific customer -> GET /customers/{id}

  • Creating a customer -> POST /customers

  • Deleting a customer -> DELETE /customers/{id}

  • Updating a customer -> PUT /customers/{id}

Always pluralize the resource name. Notice that the resource doesn't use GET /customer. At first glance, this might confuse a novice backend engineer into thinking that the endpoint expects a single customer instead of multiple ones. Removing assumptions in programming helps you reach the finish line faster.

Versioning Upfront!

Yes, you should consider versioning right from the start! When creating the initial version of an endpoint, it's tempting to overlook versioning. However, saving time requires adopting a maintainable approach from the beginning.

What does this mean?

So, your GET /customers now becomes GET /v1/customers. Introducing versioning ensures there's no confusion about which version is being used. GET /customers is not as clear as GET /v1/customers, and using the latter saves time during debugging.

Always prefix the version. Imagine introducing the version after the fact and including it in the URI as GET /customers/v1/. In older endpoints, the customer identifier will likely be assumed as v1, causing a nightmare for backwards compatibility.

Soft Deletes, NEVER Hard Delete

There are some situations where hard deletes (permanent data removal) may be allowed, but in general, it's better to provide systems that allow for easy auditability and debugging.

To remove an item after calling the DELETE /customers/{id} endpoint, set the record status to "DELETED" and mark the datetime when the item was removed. This approach indicates that the item has been removed and when it happened. It also helps avoid breaking dependencies.

If we remove a customer record, the account owned by that customer may no longer function as expected. Your product team may also require functionality to display removed customers, so remember to include retrieval functionality in your GET endpoints.

For example: GET /customers?includeDeleted=true

By addressing this early on, you won't need to make adjustments in future sprints, saving you time and avoiding headaches.

Pagination and Sorting

Always consider your fellow teammates, especially the frontend developers who will need pagination and sorting capabilities. By incorporating these features from the start, you reduce the burden on your servers (fetching data) and on your clients (rendering data). Most stacks offer various paging libraries, making implementation smoother.

Therefore, your customer endpoint should support some form of pagination, such as GET /customers?size=x&page=y, and for sorting, use GET /customers?sort_by=last_name.

Read More
Matthew Budram Matthew Budram

Monolithic & Microservice Architectures

The appeal of a microservices architecture is undeniable. Microservices are highly scalable and resilient platforms that can maintain high throughput even if some of their partner services experience outages. The well-documented benefits of this architecture can, at times, result in extreme complexity in maintaining and extending the platform.

The appeal of a microservices architecture is undeniable. Microservices are highly scalable and resilient platforms that can maintain high throughput even if some of their partner services experience outages. The well-documented benefits of this architecture can, at times, result in extreme complexity in maintaining and extending the platform.

Moving away from the monolithic architectures of the past is a natural trend, and while the benefits are significant, building out these systems requires a considerable amount of work. These systems need to be designed with careful attention to maintaining the separation of concerns between each microservice and ensuring that the service is loosely coupled from the overall solution.

I will try my best to decompose some of the challenges I have personally faced with the build out of a finance-based system.

 

Operational Overhead and Cost

A microservices architecture is highly distributed and independent, which requires the development or purchase of specialized tools to manage and support the infrastructure. However, having an entire DevOps team equipped with these tools can become expensive. In comparison, monolithic platforms are more easily managed and maintained with a smaller team.



Data Consistency

In a microservices architecture, each microservice has its own data source that is not shared with other services. If a data source is coupled to multiple microservices, a shared dependency is created, which can lead to issues with all microservices using the service in the event of a failure.

However, when multiple microservices use the same or similar information, data consistency can become a potential issue. Attempting to create and maintain consistent data across multiple services can be challenging and time-consuming, and inconsistent data can lead to serious issues. While there are several strategies to avoid data inconsistency, the implementation considerations can be complex and require careful attention.

 

Testing Challenges

In a monolithic architecture, testing is relatively simple, and it involves ensuring high unit test coverage for each module and testing the entire system to ensure end-to-end flows remain functional after each modification. However, with a microservice platform, testing becomes more complex. It requires unit testing for each microservice, testing microservices as a whole, testing end-to-end flows by coordinating calls via automated testing platforms, performing API contract testing, and using service virtualization.

This complexity is due to the distributed nature of the system, and the need to test each microservice independently as well as collectively. In addition to unit testing and testing the system as a whole, testing requirements for microservices architecture can also include service discovery and registration, versioning, routing, and load balancing. Furthermore, API contract testing and service virtualization can be necessary to ensure consistency and compatibility between microservices.

 
Read More
Matthew Budram Matthew Budram

Public vs Private Healthcare

Healthcare in the public sector is beset with challenges ranging from limited resources, aging equipment and a revolving door of unmotivated staff and an overall demotivated team. This article is written based on my own experiences in two Caribbean countries. It showcases some key differences between the behaviour of staff in public and private hospitals in the region.

Healthcare in the public sector is beset with challenges ranging from limited resources, aging equipment, a revolving door of unmotivated staff, and an overall demotivated team. This article is written based on my own experiences in two Caribbean countries. It showcases some key differences between the behaviour of staff in public and private hospitals in the region.

Efficient Delivery

Private hospitals exist for profit and are always searching for a method of competing and reducing operating costs all while increasing profits. Much of the efficiencies sought after come with a continuous analysis of processes. This analysis may be purely derived from the need to ensure the highest levels of certifications are achieved or maintained which attracts patients to their facility.

From my observations in the public sector the need to improve the efficiency is one that is a lot more reactionary. Did someone experience a fall down these stairs? Oh no - let's ensure that patients in our care are not at risk of injury as a result of aging infrastructure. The need to improve efficiencies are never driven by a strive to compete in the sector. Public hospitals exists to only service the needs of the community and usually only worry about reducing costs in the face of budget cuts.

The contrasts in my experience are night and day - any physician operating in both worlds can support this view. A private hospital is a proactive institution while a public hospital is reactionary.

Growth requires Reinvestments

The need to increase efficiencies usually come at some costs. During the analysis phase it might be noted that the introduction of a Pyxis Pharmaceutical Dispenser may save the hospital thousands of dollars over a 5 year period and reduce the need for one on staff pharmacist. For a private hospital this is an easy sell - The Pyxis will be paying for itself easily within a few years of being introduced despite the initial high upfront cost. Private Hospitals are also not shackled to bureaucratic red tape to procure a high valued item; instead, where there is a justified spend - this can easily be approved by a board that values the need for increased operational efficiencies.

In a publicly funded hospital there is a stark difference in the approach:

  • Who will pay for it? Usually, public hospitals in the Caribbean are poorly funded and rely heavily on private donors to invest in infrastructure - much of what is critical to the care of patients.

  • Reductions in staffing. If the efficiency increases due to the introduction of a new process- in this case, a machine - then it is not likely to see a change in the staff complement. This is usually despite the fact that the new process can now be done with less. In the majority of cases, it is true that the staffing was never adequate to begin with.

Laser Focused Team

Increasing operational efficiency also leads to the development of staff that are focused on delivering quality at lower costs. This happens in every department of a private hospital, from the billing department to the clinicians and even the kitchen team. The teams are usually laser-focused on trying to keep a check on the costs while ensuring that the billable costs to the patient always result in a profit. Team members are also incentivized to ensure that their abilities to lower costs and increase profits are always rewarded. The inverse is true; underperformers are quickly reprimanded, moved or dismissed in an effort to fail quickly and take corrective actions.

A team in the public sector, based on my observation, does not need to ever focus on identifying a process that can be improved. They must operate in an environment that protects themselves and delivers healthcare with just the resources at hand, leaving a focus on the profit to the finance team upstairs. This mindset from staff results in issues with billing, unrealized costs, abuses in resources and further degradation of the quality of patient care due to unnecessarily high costs. There are a number of factors that lead to this behaviour; the bureaucracy involved in processes, the lack of incentives and a lack of involvement in efficiency management that is needed from all members of the team.

Summary

The delivery of healthcare in the Caribbean between private and public hospitals is wide and varied. The outcomes are unfortunate but can be improved. In a future article, I will look at a few suggestions to tackle the disparity and close the gap in the quality of healthcare facilities.

Read More
Matthew Budram Matthew Budram

Talent Retention

Brain Drain happens when a country fails to secure its most highly educated citizens leaving a vacuum of competency in several sectors. This is felt heavily in the ICT sector with a higher dependence on outsourcing outfits playing an increased role in staffing Caribbean organizations. Brain drain has a wide reaching impact on economies with more monies leaving the region and building other, more developed companies, that can retain and attract the talent required. It is proposed that a slight change of approach to talent retention may be beneficial to Jamaica and the wider Caribbean region.

Brain Drain happens when a country fails to secure its most highly educated citizens, leaving a vacuum of competency in several sectors. The phenomenon has wide reaching impacts on economies, especially in developing countries where talent now has to be outsourced to companies from more developed countries that are better equipped to retain talent. For example, in Jamaica the ICT sector contributes heavily to brain drain in a somewhat self fulfilling prophecy. ICT companies will outsource to foreign companies instead of using their resources to hire local talent and in turn, because there is limited focus on hiring locally, many citizens are compelled to leave. In some instances they may even work at the foreign entities these Caribbean firms outsource. There must be a change in the approach of to talent acquisition and retention that would be beneficial to Jamaica and the wider Caribbean.

It is known that Jamaica ranks highly on the Human Flight and Brain Drain Index, second only to Samoa. If we ever needed a confirmation of what has been seen from many families in Jamaica - then this ranking is it. Interestingly our Caribbean neighbors, the Bahamas, ranked 120th on the index.

I have participated in several recruitment activities in Jamaica, focused on identifying talent from an early stage and worked to engage and secure the young and brilliant minds before being snatched up by the likes of Google, Meta and Amazon. Recruiting isn’t easy as I have had to formulate convincing arguments to attract talent and persuade them to remain in Jamaica. Considering the realities of high inflation and interest rates which has only been exacerbated by global conflicts such as in Europe where there is rapid rise in food and fuel costs, it has proven difficult. The outlook seems bleak and increasingly disparaging to a company looking to participate in a global economy.

The grass is always greener..

A young graduate in 2022 has more access to large multinational firms than they did in 2009. The COVID-19 pandemic proved to be a catalyst in the era of remote work and since then the world has seen a sharp increase in remote work opportunities with better incentives to foster a healthier balance between work and personal life. High-paying remote work opportunities also increased from 4% (December 19, 2019) to approximately 15% in 2022. I am inclined to believe that remote work is here to stay. With increased accessibility, better working terms and a higher compensation package, traditional company structures and practices will fail to attract the kind of talent they would need to succeed. My prediction is that individuals will become uninterested in recruitment drives and even more disenchanted with compensation packages that promise health insurance at most.

Jamaican companies have now seen its own mass resignations especially during the pandemic where productivity remained high - but demands increased. Managers are now asked to participate in employee engagement check-ins and one-and-ones to ensure that staff remain motivated and committed to the company. The effectiveness of these check-ins are constantly eroded by migration push and pull factors. Jamaican companies rely too heavily on foreign firms which instead of encouraging their employees to stay it encourages them to leave. It is difficult surmise their value within the company when hired foreign consultants earn more but are required to do the same job or less.

Cauterize the wound

It takes a sudden dramatic act to stop the loss of tech talent. It involves acknowledging the hard truths that employees will work if given the right tools, environment and a healthy work-life balance that fosters personal development.

Pay talent - retain talent:

It goes without saying that the demand for talent must be met with a salary that can retain said talent. As a former part-time lecturer in the Department of Computing at the University of the West Indies, I have seen companies such as Goldman Sachs or Google appreciate our own local talent and lure them with attractive and unmatched compensation before they've even received a degree in hand. Why don't we appreciate our own similarly?

2021 global report - Randstad

Attractive salary & benefits is the most important driver globally (62%) and scores highest among women (65%) and middle-educated employees (66%). Furthermore, women, those with mid-level education, and Latin American residents consider as many as eight employer attributes as important in an ideal employer, compared to the global average of seven.

Dispel the myth that North American knows best:

Too often do we look outside to bolster our teams with talent that do not understand the Jamaican context. I have been involved in discussions with consultants from Fortune 100 companies that have sought to influence product direction with their own misconceived notions of demand for features. I have been fortunate to see several instances where, under the guidance of local talent, these product directions are stymied with the appropriate research and proven with adoption with locally developed ideas. We know our audience better than anyone else does.

Remote Work Destination:

Jamaica is an amazing country that enjoys wonderful and consistent weather year-round. People dream of sitting on the beach and writing code - yet it's not taken advantage of. During the pandemic we saw a myriad of countries opening up borders to facilitate remote work and increase potential revenues from these workers spending.

The country benefits by having talent present physically in the same space as a college graduate looking to depart. This encourages a diffusion of ideas that wouldn't be seen outside of conferences hosted in Jamaica. The success of Tech Beach Retreat demonstrates that people are interested in Jamaica as more than the stereotypical Reggae and Tourist attraction..

Summary

The brain-drain is bad, but it can be slowed and maybe even stopped. With a simple reallocation of time and resources it is very possible. The continued over dependence on larger firms to bolster the Jamaican workforce, is simply not sustainable.



Read More