Poseidon Athens Half Marathon Registrations – Architecture, Technical & Infrastructure Overview

Table of Contents

  • Overview
  • Samples
    • Front-end Samples
    • Admin Site Samples
    • Report Site Samples
  • Architectural Overview
  • Technologies/ Frameworks
  • Infrastructure
  • Future Improvements

Overview

I have been helping my dad who is organising the 3rd biggest running event in Greece, the Poseidon Athens Half Marathon and Parallel Races, by putting together a registration capability. This article is describing all the technical aspects of this effort.

The effort has been originally started in 2019, in line for the 2020 event, but Covid hit and all the running events in Greece got postponed by two years, so it eventually went live at the end of 2021 in line for the 2022 event which successfully took place in the middle of past April.

Samples

Front-end Samples

Although the registrations for 2023 are currently closed on the main public front end in preparation for the 2023 event, here are some screenshots from the test site:

Following that page comes a secured payment page that forwards the request via the registration-server to a confirmation or rejection page.

Admin Site Samples

All the below are sample dummy data in the test environment

Report Site Samples

All the below are sample dummy data in the test environment

Architectural Overview

In short, there is a public Front-end for the registration forms that consumes and directly interacts exclusively with a public Back-end component. The latter component is also responsible for scheduling jobs and email notification sendouts. To support Organisation members activities there are two additional components: the Admin site that is an authenticated/authorised view of all the data and the Report site for anything regarding reports and analytics. Finally, there is also a standalone tool that is responsible of parsing exclusively group registrations received via a customised excel spreadsheet.

Technologies/ Frameworks

The public Front-end has been built using the Create React App npx command. Multiple Ant Design components have been utilised especially the Form component capabilities. For internalisation the i18next React library has been used while for routing, the React Router has been used with the HashRouter variant.

The public Back-end has been built using Spring Boot exposing Rest Endpoints. For Database connectivity Hibernate has been used. The Spring Scheduling capabilities have been utilised for sending the email notifications while Apache Camel Barcode component has been used to generate the QR code on the email notifications. For the generation of the PDF attachment on the email, the iText library has been used.

For the creation of the private Admin site, the JHipster generator has been used to bootstrap the project in its React variant. With its Liquibase embedded capabilities, it is the master for the database generation and future changes application. Special care has been taken to maintain the script written in JDL (JHipster Domain Language) utilising the excellent JDL Studio Visualiser. The Authentication capability has been enhanced to allow some new more granular roles that are guarding certain functionalities.

The private Report site has been built as a single application where the backend endpoints are served via Spring Boot while the frontend code is served via React components. Special care has been taken to construct it in a generic fashion in that everything that appears takes the form of tiles that are showing some diagram or numerical value along with some icon and a download report link. For the visualisation part the Ant Design Charts have been used. For the Database connectivity the Jooq library has been utilised.

Infrastructure

The overall infrastructure can be summarised on the below diagram:

The code repositories are all hosted in GitHub while CI/CD has been setup as GitHub Action triggering AWS CodePipeline

All of the backend applications (registrations-server) as well as the standalone applications with both frontend and backend code (registrations-report and registrations-admin) have been deployed into AWS Elastic Beanstalk which is AWS’s PaaS solution sitting on top of their AWS EC2 solution.

Each one of the application has been deployed twice, representing Test and Production environments, each having their configurations injected.

The registrations-client front-end codebase also triggers via GitHub actions AWS CodePipeline, but this time gets deployed in AWS Amplify solution. In particular there is a main branch for Test deployment while there exist a prod branch for the Production deployment. Aside from making it very easy to deploy front end codebases, AWS Amplify is also giving out of the box an Amazon Certificate on the autogenerated domain or the explicitly owned and associated domain.

For the registrations-server public application in particular, the Load Balancing capabilities of Elastic Beanstalk have been utilised to auto-scale (up or down) based on a Network traffic threshold strategy.

This setup is bringing two additional benefits: Firstly it makes it very easy to associate a domain and/or certificate to the Load Balancer and secondly it makes it equally easy to do the same on subdomains.

For that reason via AWS Route 53 a domain has been acquired: stc-events-registrations-server.org and underneath it two subdomains have been setup: test.stc-events-registrations-server.org and prod.stc-events-registrations-server.org, each being setup via DNS records to forward directly to the Load Balanced AWS Elastic Beanstalk instance as it is shown below:

