Download as pdf or txt
Download as pdf or txt
You are on page 1of 70

Beginning Helidon: Building

Cloud-Native Microservices and


Applications 1st Edition Dmitry
Kornilov
Visit to download the full and correct content document:
https://ebookmeta.com/product/beginning-helidon-building-cloud-native-microservices
-and-applications-1st-edition-dmitry-kornilov-2/
More products digital (pdf, epub, mobi) instant
download maybe you interests ...

Beginning Helidon: Building Cloud-Native Microservices


and Applications 1st Edition Dmitry Kornilov

https://ebookmeta.com/product/beginning-helidon-building-cloud-
native-microservices-and-applications-1st-edition-dmitry-
kornilov/

Beginning Cloud Native Development with MicroProfile,


Jakarta EE, and Kubernetes: Java DevOps for Building
and Deploying Microservices-based Applications 1st
Edition Tarun Telang
https://ebookmeta.com/product/beginning-cloud-native-development-
with-microprofile-jakarta-ee-and-kubernetes-java-devops-for-
building-and-deploying-microservices-based-applications-1st-
edition-tarun-telang/

Beginning Spring Boot 3: Build Dynamic Cloud-Native


Java Applications and Microservices - Second Edition K.
Siva Prasad Reddy

https://ebookmeta.com/product/beginning-spring-boot-3-build-
dynamic-cloud-native-java-applications-and-microservices-second-
edition-k-siva-prasad-reddy/

Spring REST: Building Java Microservices and Cloud


Applications Balaji Varanasi

https://ebookmeta.com/product/spring-rest-building-java-
microservices-and-cloud-applications-balaji-varanasi/
Cloud Native Infrastructure with Azure: Building and
Managing Cloud Native Applications 1st Edition Nishant
Singh & Michael Kehoe

https://ebookmeta.com/product/cloud-native-infrastructure-with-
azure-building-and-managing-cloud-native-applications-1st-
edition-nishant-singh-michael-kehoe/

Cloud-Native Microservices with Apache Pulsar: Build


Distributed Messaging Microservices Rahul Sharma

https://ebookmeta.com/product/cloud-native-microservices-with-
apache-pulsar-build-distributed-messaging-microservices-rahul-
sharma/

Cloud Native Devops with Kubernetes: Building,


Deploying, and Scaling Modern Applications in the
Cloud, 2nd Edition Justin Domingus & John Arundel

https://ebookmeta.com/product/cloud-native-devops-with-
kubernetes-building-deploying-and-scaling-modern-applications-in-
the-cloud-2nd-edition-justin-domingus-john-arundel/

Monitoring Cloud-Native Applications 1st Edition Mainak


Chakraborty

https://ebookmeta.com/product/monitoring-cloud-native-
applications-1st-edition-mainak-chakraborty/

Practical Process Automation: Orchestration and


Integration in Microservices and Cloud Native
Architectures 1st Edition Bernd Ruecker

https://ebookmeta.com/product/practical-process-automation-
orchestration-and-integration-in-microservices-and-cloud-native-
architectures-1st-edition-bernd-ruecker/
Dmitry Kornilov, Daniel Kec and Dmitry Aleksandrov

Beginning Helidon
Building Cloud-Native Microservices and
Applications
Dmitry Kornilov
Praha, Czech Republic

Daniel Kec
Praha, Czech Republic

Dmitry Aleksandrov
Sofia, Bulgaria

ISBN 978-1-4842-9472-7 e-ISBN 978-1-4842-9473-4


https://doi.org/10.1007/978-1-4842-9473-4

© Dmitry Kornilov, Daniel Kec, Dmitry Aleksandrov 2023

This work is subject to copyright. All rights are solely and exclusively
licensed by the Publisher, whether the whole or part of the material is
concerned, specifically the rights of translation, reprinting, reuse of
illustrations, recitation, broadcasting, reproduction on microfilms or in
any other physical way, and transmission or information storage and
retrieval, electronic adaptation, computer software, or by similar or
dissimilar methodology now known or hereafter developed.

The use of general descriptive names, registered names, trademarks,


service marks, etc. in this publication does not imply, even in the
absence of a specific statement, that such names are exempt from the
relevant protective laws and regulations and therefore free for general
use.

The publisher, the authors, and the editors are safe to assume that the
advice and information in this book are believed to be true and accurate
at the date of publication. Neither the publisher nor the authors or the
editors give a warranty, expressed or implied, with respect to the
material contained herein or for any errors or omissions that may have
been made. The publisher remains neutral with regard to jurisdictional
claims in published maps and institutional affiliations.

This Apress imprint is published by the registered company APress


Media, LLC, part of Springer Nature.
The registered company address is: 1 New York Plaza, New York, NY
10004, U.S.A.
This is for my mother.
I miss you…
—Dmitry Kornilov
To my extraordinary wife, who somehow managed to put up with my
endless late-night writing sessions. I love you.
—Daniel Kec
To my parents. They are the real heroes.
—Dmitry Aleksandrov
Introduction
Helidon is a Java framework for developing cloud-native microservices.
Its performance, lightweight approach, and convenient APIs quickly
became popular in the Java community. This book is all you need to get
started with Helidon, and it will teach you how to use it effectively. It’s
written by the Helidon developers, who know best how the framework
is designed to be used. A significant part of the book is dedicated to
MicroProfile APIs and specifications which are explained in detail.
After reading this book, you will be able to do the following.
– Create and consume RESTful services
– Package and deploy your applications to Kubernetes
– Develop observable applications and utilize health checks, metrics,
and tracing
– Secure your services using OpenID Connect
– Work with data
– Make your applications resilient
– Understand and use reactive messaging and reactive streams
Who This Book Is For
This book is for developers and architects who want to start developing
cloud-native applications using Helidon, for developers interested in
developing portable applications using MicroProfile and Jakarta EE, and
those who are looking for preparation materials for Oracle Helidon
Microservices Developer Professional certification.

Helidon Certification
Oracle announced the Helidon Microservices Developer Professional
certification and the corresponding course to prove microservices
development skills. The book authors participated in the exam and the
course development. The book is designed as an additional preparation
material for the certification test. Although the book covers Helidon
version 3.x and the exam is created from the previous version of
Helidon (2.x), it covers all certification topics, provides additional
information, and explains technologies from a different angle.
You can find more information on the official page at
https://mylearn.oracle.com/ou/learning-
path/become-a-helidon-microservices-developer-
professional/114512.
What This Book Covers
Chapter 1 introduces Helidon and explains its key advantages. It also
discusses two Helidon flavors and explains the differences between
them.
Chapter 2 introduces various tools for bootstrapping creating
Helidon applications such as Helidon CLI, Project Starter, and Maven
Archetypes; explains how to create your first application, build it using
different profiles (executable jars, JLink images, and GraalVM native
images), create a docker image and deploy it to Kubernetes.
Chapter 3 explains how to configure Helidon applications,
introduces MicroProfile Config specification; explains concepts of
config sources, defaults, and profiles; demonstrates integration with
Kubernetes config maps.
Chapter 4 explains observability and why it’s important for
microservices, covers concepts of health checks, metrics, tracing, and
logging, as well as the corresponding MicroProfile specifications.
Chapter 5 explains how to call other services in your Helidon
application. It covers MicroProfile Rest Client and cross-origin resource
sharing (CORS).
Chapter 6 explains how to work with databases, query, and update
data using JDBC and Jakarta Persistence.
Chapter 7 discusses how to make your application resilient using
MicroProfile Fault Tolerance APIs and explains timeout, retry, fallback,
bulkhead, and circuit breaker concepts.
Chapter 8 explains how to secure your applications. It covers
OpenID Connect and MicroProfile JWT RBAC specifications.
Chapter 9 explains how to document your APIs using OpenAPI and
how to automatically generate clients based on it.
Chapter 10 explains how to test your applications using JUnit since
Helidon integrates well with this framework.
Chapter 11 explains how you can schedule tasks in your Helidon
applications.
Chapter 12 explains how well Helidon is integrated with other
technologies like Neo4j, Verrazzano, Coherence CE, and the Kotlin
programming language.
Chapter 13 explains how to use some reactive functionality in your
Helidon applications. It covers MicroProfile Reactive Stream Operators,
MicroProfile Reactive Messaging specifications and integration with
Kafka.
Chapter 14 explains how to use distributed transactions in Helidon
applications. It covers the Saga pattern and MicroProfile LRA
specification.
Chapter 15 introduces Helidon SE - the reactive flavor of Helidon. It
guides you through creating the first Helidon SE application, using
different build profiles (executable jars, JLink images, GraalVM native
images), and deploying it to Kubernetes.