Having this setup is also giving out-of-the-box a certificate associated at the Load Balancer level.

Future Improvements

  • Transition to a more generic and dynamic, metadata-based configuration where more than one registration forms could be accommodated. In such an implementation the front-end registration code will be quite thin and all the metadata configuration about what is to be displayed and how will be coming from the back-end. That would facilitate setting up future registration events effortlessly without the need to write any line of code.
  • Decoupling the public back-end from scheduling activities that should be residing to a dedicated standalone other back-end application responsible exclusively for batch operations.
  • Although Card Payment is supported currently via Cardlink, invoicing is still issued manually. In a future improvement, invoice will also be generated automatically and instantly at the point of purchase.
  • Enhancing the Group Registrations capability. Currently it is accommodated via Excel spreadsheets that are sent out to Organisations and received back, being parsed later via a standalone tool. The nature of Excel is limited in terms of form handling so quality of data is lacking introducing delays in processing group registration requests. This functionality will be migrated to a web-based solution where the registration will be happening online.

Audit via JPA Lifecycle Events – the easy way

Note: Up until JPA 2.2, the JPA acronym was standing for “Java Persistence API” and its interface was specified under the javax.persistence namespace, while from JPA 3.0 onwards, the JPA acronym is now standing for “Jakarta Persistence API” while its namespace has been migrated to javax.jakarta.persistence. This is due to the Java EE specifications, migration and move into the Eclipse Foundation post Java EE 8 and officiallyfrom Jakarta EE 9 onwards. The final JCP/Oracle represented release of Java EE happened on August 31, 2017 with Java EE 8. More details about this move can be found in this Oracle article.


Although JPA does not provide native support for Auditing, we can leverage its lifecycle events capability to receive a callback on pre/post create/update/delete operations and act on them.

How can we employ that in a sustainable/ extensible manner?

It all starts with the definition and understanding of these lifecycle events. At JPA interface level we have 7 annotations that curiously all align alphabetically in the JavaDoc and they all have the same description:

Specifies a callback method for the corresponding lifecycle event.

Reference JavaDoc for JPA 2.2 under javax.persistence namespace

Reference JavaDoc for JPA 3.0 under jakarta.persistence namespace

Out of these 7 annotations capturing lifecycle events we need 3 of them to capture Audit information:

JPA Lifecycle EventAudit Event
@PostPersistCreation audit event
@PostUpdateUpdate audit event
@PostRemoveDelete audit event

Where shall we define these annotations? At each entity level?

Probably not, if we want to be dynamic and extensible, trying to minimise the changes to the bare minimum, if we want to onboard other entities to these Audit events being captured and persisted.

To that end, let’s invent a dedicated AuditEvent entity responsible for capturing the change in serialised form:

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import javax.persistence.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import java.time.Instant;
import java.util.Objects;

@Entity
@Table(name = "audit_event")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class AuditEvent implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @Column(name = "entity_name", nullable = false)
    private Long entityName;

    @NotNull
    @Size(max = 25)
    @Column(name = "entity_type", length = 25, nullable = false)
    private String entityType;

    @NotNull
    @Size(max=10)
    @Column(name = "action", length = 10, nullable = false)
    private String action;

    @Lob
    @Column(name = "entity_value")
    private String entityValue;

    @Column(name = "commit_version")
    private Integer commitVersion;

    @Size(max = 50)
    @Column(name = "modified_by", length = 50)
    private String modifiedBy;

    @NotNull
    @Column(name = "modified_date", nullable = false)
    private Instant modifiedDate;

    //getters/setters omitted
}

Notice how the entity value is specified as a @Lob to hold the serialized version of the entity. This will give us the flexibility of holding any potential data structure regardless of the entity, sacrificing a bit searching capabilities since we don’t have everything normalised on dedicated columns.

Its corresponding Spring Data JPA Repository would be:

import org.springframework.data.jpa.repository.JpaRepository;
import com.dimitrisli.blog.auditevents.AuditEvent;

public interface AuditEventRepository extends JpaRepository<AuditEvent, Long> {}

At domain model level, the Audit event action can be represented with an enum:

public enum AuditAction { CREATE, UPDATE, DELETE }

We can confine and delegate the responsibility of converting the business entity into our audit entity and persisting it, into a dedicated Service bean:

@Service
public class AuditEventService {

    private final Logger log = LoggerFactory.getLogger(AuditEventService.class);

    private final AuditEventRepository auditEventRepository;
    private final ObjectMapper objectMapper; 

    public AuditEventService(AuditEventRepository auditEventRepository, ObjectMapper objectMapper) {
        this.auditEventRepository = auditEventRepository;
        this.objectMapper = objectMapper;
    }

    @Async
    public void convertAuditEvent(Object target, AuditAction action, String user) {
        try {
            AuditEvent auditedEntity = prepareAuditEntity(target, action, user);
            if (auditedEntity != null) {
                auditEventRepository.save(auditedEntity);
            }
        } catch (Exception e) {
            log.error("Exception on audit entity {} with error: {}", target, e);
        }
    }

    private AuditEvent prepareAuditEntity(final Object entity, AuditAction action, String user) {
        AuditEvent auditedEntity = new AuditEvent();
        Class<?> entityClass = entity.getClass(); 
        auditedEntity.setAction(action.value());
        auditedEntity.setEntityType(entityClass.getName());
        Long entityId;
        String entityData;
        try {
            Field privateLongField = entityClass.getDeclaredField("id");
            privateLongField.setAccessible(true);
            entityId = (Long) privateLongField.get(entity);
            privateLongField.setAccessible(false);
            //serialized version of the entity
            entityData = objectMapper.writeValueAsString(entity);
        } catch (Exception e) {
            log.error("Exception on entity conversion with error {}", e);
            return null;
        }
        auditedEntity.setEntityId(entityId);
        auditedEntity.setEntityValue(entityData);
        if (AuditAction.CREATE.equals(action)) {
            auditedEntity.setModifiedBy(user);
            auditedEntity.setModifiedDate(Instant.now());
            auditedEntity.setCommitVersion(1);
        } else {
            auditedEntity.setModifiedBy(user);
            auditedEntity.setModifiedDate(Instant.now());
            calculateVersion(auditedEntity);
        }
        return auditedEntity;
    }
}

Notice here that the method is @Async and thus not holding back the servlet request thread of execution (assuming a non-reactive webapp exposing this capability). Also notice how we are pulling out certain attributes via reflection and how we JSON-ising the entity content via Jackson’s ObjectMapper.

Now that we have a mechanism to convert from any entity into this Audit-specific persistable entity, let’s move one level higher and connect the JPA event lifecycle callback annotations to this capability.

For that we’ll leverage Spring Data JPA’s AuditingEntityListener capability via a new dedicated listener bean:

public class AuditEventListener extends AuditingEntityListener {

    private final Logger log = LoggerFactory.getLogger(AuditEventListener.class);

    private AuditEventService auditEventService;

    @PostPersist
    public void onPostCreate(Object target) {
        try {
            auditEventService.convertAuditEvent(target, AuditAction.CREATE, SecurityContextHolder.getContext().getAuthentication().getName());
        } catch (Exception e) {
            log.error("Exception on post create {}", e);
        }
    }

    @PostUpdate
    public void onPostUpdate(Object target) {
        try {
            auditEventService.convertAuditEvent(target, AuditAction.UPDATE, SecurityContextHolder.getContext().getAuthentication().getName());
        } catch (Exception e) {
            log.error("Exception on post update {}", e);
        }
    }

    @PostRemove
    public void onPostRemove(Object target) {
        try {
            auditEventService.convertAuditEvent(target, EntityAuditAction.DELETE, SecurityContextHolder.getContext().getAuthentication().getName());
        } catch (Exception e) {
            log.error("Exception on post remove {}", e);
        }

    public void setAuditEventService(AuditEventService auditEventService) {
        this.auditEventService = auditEventService;
    }
}

Notice how we are pulling the name of the principal to be persisted as user (assuming usage of non-reactive webapp with Spring Security configured).

Now that we have confined and delegated the callback handling and persistence of the audit events, all it’s left is to register the listener at entity level. This is all we have to do to onboard any entity to this Audit capturing capability. The registration of the listeners at entity-level is happening via JPA’s EntityListeners annotation.

JPA 2.2 EntityListeners JavaDoc

JPA 3.0 EntityListeners JavaDoc

@Entity
@Table(name = "my_table")
@EntityListeners(AuditEventListener.class)
public class MyEntity implements Serializable {

    private static final long serialVersionUID = 1L;
    //
}

Easiest way to start a Spring Boot Apache Camel Project

What is the easiest way to start a Maven project that includes Spring Boot and Apache Camel?

One idea would be the Spring Initializr that include Camel as dependency.

That would bring in our POM dependencies the following coordinates:

<dependency>      
   <groupId>org.apache.camel.springboot</groupId>      
   <artifactId>camel-spring-boot-starter</artifactId>            
   <version>${camel-spring-boot.version}</version>    
</dependency>
  

That would seem sufficient but as the project grows and the need to include multiple Apache Camel Endpoint dependencies is necessary, we might run into version dependency issues between the Spring Boot Starter underlying dependencies, and the Apache Camel Endpoint dependencies.

There is a better way to manage consistently these underlying dependencies and that is via the Apache Camel BOM dedicated to Spring Boot dependencies, such as the below:

<dependencyManagement>
    <dependencies>
      <!-- Spring Boot BOM -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!-- Camel BOM -->
      <dependency>
        <groupId>org.apache.camel.springboot</groupId>
        <artifactId>camel-spring-boot-dependencies</artifactId>
        <version>${apache-camel.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

In fact this is what a dedicated Maven Apache Camel Archetype is created to do which I think is the easiest and fastest way to bootstrap a Spring Boot Apache Camel Maven project:

mvn archetype:generate \
-DarchetypeGroupId=org.apache.camel.archetypes \
-DarchetypeArtifactId=camel-archetype-spring-boot \
-DarchetypeVersion=3.4.2 \
-DgroupId=com.dimitrisli \
-DartifactId=spring-boot-camel-app \
-Dversion=1.0.0-SNAPSHOT

Contributing Scala “When to use Currying” in StackOverflow Documentation

A week or so ago I’ve contributed in StackOverflow Documentation an entry about Currying under the Scala Language tag. I’ve repeated the same exercise for Java Currying Documentation. This is how it goes:

Currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument.

This is normally useful when for example:

  1. different arguments of a function are calculated at different times. (Example 1)
  2. different arguments of a function are calculated by different tiers of the application. (Example 2)

Example 1

Let’s assume that the total yearly income is a function composed by the income and a bonus:

val totalYearlyIncome: (Int,Int) => Int =  (income, bonus) => income + bonus

The curried version of the above 2-arity function is:

val totalYearlyIncomeCurried: Int => Int => Int = totalYearlyIncome.curried

Note in the above definition that the type can be also viewed/written as:

Int => (Int => Int)

Let’s assume that the yearly income portion is known in advance:

val partialTotalYearlyIncome: Int => Int = totalYearlyIncomeCurried(10000)

And at some point down the line the bonus is known:

partialTotalYearlyIncome(100)

Example 2

Let’s assume that the car manufacturing involves the application of car wheels and car body:

val carManufacturing: (String,String) => String = (wheels, body) => wheels + body

These parts are applied by different factories:

class CarWheelsFactory {
  def applyCarWheels(carManufacturing: (String,String) => String): String => String =
          carManufacturing.curried("applied wheels..")
}
    
class CarBodyFactory {
  def applyCarBody(partialCarWithWheels: String => String): String = partialCarWithWheels("applied car body..")
}

Notice that the CarWheelsFactory above curries the car manufacturing function and only applies the wheels.

The car manufacturing process then will take the below form:

val carWheelsFactory = new CarWheelsFactory()
val carBodyFactory   = new CarBodyFactory()

val carManufacturing: (String,String) => String = (wheels, body) => wheels + body
  
val partialCarWheelsApplied: String => String  = carWheelsFactory.applyCarWheels(carManufacturing)
val carCompleted = carBodyFactory.applyCarBody(partialCarWheelsApplied)

Contributing Java “Currying” in StackOverflow Documentation

A week or so ago I’ve contributed in the StackOverflow Documentation an entry about Currying  which until now it’s mostly intact and only cosmetic changes have been made to the original. This is how it goes:

Currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument.

This can be useful when:

  1. Different arguments of a function are calculated at different times. (see Example 1)
  2. Different arguments of a function are calculated by different tiers of the application. (see Example 2)

This generic utility applies currying on a 2-argument function:

class FunctionUtils {

    public static <A,B,C> Function<A,Function<B,C>> curry(BiFunction<A, B, C> f) {
        return a -> b -> f.apply(a,b);
    }

}

The above returned curried lambda expression can also be viewed/written written as:

a -> ( b -> f.apply(a,b) );

Example 1

Let’s assume that the total yearly income is a function composed by the income and a bonus:

BiFunction<Integer,Integer,Integer> totalYearlyIncome = (income,bonus) -> income + bonus;

Let’s assume that the yearly income portion is known in advance:

Function<Integer,Integer> partialTotalYearlyIncome = FunctionUtils.curry(totalYearlyIncome).apply(10000);

And at some point down the line the bonus is known:

System.out.println(partialTotalYearlyIncome.apply(100));

Example 2

Let’s assume that the car manufacturing involves the application of car wheels and car body:

BiFunction<String,String,String> carManufacturing = (wheels,body) -> wheels.concat(body);

These parts are applied by different factories:

class CarWheelsFactory {
    public Function<String,String> applyCarWheels(BiFunction<String,String,String> carManufacturing) {
        return FunctionUtils.curry(carManufacturing).apply("applied wheels..");
    }
}

class CarBodyFactory {
    public String applyCarBody(Function<String,String> partialCarWithWheels) {
        return partialCarWithWheels.apply("applied car body..");
    }
}

Notice that the CarWheelsFactory above curries the car manufacturing function and only applies the wheels. The car manufacturing process then will take the below form:

CarWheelsFactory carWheelsFactory = new CarWheelsFactory();
CarBodyFactory   carBodyFactory   = new CarBodyFactory();

BiFunction<String,String,String> carManufacturing = (wheels,body) -> wheels.concat(body);

Function<String,String> partialCarWheelsApplied = carWheelsFactory.applyCarWheels(carManufacturing);
String carCompleted = carBodyFactory.applyCarBody(partialCarWheelsApplied);

Running Clojure via TextMate 2

TextMate 2 exists only on Mac OS X, therefore I’m assuming usage of Homebrew

1. Homebrew installation of Leiningen

$ brew install leiningen

2. Installation of Leiningen lein-exec plugin via adding to ~/.lein/profiles.clj:

{:user {:plugins [[lein-exec "0.3.5"]]}}

3. Manual installation of the (old) TextMate Clojure Bundle. TextMate 2 Bundles are living now under this directory and not as described in the Github repo:

cd ~/Library/Application\ Support/TextMate/Managed/Bundles/

4. Within TextMate 2 > Edit Bundles > Clojure > Menu Actions > Rename and edit the ‘Cake Start’ to ‘Run’ with the following scriptlet:

#!/bin/bash
[[ -f "${TM_SUPPORT_PATH}/lib/bash_init.sh" ]] && . "${TM_SUPPORT_PATH}/lib/bash_init.sh"
if [ -e “${TM_FILEPATH}” ]; then
“/usr/local/bin/lein” “exec” “${TM_FILEPATH}”
exit_show_tool_tip
else
puts “File not found or not saved. Please save first”
fi

DSL example in Scala

Here’s a DSL example in Scala that will allow us to express amounts in the context of currencies taking into account the FX rates too.

By the end of it we’ll be able to write the following:

'USD(100) + 'EUR(200) in 'EUR

Here’s the code for it:

object ScalaForexRatesDSL {

  implicit class ccy2money(ccy :Symbol) {
    def apply(amount: BigDecimal) = Money(amount, ccy)
  }
}

case class FromToCcy(ccyFrom: Symbol, ccyTo: Symbol)

case class Money(amount:BigDecimal, ccy:Symbol) {

  def + (other: Money)(implicit rates: Map[FromToCcy, BigDecimal]): Money =
    if(ccy == other.ccy) Money(amount + other.amount, ccy) else {
      val convertedOther = convertedOtherMoney(other)
      Money(amount + convertedOther.amount, ccy)
    }

  def - (other: Money)(implicit rates: Map[FromToCcy, BigDecimal]): Money =
    if(ccy == other.ccy) Money(amount - other.amount, ccy) else {
      val convertedOther = convertedOtherMoney(other)
      Money(amount - convertedOther.amount, ccy)
    }

  def * (other: Money)(implicit rates: Map[FromToCcy, BigDecimal]): Money =
    if(ccy == other.ccy) Money(amount * other.amount, ccy) else {
      val convertedOther = convertedOtherMoney(other)
      Money(amount * convertedOther.amount, ccy)
    }

  def / (other: Money)(implicit rates: Map[FromToCcy, BigDecimal]): Money =
    if(ccy == other.ccy) Money(amount / other.amount, ccy) else {
      val convertedOther = convertedOtherMoney(other)
      Money(amount / convertedOther.amount, ccy)
    }

  private def convertedOtherMoney(other: Money)(implicit rates: Map[FromToCcy, BigDecimal]): Money = {
    val rate = rates.get(FromToCcy(other.ccy, ccy)).getOrElse(1 / rates.get(FromToCcy(ccy, other.ccy)).get)
    Money(other.amount * rate, ccy)
  }

  def in(ccy: Symbol)(implicit rates: Map[FromToCcy, BigDecimal]): Money =
    if (this.ccy == ccy) this else {
      val rate = rates.get(FromToCcy(this.ccy, ccy)).getOrElse(1 / rates.get(FromToCcy(ccy, this.ccy)).get)
      Money(amount * rate, ccy)
    }

  override def toString = amount + " " + ccy.name
}


object Main extends App {

  import ScalaForexRatesDSL._

  implicit val rates = Map((FromToCcy('EUR, 'USD) -> BigDecimal(1.13)))

  print( 'USD(100) + 'EUR(200) in 'EUR )

}

Spring Boot – Testing

Spring Boot dependency for Testing is bringing exactly what I would have wished for (I hope they would include in the future the upcoming AssertJ).

The dependency looks like this:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

and brings in the classpath the usual Testing suspects: JUnit4, Mockito and Hamcrest.

Screen Shot 2015-07-12 at 21.18.17

Spring Boot – Logging

As is the case with any new framework (or major addition to an existing and well known framework) the best way to approach it, work with it and learn it is not to dive head first but rather take baby steps in areas that feel more accessible and can link with ideas/processes/steps you are already following.

Spring Boot is sporting several starter recipes that can speedup your configuration and can be consumed separately to each other.

Let’s first talk about logging.

The best practise is to code against SLF4J, provide a desired implementation (preferably Logback), and include in the classpath SLF4J bridges for JUL, JCL and Log4J.

The process is described here by the person/team that authored SLF4J, Log4J, Log4j2, Logback and all the 3 SLF4J bridges namely for JUL, JCL, and Log4J.

The design we described and we are aiming at is also well depicted in the link above and is provided here for quick reference:

Screen Shot 2015-07-12 at 21.03.18

Let’s now get back to Spring Boot.

The Spring Boot dependency module is the following:


		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
		</dependency>

That is actually doing all we want and described above effortlessly leaving our classpath changed by this much:

Screen Shot 2015-07-12 at 21.09.39

Scala Option Advanced example

I’ve been tinkering with mime types in a toy Scalatra file server so while maintain a file with all known mime types a common task is to figure out the extension of a given file name.

With a bit out-of-the-box thinking on capturing the extension but without using Options therefore open to failures:

def findExtension(s:String)=s.reverse.takeWhile(_!='.').reverse

Here’s how it looks while wrapping the string in the Option monad and performing type-safe operations on it before returning a Some or None of the extension:

scala> def findExtension(s:String)=
 Option(s) //wrap into Option
 .filter(_.exists(_=='.'))  //ignore files without a dot
 .map(_.reverse)  //out-of-the-box thinking
 .map(_.takeWhile(_!='.'))  //maybe could be done with some non-greedy advanced regex too
 .filter(_.trim.nonEmpty)  //take "" empty strings as None
 .map(_.reverse)  //reverse back
findExtension: (s: String)Option[String]

scala> findExtension("hi")
res128: Option[String] = None

scala> findExtension("h.io")
res129: Option[String] = Some(io)

scala> findExtension("www.github.io")
res130: Option[String] = Some(io)