Sample Code
You can access sources of all book samples in the book’s official GitHub
repository at https://github.com/Apress/Beginning-
Helidon.
To compile samples, you need the following tools installed:
– Linux or macOS environment. On Windows we recommend using
Windows Subsystem for Linux (WSL).
– JDK 17
– Maven 3.9.0
– Some samples rely on using utilities like cURL
If you found issues with the sample code, please file it in the issues
tracker at https://github.com/Apress/Beginning-
Helidon/issues.
Preface
About This Book
The cloud era we are living in dictates some requirements for
applications. This book is about Helidon—a Java framework designed
for developing cloud-native applications. Helidon collects all
functionality you need to create cloud-native applications which start
fast, has low disk image footprint, and low memory consumption.
Helidon supports modern standards such as MicroProfile and partially
Jakarta EE, which adds a portability aspect to your application, allowing
it to be run on supported runtimes of different vendors.
After reading this book, you will know how to build Java cloud-
native applications using Helidon, understand different options for
packaging applications to a docker container, and deploy them to
Kubernetes. You also learn how to use MicroProfile APIs and Helidon
Reactive APIs.
The book contains many useful recipes, best practices, and
methodologies around each covered topic. It comes with samples
demonstrating the different functionality of Helidon and can be used as
a hands-on reference. The information is given in increasing complexity
order starting from creating a simple RESTful service and ending up in
complicated scenarios with OpenID Connect and distributed
transactions.
Prerequisites
You need to know Java language syntax, semantics, and the basics of
Java functional programming, including lambda functions.
Some book chapters require some basic understanding of the
dependency injection design pattern and Jakarta EE CDI specification.
Some book chapters require understanding reactive programming
concepts, including backpressure, observers, and schedulers.
To run samples, you must have JDK 17 and Maven 3.8.4 installed.
Samples should run fine with the newer versions of Maven, but we used
this version to build them and can guarantee that everything works.
The preferred environment is Linux or macOS with a bash shell. We
recommend using Windows Subsystem for LInux (WSL) if you use
Windows.
Any source code or other supplementary material referenced by the
author in this book is available to readers on the GitHub repository:
https://github.com/Apress/Beginning-Helidon. For more detailed
information, please visit http://www.apress.com/source-code.
Table of Contents
Chapter 1:​Introduction
Introducing Helidon
Helidon Flavors
Helidon MP
Helidon SE
Which Flavor Should You Use?​
Summary
Chapter 2:​Your First Application
Generating Your First Application
Helidon CLI
Project Starter
Helidon Maven Archetypes
Analyzing Generated Project
Quickstart Application
Maven Project
CDI
RESTful Web Service
Build and Run
Packaging
Executable JAR
jlink Image
Native Image
Deploying to Kubernetes
Summary
Chapter 3:​Configuration
Expressions
Programmatic API
Config Sources
Converters
Automatic Converter
Aggregated Properties
Custom Converter
Profiles
Property Level
Config Source Level
Meta Configuration
YAML Config Source
Custom Config Source
Dynamic Config Source
Kubernetes ConfigMap
Environment Variables
Mounted Volume
Summary
Chapter 4:​Observability
What Is Observability?​
Health
Kubernetes Probes
MicroProfile Health
Adding Health Checks to Your Helidon Application
Built-in Checks
Custom Checks
Metrics
Adding MicroProfile Metrics Support to Your Helidon
Application
Sample Application
MicroProfile Metrics REST API
Metrics Model
Using MicroProfile Metrics API
Tracing
Distributed Tracing
Adding Tracing to Your Helidon Application
Helidon Tracing Implementation
MicroProfile OpenTracing API
Configuration
Logging
Logging in Helidon
Summary
Chapter 5:​Communicating with Other Services
MicroProfile Rest Client
Integration with MicroProfile Config
Exception Handling
Modifying Requests and Responses
Handling Headers
Asynchronous Operations
Programmatic APIs
MicroProfile Rest Client Conclusion
JAX-RS Client API
Providers
Asynchronous Operations
JAX-RS Client API Conclusion
CORS
Summary
Chapter 6:​Accessing Data
Low-Level Data Access with JDBC
Working with DataSource
Data Access with JPA
Transactions
Summary
Chapter 7:​Resiliency
Retry
Fallback
Asynchronous
Timeout
Circuit Breaker
Bulkhead
Fault Tolerance and CDI
Summary
Chapter 8:​Security
Serving HTTPS
Helidon Security
Basic Authentication
JSON Web Token
MicroProfile JWT RBAC
OpenID Connect
Token Propagation
Configuration of Secrets
AES-GCM Encryption
RSA Encryption
Plain Text Password Detection
Summary
Chapter 9:​Using OpenAPI
About OpenAPI
Basic Usage
Static OpenAPI Files
Automatic Client Generation
Summary
Chapter 10:​Testing Your Helidon Application
Testing in Helidon
Testing with JUnit 5
Advanced Usage
Testing with TestNG
Work with Testcontainers
The Other Way Around
Summary
Chapter 11:​Scheduling Tasks
Scheduling in Helidon
Simple Scheduling in Helidon
Scheduling in Kubernetes
Summary
Chapter 12:​Integration with Other Technologies
Neo4j
Enabling Metrics and Health Checks
Coherence
Integrating with Helidon
Verrazzano
Deploying the Helidon Wizard Application
Summary
Chapter 13:​Going Reactive
Reactive Streams
Reactive Operators
Marble Diagrams
MicroProfile Reactive Streams Operators
of
empty
failed
generate
iterate
fromCompletionSt​age
fromCompletionSt​ageNullable
fromPublisher
concat
map
peek
filter
limit
takeWhile
dropWhile
skip
flatMap
flatMapCompletio​nStage
flatMapIterable
onComplete
onError
onErrorResume
onErrorResumeWit​h
onTerminate
cancel
reduce
distinct
findFirst
forEach
ignore
toList
to
via
Helidon Reactive Operators
Reactive Messaging
Channel
Emitter
Message
Acknowledgment
No Acknowledgment
Messaging Health
Messaging Connectors
Kafka Connector
Nack Strategies
JMS Connector
Injected ConnectionFactor​y
Lookup ConnectionFactor​y over JNDI
Destination
Message
javax vs.​jakarta JMS
WebLogic JMS Connector
WebLogic Destination CDI Syntax
JNDI Destination
Oracle AQ Connector
Single Consumer Queue
Multi-Consumer Queue
Mock Connector
Summary
Chapter 14:​Long Running Actions (LRA)
LRA Transaction
Context Propagation
Participant
Complete
Compensate
Status
Forget
AfterLRA
Leaving LRA
Non-JAX-RS Participant Methods
Asynchronous Compensation
LRA Coordinator
Narayana LRA Coordinator
MicroTx
Experimental Helidon LRA Coordinator
Online Cinema Booking System
Summary
Chapter 15:​Helidon SE
Helidon SE Basics
Generating Helidon SE Application
Using Project Starter
Using CLI
Analyzing the Generated Project
Main Method
Creating and Starting a Web Server
Configuration
Routing
RESTful Services
Health Checks
Metrics
Building and Packaging
Other Helidon SE Features
Summary
Index
About the Authors
Dmitry Kornilov
is the Director of Software Development
at Oracle. He leads the Helidon project
and actively participates in Jakarta EE
and MicroProfile communities. Dmitry is
an open source enthusiast and speaker
who has earned the Star Spec Lead and
Outstanding Spec Lead awards.

Daniel Kec
is a Java developer at Oracle working on
the Helidon project. Daniel has been a
Java developer for 15 years and is
currently working at Oracle on the
Helidon project, which enabled him to
tinker with the coolest open source
technologies. While working on reactive
operators, reactive messaging, and Long
Running Actions (LRA) for Helidon was
fun, working with Loom features opens
another chapter for an otherwise
passionate reactive devotee.
Dmitry Aleksandrov
is a software developer at Oracle and a
Java Champion and Oracle
Groundbreaker. He is currently working
on the Helidon project. He is a co-lead of
the Bulgarian Java Users Group and co-
organizer of the jPrime conference.
About the Technical Reviewers
Tomas Langer
is the architect of the Helidon project. He
has been with the project since its
inception. In his career, he has always
been involved with service development,
both hands-on and from an architectural
point of view. He has also been in
architecture and enterprise architecture
roles, focusing on integration, security,
and technology.

David Kral
is a Java software developer on the
Helidon project at Oracle, with more
than seven years of experience designing
and developing Enterprise Java
components and APIs. He is an active
open source contributor, mainly to
project Helidon, but also contributing to
the various related Jakarta EE projects
such as JSON-B, Yasson, and Jersey.
© The Author(s), under exclusive license to APress Media, LLC, part of Springer
Nature 2023
D. Kornilov et al., Beginning Helidon
https://doi.org/10.1007/978-1-4842-9473-4_1

1. Introduction
Dmitry Kornilov1 , Daniel Kec1 and Dmitry Aleksandrov2
(1) Praha, Czech Republic
(2) Sofia, Bulgaria

This chapter covers the following topics.


Understanding cloud-native application
Introducing Helidon
Explaining Helidon flavors
Discovering which flavor is suitable for your application
We live in a cloud computing era, and it dictates its application
design requirements. This book is about building cloud-native
applications.

Note Applications designed to operate in a cloud environment


utilizing all cloud benefits are called cloud native.

The main benefit of the cloud is the ability to scale applications quickly
—something we didn’t have when we hosted applications in data
centers. For example, in the cloud, you can scale up your application
during working hours when it’s intensively used and then scale it down
for the rest of the time. Another example is testing. You can deploy an
extensive testing infrastructure quickly and dispose of it quickly when
your testing is finished. In both instances, you are not paying extra for
resources you don’t need. This makes the cloud environment very cost-
efficient when a good application design is used.
So, what makes good cloud-native application design? Well, it
depends on what you want to achieve. The most common scenario is
using microservices deployed on Kubernetes. Microservices
architecture allows you to scale some parts of your application
independently. Kubernetes is a de facto standard container
management system. It can be deployed on-premises or in the cloud. All
big clouds provide managed Kubernetes services. This book does not
discuss Kubernetes setup and microservices architecture. Instead, it
concentrates on the design of individual services.
And what’s the right design for your service? Cloud-native
applications have some specific requirements.
Your application should be containerized. It’s a requirement to make
it run in a Kubernetes environment.
Your application should be observable. It should provide some
telemetry data to help identify and quickly fix problems. For example,
you want to only redirect user requests to a node that has been fully
initialized, or you may want to restart a node running out of memory.
Or you may want to know which operations take more time to
execute for optimization. Observability is not a requirement, but it
makes your life much easier.
Your application should start fast. The sooner you start your node,
the sooner it will serve requests. Time is money.
Your application should consume the least amount of memory
possible. In a typical cloud environment, you pay for RAM. The less
RAM used, the less you pay.
Your application disk image footprint should be as small as possible.
You pay for disk space. The less disk space used, the less you pay. You
are also paying for traffic. The smaller your application, the less
traffic is used.
Java EE was an excellent choice for building on-premises, back-end
applications for years. While it can be used to build cloud-native
applications, it could be better.
It’s like using an LTE phone in a 5G network. Will it work? Yes. Will
it work fast? Indeed. Will it use all the benefits of 5G? No. Will it work as
fast as devices with native 5G support? Certainly not.
There was a need for a new Java framework for building cloud-
native applications to compete with Spring Boot. This is why Oracle
started working on the Helidon framework.

Introducing Helidon
A product name should reflect its purpose and trigger proper brain
associations. “Bulldog” or “Elephant” would not be good names. We
wanted a name that could convey something small, lightweight, and
fast, like a bird. “Swallow” would be a perfect fit. Wikipedia says it has
“a slender, streamlined body and long pointed wings, which allows for
great maneuverability and… very efficient flight.” Perfect for darting
through the clouds. We wanted to know how swallow sounded in other
languages. In Greek, for example, it’s Χελιδό νι. We slightly changed it
and proposed Helidon to the team. There were other options, but
Helidon was the clear winner.
Here is a one-sentence description: Helidon is a set of Java libraries
for developing cloud-native services. This definition is very general, and
it is challenging to understand all its nuances. But if someone asks what
Helidon is, it’s a good answer. It’s clear and doesn’t include any
confusing subatomic and supersonic concepts.
Helidon was designed to achieve the following high-level goals.
Performance by design
Cloud-native
Embrace Java SE
Compatibility with modern Enterprise Java standards
Support GraalVM Native Image
The first goal is very clean. The whole Helidon design is done with
performance in mind. The core of Helidon is the reactive web server
built on top of Netty.

Note Netty is an asynchronous event-driven network application


framework for rapidly developing maintainable high-performance
protocol servers and clients. (See more at https://netty.io.)
The reactive non-blocking implementation allows Helidon to achieve
impressive performance numbers. Oracle’s performance tuning team
worked closely with us to tune Helidon’s performance, and our users
are happy with the results.
The cloud-native goal is listed second, but it’s a primary goal.
Helidon is created as a tool for developing cloud-native applications. It
provides a fast start-up time, low memory consumption, a small disk
footprint, and all other cloud-native application features listed in the
previous section.
The third goal is embracing Java SE. Embrace in this context means
several things. We are trying to quickly adopt the latest Java versions
and use new Java features. For example, Helidon is fully modularized
and leverages jlink advantages. We heavily use the Flow API and rely on
java.util.logging. Another advantage is minimizing the number
of third-party dependencies. We do not use third-party libraries if the
same functionality is achievable using pure JDK. As a result, we only
depend on half of the Internet, and the Helidon application footprint
becomes reasonably tiny. Also, it saves time for our corporate
customers who need legal approvals for all third-party dependencies
used in their applications.

Note Helidon keeps the number of third-party dependencies low


by design.

And the fourth goal is compatibility with modern Enterprise Java


standards. There are two standards in the Enterprise Java world now:
Jakarta EE and MicroProfile. Helidon fully supports MicroProfile and
partially Jakarta EE. Why? Because the standards are reducing entropy.
Systems built on standards are portable and highly maintainable.
Developers get APIs, and the development experience they are used to,
and architects get confidence that the system will be upgradable and
supportable. Jakarta EE is a successor of Java EE. It makes Helidon a
good choice for migrating old Java EE-based applications to
microservices. All supported specifications are covered later in the
book.
GraalVM Native Image creates a native executable from your Java
application. With GraalVM Native Image, you don’t need JVM anymore.
Your application will be compiled into one executable file. It starts in
milliseconds and consumes less RAM, making it perfect for cloud-native
applications, mainly functions. Supporting GraalVM Native Image
became a standard feature for modern microservices frameworks like
Quarkus and Micronaut. Even Spring, with its runtime nature, now
supports it with Spring Native. Helidon is not an exception. It supports
the native image in all flavors. Jumping ahead a bit, it’s worth
mentioning that Helidon makes it possible to use CDI (including
portable extensions) with the native image. Other frameworks usually
don’t support it because of the runtime nature of CDI extensions.
The work that ultimately became Helidon started in 2017, but there
were several prototypes before the main design concepts were shaped.
The primary focus was made on reactive APIs. It was a popular concept
allowing us to achieve excellent performance. It was inspired by Netflix
—like most other reactive frameworks at the time. We wanted to create
a lightweight set of libraries that didn’t require any application server
runtime, allowing your application to be a standard Java SE application.
These libraries could be used separately from each other, but when
used together, they would provide everything a developer needs to
create a cloud-native service.

Note Helidon is an open source product hosted on GitHub at


https://github.com/helidon-io/helidon and licensed
under the Apache 2.0 license.

The open source model is now the obvious choice for modern
frameworks and libraries. It gives users transparency and the ability to
check our progress and provide contributions.

Note Oracle requires all external contributors to sign the Oracle


Contributor Agreement (OCA) to get their contributions accepted.
It’s an easy process. You can find more details about it at
https://oca.opensource.oracle.com/.

Helidon Flavors
Helidon flavors are different APIs providing different development
experiences you can use to develop your application.
For example, imagine that you are playing a computer game. Your
goal is to kill the last boss and save the planet. When you start playing,
you must choose your character, and you have several options, like
warrior or sorcerer. The gaming mechanics of these two are different.
Warrior is a melee character with a sword, and a sorcerer uses magic
and range attacks. Despite that, you can finish the game and kill the
final boss using any of them.
In this example, the game is an application you are developing, and
the characters you choose to play are the different programming
approaches you can choose. Helidon offers two characters, called
flavors.

Note Helidon has two flavors: Helidon MP and Helidon SE.

To better understand the differences, let’s look at two snippets of code.


Both implement a simple RESTful service that returns “Hello World”
when a Get request is sent to the /hello endpoint.
Listing 1-1 shows how it looks in Helidon MP.

@Path("hello")
@ApplicationScoped
public class HelloWorld {
@GET
public String hello() {
return "Hello World";
}
}
Listing 1-1 Helidon MP Code-Style Sample

Listing 1-2 shows how it can be done in Helidon SE.

Routing routing = Routing.builder()


.get("/hello", (req, res) ->
res.send("Hello World"))
.build();
WebServer.create(routing)
.start();
Listing 1-2 Helidon SE Code-Style Sample

The differences are collected in Table 1-1.


Table 1-1 Helidon Flavors Comparison

Helidon MP Helidon SE
Declarative style APIs Functional style APIs
Blocking, synchronous Reactive, non-blocking
Small memory footprint Tiny memory footprint
Annotations are heavily used No annotations
Jakarta Contexts and Dependency Injection (CDI) No dependency injection
Full support of MicroProfile and partial support of No Enterprise Java standards
Jakarta EE support

The flavors are a logical consequence of the Helidon design (see


Figure 1-1).

Figure 1-1 Helidon architecture

Helidon SE forms a high-performance lower level, and Helidon MP


is built on top of it. It explains why both flavors pretty much provide the
same functionality. A Helidon feature is implemented in Helidon SE, and
a thin adapter layer is built in Helidon MP. It helps to achieve high
performance.
Helidon MP
Helidon MP is a flavor that supports modern Enterprise Java standards.
It’s designed for ease of use and provides a Spring Boot-like
development experience with heavy usage of dependency injection,
annotations, and other magic. As a drawback, you have less control
because the framework does many things automatically. This drawback
should not be considered a showstopper. It’s a relatively small
limitation, but it’s still worth mentioning to make this book fair.
Referring to the game sample, Helidon MP is a sorcerer. Magic
makes him a powerful opponent. Magical attacks are solid and magical
shields are effective. It’s easy to play as a sorcerer, even without a
complete understanding of the nature of magic.

Modern Enterprise Java Standards


Jakarta EE is a new name for Java EE since it’s been transferred to
its new home at the Eclipse Foundation. As of this writing, it
contained 40 individual and three platform specifications. There are
specifications Enterprise Java developers use on almost every single
project, such as CDI, JAX-RS, and JSON-P/B, as well as new
specifications, such as Jakarta EE Core Profile and Jakarta Config.
Jakarta EE is supported by major industry players such as Oracle,
IBM, RedHat, Payara, and Tomitribe. Jakarta EE always cared about
backward compatibility and stability, making it a perfect fit for
enterprise applications. (See more at https://jakarta.ee.)
MicroProfile is a collection of open source community-driven
specifications designed to help build cloud-native applications using
Enterprise Java. It makes it an excellent extension to Jakarta EE.
MicroProfile is an Eclipse Foundation project using a specification
process derived from Eclipse Foundation Specification Process
(EFSP). Unlike Jakarta EE, MicroProfile has a faster release cadence
delivering three releases per year: one major and two minor. Fast
innovations and staying at the edge of modern technologies often
mean breaking backward compatibility. Although preserving
backward compatibility is not a goal, MicroProfile tries to minimize
the number of backward incompatible changes and follows the
semantic versioning model to deliver breaking changes only in major
releases. More information can be found on MicroProfile’s official
web page at https://microprofile.io.

As shown in Figure 1-2, Helidon MP supports the whole MicroProfile


5.0 platform, which includes ten specifications and eight Jakarta EE
specifications which include Jakarta CDI and Jakarta RESTful Web
Services (former JAX-RS). CORS and gRPC are two components not
covered by any specifications; they are Helidon-specific.

Figure 1-2 Helidon MP components

There are different opinions about what MP in Helidon MP means.


Some people think that MP comes from MicroProfile. But there is also
another opinion that MP means Magic Powered.

Helidon SE
I already said that Helidon is based on the reactive web server built on
Netty. It forms a reactive, non-blocking flavor called Helidon SE.
Reactive Programming is a big topic that deserves a whole book. It is
discussed briefly in Chapter 15.
Helidon SE features APIs based on Java SE Flow API. It intensively
uses the Builder pattern, fluent APIs, and lambdas. Neither annotations
nor dependency injection is used. We say, “No magic involved.” The
produced code is very clean, and you have complete control over what
the code is doing because the framework doesn’t generate any code at a
build or run time. It naturally makes Helidon SE a perfect fit for
GraalVM Native Image. A drawback is that more coding is required.
Also, reactive programming is not easy to use. Is this drawback big? It
depends on your programming experience. If you are familiar with
reactive programming, it’s small. If not, it’s bigger.
The reactive web server APIs are inspired by Express. It makes
Helidon SE a good choice for JavaScript developers who want to switch
to Java.

Note Express is a fast, unopinionated, minimalist web framework


for Node.js. (See more at https://expressjs.com.)

Referring to our game example, Helidon SE is a warrior with fast dual-


wield short swords and no armor. Light weight allows him to move fast
and quickly dodge enemy attacks. With the dual-wield weapon, he can
hit his enemy twice as fast. But to use it effectively, you as a player need
to know how to play for this character. One wrong move, one slow
reaction, and your warrior is dead.

Which Flavor Should You Use?

Note Your application must be either a Helidon MP application or


a Helidon SE application.

You can use Helidon SE functionality in your Helidon MP applications,


but not vice versa. It’s a consequence of Helidon’s design. Helidon SE
belongs to a lower layer and isn’t aware of Helidon MP’s existence. Also,
most Helidon MP features require an initialized CDI container which
doesn’t exist in Helidon SE by design. Referring to my game example,
you cannot play with two characters simultaneously. But your sorcerer
can use a dagger sometimes when magic is not efficient enough.
A good example of using Helidon SE functionality in Helidon MP is
reactive messaging (covered in Chapter 13).
So which flavor should you use in your application? When in doubt,
use Helidon MP.

Tip If you don’t know which flavor to use, use Helidon MP.

Table 1-2 is a collection of recommendations.

Table 1-2 Helidon Flavors Recommendations

Use Helidon MP Use Helidon SE


• You don’t know what flavor to • Performance achieved by heavy usage of
choose. concurrency is your primary goal.
• You want to use CDI and other • You want to have complete control of
MicroProfile or Jakarta EE APIs. your application.
• You are migrating from the existing • You have experience with reactive
Java EE/Jakarta EE application. programming.
• You are a Spring Boot or Java EE • Your application deals with uploading
developer and want a similar files.
development experience. • Your application is not CDI based, and
you are not planning to use any
MicroProfile and Jakarta EE APIs.

This book is mainly about Helidon MP, but Helidon SE is briefly


covered in Chapter 15.

Summary
Applications designed to operate in a cloud environment utilizing all
cloud benefits are called cloud native.
Helidon is a set of Java libraries for developing cloud-native services.
Helidon comes in two flavors: Helidon MP (declarative style APIs
implementing MicroProfile and some Jakarta EE specifications) and
Helidon SE (reactive, non-blocking APIs).
If you don’t know which flavor to use, use Helidon MP.
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
D. Kornilov et al., Beginning Helidon
https://doi.org/10.1007/978-1-4842-9473-4_2

2. Your First Application


Dmitry Kornilov1 , Daniel Kec1 and Dmitry Aleksandrov2
(1) Praha, Czech Republic
(2) Sofia, Bulgaria

This chapter covers the following topics.


Creating a Helidon application using Project Starter, a command-line interface (CLI), or
Maven archetypes
Building an executable JAR, jlink-optimized JVM, and GraalVM Native Image
Making a Docker image and deploying it to Kubernetes
This is the first practical chapter offering hands-on coding. It’s interesting how book-
writing methods have evolved. In the early 1990s, technical books were quite boring to read.
The authors didn’t care much about making the book entertaining. Studying is work, and work
is challenging. Reading books requires thinking about every sentence and understanding the
author’s words. It was difficult, but it forced your brain to work. Now it’s changed. Technical
books try to be easy and fun to read while delivering the same amount of pure knowledge.
These books are also easier and more fun to write. We want to make our book easy to read; this
short introduction is part of “making it fun.”

Generating Your First Application


What’s the first thing a developer starting with Helidon should do? Create a project and start
coding, of course. Let’s do that now.

Tip There are three ways to create a new Helidon project: a command-line interface (CLI),
Project Starter, or Maven archetypes.

Helidon CLI
Helidon CLI is a command-line utility that simplifies your work with Helidon. Using CLI, you
can create a project based on provided templates. It also has a feature called developer loop.
When a source code change is detected, it automatically recompiles and restarts your
application. We’ll take a deeper look at it later. Now, let’s install CLI and generate our first
project.
First, you must ensure that JDK 17 and the latest version of Maven are installed. To check it,
type java -version and mvn -version in your terminal. You should have an output
similar to the following.

$ java -version
java version "17.0.2" 2022-01-18 LTS
Java(TM) SE Runtime Environment (build 17.0.2+8-LTS-86)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.2+8-LTS-86, mixed
mode, sharing)

$ mvn -version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: /usr/local/Cellar/maven/3.8.6/libexec
Java version: 18.0.1.1, vendor: Homebrew, runtime:
/usr/local/Cellar/openjdk/18.0.1.1/libexec/openjdk.jdk/Contents/Home
Default locale: en_RU, platform encoding: UTF-8
OS name: "mac os x", version: "12.5", arch: "x86_64", family: "mac"
If not, please install JDK and Maven. Detailed instructions on how to do this can be found on
the Internet.
Now, let’s install CLI. Installation commands depend on the operating system you are using.
Use the following if you are on macOS.

curl -O https://helidon.io/cli/latest/darwin/helidon
chmod +x ./helidon
sudo mv ./helidon /usr/local/bin/

Use the following if you are on Linux.

curl -O https://helidon.io/cli/latest/linux/helidon
chmod +x ./helidon
sudo mv ./helidon /usr/local/bin/

If you are on Windows, you must run PowerShell as administrator and execute the
following command.

PowerShell -Command Invoke-WebRequest -Uri


"https://helidon.io/cli/latest/windows/helidon.exe" -OutFile
"C:\Windows\system32\helidon.exe"

Type helidon in your command prompt to test that CLI has been installed. A screen with
short instructions should confirm the successful CLI setup.

$ helidon

Helidon command line tool

Usage: helidon [OPTIONS] COMMAND

Options

-D<name>=<value> Define a system property


--verbose Produce verbose output
--debug Produce debug output
--plain Do not use color or styles in output

Commands
build Build the application
dev Continuous application development
info Print project information
init Generate a new project
version Print version information

Run helidon COMMAND --help for more information on a command.

When CLI is installed, it’s time to generate your first project and see what’s been generated.
Use the init command to initiate the process.

$ helidon init

You are asked questions about a project you want to generate. The first question is about a
Helidon flavor to use in your project.

| Helidon Flavor

Select a Flavor
(1) se | Helidon SE
(2) mp | Helidon MP
Enter selection (default: 1):2

You want to use the MP flavor, so type 2 in the command prompt.


The next question is about the type of project to generate.

| Application Type

Select an Application Type


(1) quickstart | Quickstart
(2) database | Database
(3) custom | Custom
Enter selection (default: 1):1

There are three options.


Quickstart generates a Maven project with all dependencies, Dockerfiles, Kubernetes
application descriptors, and a simple greeting service application containing a sample of a
RESTful service and all needed bootstrap code. If you plan to develop a RESTful service, it’s a
good option.
Database is the best option if your application works with databases. The generated project
contains all needed third-party dependencies, configuration files, and bootstrap code.
Custom offers more choices and allows fine-grained customization of your project. It asks
about the media support you want and whether you want to enable metrics, health checks,
and tracing. Also, it asks about database support and allows you to choose between
Hibernate and EclipseLink.
Let’s use the Quickstart template. Type 2 in the command prompt.
The next question concerns a JSON library you want to use in your project. There are two
options.
Jackson is a popular library for binding Java classes to JSON objects. It’s the default choice.
JSON-B is Jakarta JSON Binding specification implementation. To be more specific, Yasson is
used. Choose this option if you want to be fully standards-compliant.
Let’s use JSON-B for this sample application.

Select a JSON library


(1) jackson | Jackson
(2) jsonb | JSON-B
Enter selection (default: 1): 2

And the last series of questions is about the Maven coordinates and Java package names to
use. Feel free to use defaults for this sample. You should change it to a real application.

Project groupId (default: me.dmitry-helidon):


Project artifactId (default: quickstart-mp):
Project version (default: 1.0-SNAPSHOT):
Java package name (default: me.dmitry.mp.quickstart):

Switch directory to /Users/dmitry/quickstart-mp to use CLI

Start development loop? (default: n):

Tip Development Loop is a CLI mode that keeps your application running and observes
its source code for changes. When changes are detected, your application is automatically
recompiled and restarted.
Use the CLI helidon dev command to start the development loop.

You can use batch mode if you want one command to generate your project. In batch mode, you
answer all questions in the init command parameters.
You can use the following command to generate the same project.

helidon init --batch --flavor MP --archetype quickstart

A complete list of parameters can be found on the help page for the helidon init
command.

helidon init --help

Project Starter
Project Starter is a web application allowing the generation of Helidon projects (see Figure 2-
1). It has the same features as the CLI described in the previous section.
To open the Project Starter, open https://helidon.io/starter in your browser or
press the Starter button at the top of the Helidon home page at https://helidon.io.
Figure 2-1 Project Starter
Project Starter guides you through multiple steps where you should select an
option/feature you want to include in your project. The number of steps can vary depending on
your previous choices. Still, you can click Download at any stage to fill all unvisited pages with
defaults and download a zip file containing the generated project.

Helidon Maven Archetypes


Another method of generating a Helidon project is using Maven archetypes. Helidon provides
Maven archetypes for all options offered by CLI.
The following shows how to generate the same Quickstart application using CLI.

mvn -U archetype:generate -DinteractiveMode=false \


-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-mp \
-DarchetypeVersion=3.0.0 \
-DgroupId=me.dmitry-helidon \
-DartifactId=quickstart-mp \
-Dpackage=me.dmitry.mp.quickstart

A complete list of Maven archetypes and corresponding CLI options are in Table 2-1.

Table 2-1 Helidon Maven Archetypes and Corresponding CLI Options


Maven CLI Option Description
Archetype
helidon-bare- --flavor MP -- Helidon MP application with minimum dependencies
mp archetype bare

helidon- --flavor MP -- Sample Helidon MP project that includes multiple REST


quickstart-mp archetype quickstart operations (greeting service) (It is analyzed later in this chapter.)
helidon- --flavor MP -- Helidon MP application that uses JPA with in-memory H2
database-mp archetype database database
helidon-bare-se --flavor SE -- Minimal Helidon SE project suitable to start from scratch
archetype bare

helidon- --flavor SE -- Sample Helidon SE project that includes multiple REST


quickstart-se archetype quickstart operations (greeting service)
helidon- --flavor SE -- Helidon SE application that uses Helidon DBClient with in-
database-se archetype database memory H2 database

Analyzing Generated Project


Quickstart Application
Congratulations! You just created your first Helidon application. This simple but fully
functional greeting service can greet the world and a given user. It allows you to customize the
greeting, fully supports health checks, metrics, and tracing, uses externalized configuration,
and contains a Docker build file and Kubernetes deployment descriptor. It’s a great candidate
to bootstrap your bigger service.
You can see a full REST API description with invocation samples in Table 2-2.

Table 2-2 Quickstart Application REST API

Endpoint Description and Sample


GET /greet Greets the world
curl -X GET http://localhost:8080/greet
{"message":"Hello World!"}

GET Greets the specified user


/greet/{user} curl -X GET http://localhost:8080/greet/Dmitry
{"message":"Hello Dmitry!"}

PUT Changes the greeting


/greet/greeting curl -X PUT -H "Content-Type: application/json" -d '{"greeting" :
"Hola"}' http://localhost:8080/greet/greeting
curl -X GET http://localhost:8080/greet/Dmitry
{"message":"Hola Dmitry!"}
GET /health Health check
curl -s -X GET http://localhost:8080/health
GET /metrics Metrics in Prometheus format
curl -s -X GET http://localhost:8080/metrics
Metrics in JSON format
Endpoint Description and Sample
curl -H 'Accept: application/json' -X GET
http://localhost:8080/metrics
Listing 2-1 shows what’s been generated.

$ tree quickstart-mp/
quickstart-mp
app.yaml ①
Dockerfile ②
Dockerfile.jlink ③
Dockerfile.native ④
pom.xml ⑤
README.md
src
main
java
me
dmitry
mp
quickstart
GreetingProvider.java ⑥
GreetResource.java ⑦
Message.java
SimpleGreetResource.java
package-info.java
resources
META-INF
beans.xml ⑧
microprofile-config.properties ⑨
native-image
reflect-config.json ⑩
application.yaml
logging.properties
test
java
me
dmitry
mp
quickstart
MainTest.java ⑪
resources
application.yaml
Listing 2-1 Generated Quickstart Application Source Code

① Kubernetes deployment descriptor


② Dockerfile to build a Docker image with your application running on standard Java
runtime
③ Dockerfile to build a Docker image with your application running on custom Java runtime
(jlink image)
④ Dockerfile to build a Docker image with your application’s native image
⑤ Maven project
⑥ Application scoped Jakarta Enterprise bean
⑦ JAX-RS resource serving REST requests
⑧ CDI bean archive descriptor
⑨ Configuration properties of the Quickstart project
⑩ Configuration file for fine-tuning GraalVM native image build
⑪ Example JUnit test
This small application includes everything you need to build a fully functional RESTful web
service, test it, package it as a Docker image, and deploy it to Kubernetes. This chapter explains
how to do it, starting with analyzing the GreetingResource class representing a RESTful
web service.

Maven Project
The Maven pom.xml is created automatically as part of Quickstart application generation. It’s
clean and straightforward. It can be opened with all IDEs supporting Maven projects. As heavy
IntelliJ Idea users (this book is written in IntelliJ!), we can confirm that it opens without
problems.
There is only one dependency required to build a MicroProfile application.

<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile</artifactId>
</dependency>

It’s a bundle containing all dependencies required by the MicroProfile platform. It makes
sense to use it during your application development phase.
If you don’t use all MicroProfile features, you can minimize the dependencies, decreasing
your application footprint. In this case, you can use the helidon-microprofile-core bundle,
which contains only a minimal set of dependencies, and add all other dependencies manually.

<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile-core</artifactId>
</dependency>

CDI
Jakarta Contexts and Dependency Injection (CDI) is a key part of all MicroProfile applications.
It wires all components altogether and enables injection in users' applications. Helidon MP is a
big CDI container that starts automatically when your application starts.

Jakarta Contexts and Dependency Injection (CDI)


CDI is a dependency injection (DI) specification. It’s a part of Jakarta EE and used to be a
part of Java EE earlier. CDI is heavily annotation based. It allows users to define beans,
manage their life cycle using contexts, and inject them to other managed beans using
constructor injection, field injection, or setter injection. In addition to that, CDI offers other
valuable features such as interceptors, decorators, and event notifications. It’s highly
customizable and integration-friendly. CDI provides functionality similar to what Spring
Dependency Injection (Spring DI) does. There are several CDI implementations on the
market. Helidon MP is using Weld (https://weld.cdi-spec.org).
The best way to learn about CDI is to read the specification itself. You can find it here:
https://jakarta.ee/specifications/cdi/3.0/jakarta-cdi-spec-
3.0.html.

The injection works only with CDI-managed beans. If CDI does not manage your class, you
won’t be able to inject it.

The easiest way to make your class a CDI-managed bean is to assign it a scope by annotating
it with @RequestScoped, @ApplicationScoped, or @Dependent.

A @RequestScoped bean’s life cycle is tied to an HTTP request life cycle. It’s a singleton
per request. A new object is created for each HTTP request and shared with other objects
within this request life cycle.
@ApplicationScoped beans are singletons. They are created once and shared with
other objects in your application.
A @Dependent bean’s life cycle is tied to the bean it is injected into. These beans are not
singletons. A new instance is created for every injection point and never shared.
Listing 2-2 shows how the injection is used in our Quickstart application.

@ApplicationScoped ①
public class GreetingProvider {
...
@Inject ②
public GreetingProvider(
@ConfigProperty(name = "app.greeting") String message) {
...
}
...
}
Listing 2-2 CDI Usage in GreetingProvider.java

① It makes this class a singleton. It’s created only once, and this instance gets injected into
other objects.
② It’s an example of constructor injection. A configuration property gets injected into the
constructor. The configuration is discussed in Chapter 3.

@RequestScoped ①
public class GreetResource {
...
@Inject ②
public GreetResource(GreetingProvider greetingConfig) {
...
}
...
}
Listing 2-3 CDI usage in GreetingResource.java
Another random document with
no related content on Scribd:
CHAPTER XVII
PLOT AND COUNTER-PLOT

Next day, Feng having left for Edinburgh to visit some friends,
Thelma and I traveled to London together. At King’s Cross I saw her
into a taxi, for she was going to Highgate to spend a few days with a
girl cousin, and myself went across to Russell Square.
Mrs. Chapman was greatly excited at my return, and was eager to
know exactly what had happened, for already Hensman had been
round and told her of my accident.
“Yesterday, about four o’clock, a gentleman called, sir,” my old
servant went on. “He was very anxious to see you, and seemed
worried that you were away. I told him I expected you back today.
Then, after hesitating a little, he asked leave to come in and write a
note for you. He’s left it on your table, sir.”
“Who was he?”
“I’ve never seen him before, sir. He was a tall man with a long
hooked nose, and a thin face deeply lined.”
It sounded very like a description of my affable friend from Bradford!
“Did you notice his tie-pin?” I asked.
“Yes, sir. It was a funny one—like a little eye.”
I dashed into my room where upon my blotting-pad lay a letter. This I
tore open and read. It was written in the same handwriting as that
mysterious letter to the Coroner, and upon a sheet of my own note-
paper.
“Do you refuse to be warned?” it read. “Drop your search
for Stanley Audley, or next time steps will be taken to
prevent you from escaping. It is known that you love
Thelma, and that is forbidden, for Stanley Audley still lives,
and is watching you!”
There was no signature. I took from my pocket the strange letter left
in my bedroom and compared them. The writing was exactly similar.
“How long was the man here?” I asked of Mrs. Chapman, on
entering the little kitchen of the flat.
“Oh! about ten minutes, sir. He seemed very busy writing, so I left
him.”
“Ten minutes!” I echoed. “Six lines of writing could not take that
time!”
Clearly there must be another reason why my home should have
been so boldly entered, so I dashed back to my room and on
opening the drawers of my roll-top desk I found three of them in
disorder, as though they had been hurriedly searched.
At once I realized what had gone. All the letters I had received from
Thelma I had kept tied up with pink tape because of my legal
training, I suppose. They had been lying in the bottom drawer on the
right hand side. It was not my habit to lock up anything from my old
and trusted servant, hence the desk had not been closed down. Had
it been, the drawers would have locked themselves automatically.
The letters were no longer there! The mysterious visitor had
evidently sought for and found them. Was the intention to place them
in the hands of the missing man? Or was it blackmail?
Every incident in the queer tangle of events seemed to add a further
puzzle to the mystery of Stanley Audley and his associates. An
intention to levy blackmail might explain the theft of the letter, though
they were innocent enough. But they did not explain the attack on
myself and the constant espionage to which I was subjected. Why
should I be marked down for assassination? That I had made a
foolishly romantic promise to act as guardian and protector of a
pretty bride, was not enough to answer that question.
Each day that passed since that fateful afternoon amid the silent
Alpine snows had increased the mystery which surrounded Stanley
Audley. Was he a crook, an associate of an unscrupulous
international gang of forgers—or was he after all, an honest man? If
only Thelma would speak! But it was obvious her lips were sealed,
and I felt convinced they were sealed by fear. Someone, it was
obvious, had some hold over her which enabled him to command
her silence. It was her duty as a wife, she claimed, to preserve her
husband’s secrets inviolable. But what was the secret?
I returned to the office next day depressed and puzzled to the last
degree. I was hardly conscious of what I was doing. As in a waking
dream I lived through the agony I had gone through at Stamford.
Time and again I seemed to feel that cold thing on my lips; the small,
evil-looking eye I had seen in my half-consciousness seemed to
glare balefully at me even in the broad daylight. And time after time,
as I sat in my office striving wearily to read letters and dictate
coherent replies, Thelma’s exquisite face appeared to float in the air
before me. Distraught and overwrought I realized at last that work
was hopeless and hurriedly left the office.
For hours I tramped the London pavements, tormented by thoughts
of Thelma, racking my brain for some possible way out of the
horrible position in which I found myself. It must have been far into
the morning before—quite automatically—I staggered homeward
and flinging myself, dressed as I was, upon my bed, fell into the
deep stupor of utter exhaustion.
Four days after my return to London I happened to be passing along
Pall Mall, when a sudden fancy took me to call upon old Humphreys.
There another surprise awaited me.
“Mr. Humphreys is away, sir—in Edinburgh,” the fair-haired clerk at
the key-office informed me.
Edinburgh! Old Feng had left me suddenly to go there! Was it a
coincidence, or were they meeting in Scotland for some purpose?
“We expect him back tomorrow night,” the young man added.
So I turned away.
Next day, knowing that Thelma was going shopping with her cousin
in the West End, I spent the afternoon wandering in Regent Street in
the hope of meeting them. I had telephoned to Highgate with the
intention of making an appointment and taking them to tea, but they
had already left. Thelma’s aunt, who spoke to me, had mentioned
several shops they intended visiting, and I had spent nearly an hour
and a half in search of them, when suddenly near the Oxford Circus
end of Regent Street, I noticed a rather shabbily dressed old man
standing at a window, examining the jewelry displayed.
Next second my heart gave a bound. It was Doctor Feng, but so well
disguised was he that I was compelled to look twice in order to
reassure myself that I was not mistaken. Gone was the erect alert
figure I knew so well. The man before me stooped heavily, with his
chin kept well down; Doctor Feng’s usually well-cut and well-tended
clothing had given place to garments utterly frayed and shabby,
while the old felt hat on his head was badly stained and worn.
Instantly I drew back in astonishment, not wishing to reveal myself.
For what reason was he idling there in that garb? He presented a
broken-down appearance, as if he were a professional man who had
fallen on evil times.
It was clear that his interest in the jewelry was only feigned, and
before long I saw he was keenly watching the entrance to a well-
known milliner’s, though from such a position he was not likely to
attract the notice of anyone emerging.
I stood there watching the watcher, for perhaps ten minutes. Then
Thelma and her cousin came out and turned towards Piccadilly
Circus. Feng at once moved slowly on, following their movements. I
was within a few yards of him, but so intent was his watch upon the
two girls that he never once turned round. Otherwise he would
almost certainly have seen me, for I knew his eyesight was
remarkably good.
He watched them enter two shops, keeping himself well away from
observation. At last they entered a tea-shop. Then having apparently
satisfied himself that they had seated themselves, he strolled away.
In about a quarter of an hour he returned, and so suddenly did he re-
appear that I was half afraid that he must have seen and recognized
me. A few minutes later, however, it became clear that he had not,
for again he stood idly looking into a neighboring shop window.
When Thelma and her cousin came out they crossed the road, and
walked to Piccadilly Circus, where they entered a well-known
draper’s. It was then after five o’clock.
Again old Feng lounged outside while I, fearing recognition,
remained on the opposite side of the road near the entrance to the
Café Monico.
The time passed slowly. The hurrying home-going crowds focussed
upon the Tube station where all had become bustle, and already
half-an-hour had passed. I watched the old man peer into the big
shop every now and then curiously impatient and anxious. It was
plain that he could not see the pair. He must have thought they were
making extensive purchases, for nearly three quarters of an hour
elapsed ere it seemed to dawn upon him that there were two exits
from the shop into Piccadilly!
His chagrin could be plainly seen. Ignorant, of course, that they were
being watched, the two girls had unwittingly eluded his vigilance and
calmly left by the other entrance.
He hurried round the corner amid the crowd awaiting the motor
buses, and then sped back again. It was plain that he was annoyed,
and I thought very considerably perturbed.
Realizing at last that they had eluded him he crossed the Circus and
entered a motor bus which would take him home to Barnes. Then,
having watched his departure, I turned away and walked thoughtfully
back to Russell Square.
On leaving the office early next afternoon, I called upon Hartley
Humphreys, at the Carlton. A page took me up in the lift and
knocked at the door. But before he did so I distinctly heard voices
within and recognized them as those of Humphreys and Feng. They
were laughing loudly together. When they heard the page knock,
they instantly ceased talking. I heard a door communicating with the
adjoining room close, and then Humphreys gave permission to enter.
The old financier sat alone and was most effusive in his welcome.
“So glad to see you, Yelverton!” he cried, grasping my hand. “Sit
down,” and he touched the bell for the waiter. “I’ve been north and
only got back last night. Next week I hope to move into that house at
Hampstead that I’ve bought. I’m sick to death of hotels. You must
come and see me there; come and dine one night.”
I thanked him and expressed great pleasure at his invitation.
Why, I wondered, had Feng hurriedly disappeared? He had passed
into that adjoining room which was a bedroom, and thence, I
supposed, out into the corridor. Or perhaps he was in the next
apartment listening to our conversation.
Over a whiskey and soda I told Humphreys of the desperate attempt
that had been made upon my life, and described all the
circumstances. Somehow I felt confidence in him, even though he
had Harold Ruthen in his employ. I suspected Feng the more
because of the manner in which he had kept secret watch upon
Thelma.
“By jove!” said Humphreys, when I had finished. “You certainly had a
very narrow escape.”
“Yes. But fortunately the dose given was not fatal, though the doctor
has told me that had I swallowed a few more drops I should certainly
have died.”
“But the letter to the Coroner!” remarked the old man. “Your enemy
took care to complete the picture of suicide, didn’t he?”
“I should have had some difficulty in disproving the charge of
attempted suicide if it were not for the handwriting,” I said. “The
assassin did not reckon on the chance that I should escape and
prove the letter to be a forgery!”
Then I told him of the visit paid to my rooms and the theft of
Thelma’s letters.
“Ah!” he said. “It is your association with that little lady which has
brought you into danger. Depend upon it there is some secret
connected with Audley that, at all hazards, has to be kept—even if it
involves plotting your death. You have had a pretty severe warning
and if I were you I should certainly heed it. Whatever the secret may
be—and it clearly must be something very serious—it evidently does
not concern you personally and if you drop the whole affair you will
be safe enough. Surely there is no reason why you should run any
further risk?”
“It concerns Thelma,” I said doggedly, “and for her sake I have
determined, no matter at what risk to myself, and no matter who
threatens me, to elucidate the mystery of Audley’s dual rôle, and his
curious disappearance. For the future at least I shall be forearmed.”
The old man, with knit brows, shrugged his shoulders dubiously.
“Of course I can quite understand, Yelverton,” he said at last with a
smile. “You have fallen in love with her. Oh! it is all very foolish—very
foolish, indeed. I suppose you have discovered a good many things
concerning Stanley Audley?”
“Yes, many curious facts which require explanation,” I said.
“Really?” he asked, interested. “What are they?”
In response, I told him one of two strange things I had discovered
concerning the missing man, at which he expressed himself utterly
astounded.
“I really don’t wonder that the remarkable affair has bewildered you,”
he said at last. “I had no idea that Audley was such a man of
mystery. I thought he had merely left his bride and hidden himself
because he grew tired of her.”
“No. He is hiding because of his fear of somebody—that is my
opinion.”
“Have you any idea where he is?”
“Not in the least,” I replied frankly, at the same time recollecting that
his friend, Ruthen, whom I so disliked, was also in search of
Thelma’s husband.
“But don’t you think that his wife knows his whereabouts?” he asked.
“I cannot form a decided opinion,” was my reply. “Sometimes I think
she does; then at others I feel sure that she firmly believes that he is
dead.”
“You do not believe they hold communication in secret?”
“I think not.”
“What causes her to believe that he is dead, I wonder?”
“Because she obtains no news from him and somebody has told her
so,” was my reply, reflecting that Feng might be listening to our
conversation.
Slowly he placed his cigarette-end in the ash tray at his elbow and
drained his glass.
“Well, Yelverton,” said the calm old cosmopolitan who was once such
a confirmed invalid and whose lameness had happily been restored,
“after all, I don’t see how Audley’s movements concern you—except
for one thing—your indiscreet affection for his wife. Of course the
position does not please you—it is natural that it should not please
you—but if I were you I would drop it all. I agree with Feng that for
you to continue can only lead to unhappiness. More than that you
run a great risk at the hands of some unknown persons whose
desperation is already proved by what happened at Stamford.
Something more serious may yet happen. Therefore,” he added,
regarding me very seriously, “were I in your place I would run no
further risk.”
“I know your advice is well meant, Mr. Humphreys,” I declared. “But I
have made up my mind to solve this mystery, and I will never rest
until I have done so.”
“For Thelma’s sake—eh?” he asked, or rather snapped impatiently.
“Perhaps.”
“Then, of course, you must make up your mind to take the
consequences. You have asked my advice, and I have given it. But if
you pursue an obstinate course,” he said, stroking his thin gray
beard as though in thought, “if you are so foolishly obstinate you will
have yourself alone to blame should disaster fall upon you. I
honestly believe that if you continue, you are a doomed man!”
His tone of voice struck me as highly peculiar: he might almost have
been passing sentence of death upon me!
I had no reason to doubt his friendliness, yet his intimate
acquaintance with Feng, whom I distrusted, puzzled me more than
ever.
“What causes you to think that another attempt may be made upon
me,” I asked again, looking very straight at my companion.
“Has not the past proved the existence of some mysterious plot
against you—that some person or persons are determined that you
shall never learn their secret?” he asked again very seriously.
“Complaisance is always the best policy before anything we cannot
alter.”
I saw the force of his argument, of course, but with firmness replied

“Nothing shall deter me from solving this mystery, Mr. Humphreys.
Nothing.”
CHAPTER XVIII
MISSING!

A week later I was engaged one morning dictating letters to my


typist when Hensman rushed into my room, evidently in a state of
great agitation.
“Can I speak to you for a moment?” he asked. He was pale and
agitated.
At a sign from me the girl left the room. “What’s wrong, old man?” I
said.
“Have you seen the paper this morning?” he asked.
“No, not yet. Why?”
“Then you haven’t seen this,” he said, handing me his copy of the
Times which, as most solicitors do, he was in the habit of scanning
before he began his day’s work.
What I read staggered me. It was as follows:
Missing Lady
“The police are actively in search of Mrs. Thelma Audley,
aged 20, daughter of Mrs. Shaylor, widow of Lieutenant-
Commander Cyril Shaylor, R.N., who left her home at
Bexhill-on-Sea on the morning of the 18th inst. after the
receipt of an urgent telegram calling her to London.
“She did not show the message to anyone, but its receipt
apparently caused her great excitement, for she hurriedly
packed a bag, telling her mother that she would be staying
at the Grosvenor Hotel at Victoria and would return next
day.
“Nothing has since been seen or heard of her. She did not
arrive at the hotel, and it is an open question whether she
actually ever went to London.
“Inquiries show that she did not travel by the train she
intended. But as there are two lines of railway from Bexhill
to London the lady may have taken the second route, by a
train leaving half-an-hour later, which brought a good
many returning excursionists to London, so that she may
easily have passed unnoticed.
“One curious feature of the case is that Mrs. Audley, on
receipt of the telegram, apparently burned it by applying a
match, as the tinder was found in the fireplace of her
bedroom. Another most curious feature is that her mother
Mrs. Shaylor received on the following day a telegram
handed in at Waterloo Station, with the words, ‘Am all
right, do not worry. Back soon—Thelma.’
“Mrs. Audley and her mother are well-known in Bexhill,
where they have lived for two years. The young lady
married early in the New Year, but her husband being
called abroad, she has remained at home during the
summer. Any information concerning the missing lady will
be gladly received by her mother, and can be given to any
police station. Her description which was circulated
yesterday is as follows:—”
Then followed a very minute description of Thelma, and of the
clothes she wore when she left Bexhill.
Thelma had disappeared! Did that mysterious message emanate
from her husband? Had she gone to join him in hiding?
Why had she been so careful to destroy that message which called
her to London? If it were from Stanley, as I felt certain it was, then
what more natural than that she would have told her mother and
explained that she was rejoining him?
She was elated at receipt of the message! Why?
“This is even more amazing than the past events,” I declared to
Hensman when, at last I found my tongue. “What do you think of it?”
“I don’t know what to think of it, old chap,” was my partner’s reply,
“except that it makes the whole affair more mysterious than ever. It is
quite clear she has disappeared of her own free will. Possibly she
has some motive, as her husband undoubtedly had, for effacing
himself, and I should think it quite possible she has gone to join him,
wherever he is.”
I put in a telephone call to Mrs. Shaylor at once. Her strained voice
clearly betrayed acute distress and anxiety. When I told her I had
read the account of Thelma’s disappearance, she said:
“Oh! Mr. Yelverton, I am so terribly distressed. What do you think of it
all? I suppose you know nothing of my girl’s whereabouts.”
“Absolutely nothing,” I said despairingly. “I wrote to her some days
ago, but had no reply.”
“Your letter is here. It came on the night she left. I recognized your
handwriting. I believe she is in London, and that she sent me that
reassuring telegram from Waterloo, but the police do not believe it.
They doubt that she ever went to London.”
“Who says so? The local police?”
“One of the two detectives who came down from London yesterday
to see me.”
“But that telegram which she burned,” I asked. “Who was the sender.
Have you any suspicion?”
“I feel quite certain that it was from Stanley.”
“Then if she is with her husband, why should we worry?” I asked.
“Because—well, because I have a strange intuition that there is
something seriously wrong. Why, I can’t tell—a mother’s intuition is
usually right, Mr. Yelverton.”
“Is that really all you know?” I asked eagerly. “Cannot I be of any
service in assisting to trace her?”
“Well, the police are evidently doing their best,” was her reply. “There
is one queer circumstance about the affair, namely that on the day
before she received the telegram, a stranger called to see her. We
had just had dinner when he was announced. He was a tall, thin,
fair-haired young man, and he asked to see Thelma. She saw him in
the morning-room, and she was alone with him for about ten minutes
or so. After he left she seemed to be wonderfully elated. She would
tell me nothing, only that some good news had been imparted to her
by the stranger. I asked her why she did not confide in me, but she
replied that it was her own affair, and that at the moment she was not
allowed to divulge it. Later on she would tell me all. Then next day
she received the telegram which she had apparently been expecting,
and left.
“Oh! Mr. Yelverton! The mystery of it all is driving me to distraction,”
the poor lady went on. “If you can do anything to help me I shall
thank you forever.”
“Listen, Mrs. Shaylor,” I said over the wire. “Will you kindly repeat the
description of that stranger who called to see Thelma on the day
previous. It is important—very important!”
She gave a detailed description of the fair-haired young man and the
clothes he wore.
“Did she appear to know him?”
“Oh, yes! It was evident that they had met before,” came the voice
over the telephone. “He greeted her merrily, and asked to be allowed
to speak with her in private. Later, I heard Thelma’s voice raised in
exultant laughter.”
“Have you never seen the young man before?” I asked.
“Never. He was a total stranger to me. But Thelma knew him without
a doubt. If you can help me to re-discover her it is all I can ask of
you, Mr. Yelverton. You can imagine my distress. Why she does not
let me hear from her I cannot think.”
“Perhaps Stanley—who is evidently in hiding, forbids it,” I said in an
effort to relieve her anxiety, though the fact of her disappearance in
itself showed some sinister influence at work.
“Perhaps so, Mr. Yelverton. Yet if that is the case it is surely very
unfair to me!”
“Time’s up,” chipped in the voice of the operator at the exchange.
“Sorry! Time’s up!”
And the next instant we were cut off.
Hensman had been standing beside me as I had been speaking.
“Well, what shall you do now?” he asked. “You’ve apparently placed
yourself in a fine fix, Rex. First you narrowly lose your life, and now
the lady is missing. Is it yet another plot?”
“Undoubtedly,” I replied, reflectively. “I must have time to consider
what steps to take.”
“If I were you I wouldn’t mix myself up in the affair any further. Take
my advice, old man. You haven’t been the same for months. It has
got on your nerves,” he declared, as he filled his pipe.
“I know it has, my dear fellow, but when I decide to do a thing, I do it.
I mean to solve this enigma.”
“Well, you haven’t been very successful up to the present, have
you?” he remarked, a trifle sarcastically, I thought.
“No. But I will not give up,” I said firmly. “This second mystery of
Thelma’s disappearance makes me more than ever determined to
continue my search.”
“Then forgive me for saying so, Rex—it is perhaps unpardonable of
me to intrude in your private affairs—but I think you are acting very
foolishly. If the young lady has disappeared, then, no doubt, she has
done so with some distinct motive.”
“In that case she would have confided in her mother,” I argued.
“Over the telephone you spoke of some stranger who had visited
her.”
“Yes. It is that fact which urges me on to prosecute my inquiries,” I
replied. “The young man evidently bore some message, but from
whom?”
Hensman’s advice was, of course, sound enough, but he was not in
love as I was. He saw things through quite a different pair of
spectacles.
An hour later I took a taxi to Castlenau to seek old Doctor Feng, my
object being to ascertain whether he had any knowledge of what had
occurred.
In answer to my ring the doctor’s housekeeper appeared. She was a
sour-faced old woman in a rather soiled apron, whom I had seen
before.
“The doctor ’aint in, sir,” she replied, in true Cockney intonation. “I
don’t know where ’e is.”
“What time did he go out?” I asked.
“Oh! ’e went out on Tuesday morning, and ’e ’aint been back since.
But ’e often goes away sudden like.”
“Does he?” I asked.
“Yes, sir.”
“Ah!” I laughed. “I see you don’t like him.” I hoped to get more out of
her.
“I do. The doctor’s real good sort, sir. ’E’s been awfully good to me
and my girl, Emily. I don’t know what we should ’ave done this winter
if we ’adn’t ’ad this place. ’E’s a bit lonely, is the doctor. But ’e’s been
a real good gentleman to me.”
“Do you happen to know a friend of his, a Mr. Harold Ruthen,” I
asked suddenly.
“Of course I know ’im, sir. ’E’s often ’ere. ’E’s brought a lady once or
twice—a pretty young married lady. I don’t know ’er surname, but the
doctor calls ’er Thelma.”
Thelma! I held my breath. In face of what I had learned this was
staggering.
“I know the lady,” I said, with an inward struggle to remain unexcited.
And I went on to describe her and her dress.
“That’s the lady, sir.”
“When was she last here?”
“Oh! Well, it was about three days ago, sir. She came with another
young gentleman whom I’d never seen before—she called ’im
Stanley.”
Stanley! Could Stanley Audley have been there?
“Yes,” I said excitedly as I stood within the hall, “and what else? I
have reason in asking this. A great deal depends upon what you can
tell me.”
“I ’ope I’m not telling anything wrong, sir,” replied the woman. “Only
you’ve asked me, and I’ve told you the truth.”
“Thanks very much,” I replied. “This is all most interesting. Describe
what this friend of the young lady’s was like.”
She reflected a moment, and then, telling me that he wore a dark
blue suit and was a “thorough gentleman”—presumably because he
had given her a tip before his departure—she described a young
man which was most certainly the missing man, Stanley Audley.
I questioned her, and she became quite frank—after I had placed a
couple of half-crowns into her hand—concerning the visit of Thelma
and Stanley.
“They came ’ere early in the afternoon,” she said. “They’d a long talk
with the doctor—a very serious talk, for when I passed the door they
were only a talkin’ in whispers. I don’t like people what whisper, sir. If
they can’t talk out loud there is somethin’ wrong—that’s what I
always says.”
I agreed. Further, I gathered from her that the conference between
Thelma, Stanley and old Feng had been most confidential.
“The young man left ’arf an ’our before the young lady,” she told me.
“’E seemed very nervous, I thought. It was dark when ’e went, and
as he said good-bye to the doctor, I ’eard ’im say, ‘Remember, I’m
dead—as before!’ I wonder what ’e meant? I’ve been thinking over it
lots of times. But, of course, sir, wot I’ve told you is all secret. I ought
not to ’ave told you anything. I’ve got a good job, and I don’t want to
lose it, as things are ’ard in these days, I tell you straight. So you
won’t repeat to the doctor what we’ve been talkin’ about, will yer?”
“No!” I said. “Certainly not.”
CHAPTER XIX
AT HEATHERMOOR GARDENS

That night the newspapers contained a paragraph repeating what


had appeared in the morning concerning Mrs. Audley’s
disappearance, and stating that no trace of her had been discovered
after she had left Bexhill.
Her secret visit to old Feng, accompanied by Stanley, three days
before, added to the mystery. Feng knew of my search for Audley.
Then, why had he not told me the truth? With what motive was I
being misled and befooled by a conspiracy of silence?
I began to realize that that motive, whatever it was, must be far
stronger than I had previously suspected. And in my heart, I confess,
I was dismayed by the knowledge that Stanley Audley was still alive:
it showed that the goal upon which I had set my heart would never
be reached. My distress and dismay as I sat late into the night in my
silent bachelor room, may well be imagined.
Had Thelma purposely gone into hiding with her husband, and with
the connivance of Feng—or had she since met with foul play? Her
failure to take her mother into her confidence seemed to me to
suggest the latter.
I was strongly tempted to go to Scotland Yard and tell the police all I
knew about the missing girl. But after long consideration I decided
that I could do little, if any, good. The police were pursuing their own
methods and what I could tell them would not help matters much. In
addition I am afraid I did not want the police to get hold of Stanley
Audley. If, as I strongly suspected, he was engaged in the nefarious
trafficking in forged bank-notes, anything I did could only bring fresh
distress upon Thelma. And I could not force myself to believe that
her husband would be sufficiently callous and cold-blooded to allow
any serious harm to befall her. In the long run it proved I was right.
The issue was in other hands than those of Scotland Yard.
I was trying to fix my mind upon my work at the office next day, when
my telephone rang and I heard the cheery voice of old Mr.
Humphreys.
“Look here, Yelverton, I’ve been meaning to ring you up for some
days past. Can you come and dine with me tonight? I’m in my place
at Hampstead at last—moved up here a week ago. Will you take the
address—14, Heathermoor Gardens—up at the top end of Fitzjohn’s
Avenue.”
I scribbled the address on my blotting-pad.
“You’ll easily find it,” he went on. “Come at eight, won’t you? The
best way is to go to Hampstead Heath tube, and walk. It’s only two
minutes.”
I gratefully accepted, for I wanted to discuss with him Thelma’s
mysterious disappearance.
“Have you seen Doctor Feng lately?” I asked him, before he rang off.
“No; I think he must be in Paris. He told me he was going over,” was
the reply.
About a quarter to eight that night I emerged from the lift at
Hampstead station, and having inquired for Heathermoor Gardens,
walked through the rain to a highly respectable road of large
detached houses, each wherein dwelt prosperous city men,
merchants, barristers and the like. The night was dark, and even
though the street lamps shone, it was with some difficulty that I found
Number Fourteen.
The house proved to be a large corner one, of two stories and
double-fronted. Certainly it was the largest and best of them all and
had big bay windows, and possessed an air of prosperity akin to that
of my friend, the Anglo-Turkish financier.
The door was opened by a round-faced clean-shaven young man-
servant who asked me into the spacious lounge-hall in which a wood
fire burned brightly, and after taking my hat and coat, ushered me
into a small cozy library on the left, where old Mr. Humphreys rose
from the fireside, greeting me merrily.
“I’m awfully glad you could come, Yelverton,” was his greeting, “I
haven’t asked anybody to meet you, for I thought we’d just have a
quiet hour together, so that I can show you round my new home, and
we can have a gossip. Sit down. Dinner will be ready in a moment.”
Then he pressed the bell and a moment later the man appeared
bearing a tray with two cocktails. We raised our glasses and drank.
Mine was delicious. I gazed around the sumptuously furnished room
and congratulated him upon it.
“Yes,” he said. “I’ve tried to make it as cozy as I can. I thought I
would bring my furniture from Constantinople, but on second
thought, decided it was too oriental and heavy and would hardly
have been in keeping with an English house. So I sold it and have
bought this place and furnished it.”
“It is really charming,” I said, noting the taste displayed.
“Yes, I didn’t want it to appear too new, so some of the stuff is
second-hand. I hate a place which looks like the palace of a war-
profiteer, don’t you?” he laughed.
The room was just my ideal of a man’s den, lined as it was with
books with a soft-lined Turkish carpet, a big carved writing table and
several deep saddle-bag chairs. The atmosphere was heavy with the
scent of his exquisite Turkish tobacco—that my host smuggled—the
only way to get the first grade of tobacco-leaf.
He referred to it as he handed me a thin cigarette.
“In these days of Turkey’s trials—thanks to her German betrayers,
one no longer gets a little of the tobacco reserved for the Yildiz as it
used to be. The Sultan grew his own tobacco in Anatolia—the most
delicious of all tobacco and the second grade was sold to Europe as
the finest. But the best was always kept for the Yildiz and for His
Majesty’s ministers and his harem. I fear the few cigarettes I have
left are the last of the Imperial tobacco.”

You might also like