Zalando Tech Blog – Technical Articles Michal Raczkowski

Decoupled styling in UI components

Styling isolation

Styling isolation achieved via CSS-modules, various CSS-in-JS solutions or Shadow-DOM simulation is already a commonly used and embraced pattern. This important step in CSS evolution was really necessary for UI components to be used with more confidence. No more global scope causing name conflicts and CSS leaking in and out! The entire component across HTML/JS/CSS is encapsulated.

Styling API - exploration

I expect CSS technology to offer much more in the future. The encapsulation usually comes hand in hand with the interface, for accessing what was hidden in an organised way. There are different ways to provide styling-APIs, for customising the component CSS from the outside.

One of the simplest methods is to support modifiers; flags for the component, used to change appearance, behavior or state:

<MyComponent type="large" />

This is convenient if there are a few predefined modifiers. But what if the number of different use cases grows? The number of modifiers could easily go off the scale if we combined many factors, especially for non-enum values like "width" or "height".

Instead we could expose separate properties that provide a certain level of flexibility:

<MyComponent color="red" border="2" borderColor="black" />

In such cases different modifiers can simply be constructed by users of the component. But what if the number of CSS properties is large? This solution also doesn't scale nicely. Another con is that any modification of the component's styles will likely force us to change the API as well.

Another solution is to expose the class that will be attached to the root element (let’s assume it's not a global class and proper CSS isolation technique is in place):

<MyComponent className="my-component-position" />

Attaching a class from the outside will effectively overwrite the root element CSS. This is very convenient for positioning the component, with such CSS properties as: "position," "top," "left," "z-index," "width," and "flex.” Positioning of the component is rarely the responsibility of the component itself. In most cases it is expected to be provided from outside. This solution is very convenient and more flexible than former proposals. But it’s limited to setting the CSS only for the component's root element.

The combination of the above solutions would likely allow us to address many use cases, but is not perfect, especially for component libraries, where simple, generic and consistent API is very important.

Decoupled styling

I'd like to take a step back and rethink the whole idea of styling-API for components. The native HTML elements come with minimal CSS, enough to make the elements usable. The users are expected to style them themselves. We are not talking about "customisation", as there is basically no inherent styling in place to "customise". Users inject styling, via a “class” attribute or “className” property:

<button class="fashion-store-button" />

In latest browsers like Chrome, we can also set the styling for more complex HTML5 elements like video elements:

<video class="fashion-store-video" />

.fashion-store-video::-webkit-media-controls-panel {
 background-color: white;
}

Thanks to Shadow DOM and webkit-pseudo-elements users can set the styles not only for the root element, but also for important inner parts of the video component. However webkit pseudo-elements are poorly documented and seem to be unstable. It’s even worse for custom elements, because currently it’s not possible to customise the inner parts of elements (::shadow and /deep/ have been deprecated). However, there are other proposals that will likely fill the gap:

Let's summarise the native approach, which I call "decoupled styling":

  1. A component is responsible only for its functionality (and API) and comes with minimal or no styling
  2. A component styling is expected to be injected from outside
  3. There is styling-API in place to style the inner parts


Benefits

The nature of styling is change, the nature of functionality (and API) is stability. It makes perfect sense to decouple both. Decoupled styling actually solves many issues that UI-component library developers and users are facing:

  • styling couples components together
  • same changelog for styling and functionality/API causes upgrading issues (e.g. forced migrations)
  • limited resilience - changes in styling propagate to all parts of the frontend project
  • costs of rewriting components to implement a new design
  • costs of rewriting/abandoning projects, because of outdated components
  • limitations of styling-API to address different use cases
  • bottleneck of component library constantly adjusted for different use cases


API proposal

In the world of custom UI components, many components are constructed from other components. Contrary to native HTML/CSS implementation with injecting a single class name, here we need API for accessing the nested components. Let’s look at the following proposal for the API.

Imagine a “Dialog” component that contains two instances of a “Button” component (“OK” and “Cancel” buttons). The Dialog component wants to set the styling for OK button but leave the styling for the Cancel button unchanged (default):

<Button classes={icon: "button-icon", text: "button-text"}>OK</Button>
<Button>Cancel</Button>

We used “classes” property to inject the CSS classes for two of Button’s internal elements; the icon and the text elements. All properties are optional. It’s up to component itself to define its styling-API (set of class names referencing their child elements).

To use Dialog with its default, minimal styling:

<Dialog />

But for cases where we want to adjust the styles, we will inject it:

<Dialog classes={root: "dialog-position"} />

We injected a class that will be attached to the root element. But we can do much more:

<Dialog classes={
 root: "dialog-position",
 okButton: {
   icon: "dialog-ok-button-icon",
   text: "dialog-ok-button-text"
 }
} />

The example above shows how we can access every level of nested components structure in the Dialog. We’ve set the CSS classes for the root element and OK button. By doing that we will effectively overwrite the styling for the OK button, that is preset inside Dialog.

In the same way we will be able to set the styling for components that contain Dialogs, and farther up, to the highest level of the application. On the root level of the application, defining the styles will practically mean defining the application theme.


Implementation

I implemented two examples using React and TypeScript, first with CSS Modules and second with Emotion (CSS-in-JS library). Both are based on the same concept:

  • default, minimal styling for components is predefined as an isolated set of classes
  • styling-API (set of class names) is defined using TypeScript interface, with all properties optional
  • components allow injection of class names object (via “classes” parameter) which is “deeply-merged” with default class names object, overwriting the styles

React, TypeScript, CSS Modules: https://github.com/mrac/decoupled-styling-css-modules
React, TypeScript, Emotion: https://github.com/mrac/decoupled-styling-css-in-js

Conclusion

Decoupling styling from UI components may be a step towards making them really reusable, drawing from the original idea behind Cascade Style Sheets to separate the presentation layer from UI logic and markup. Defining boundaries between UI logic and markup on one side and styling on the other side would likely change the way UX designers collaborate with engineers.  Here designers would style components based on API provided by engineers. It would be easier now to specify what constitutes a breaking-change within that contract. Putting an ever-changing skin on top of what is stable would likely save costs, friction and contribute to software quality.

Zalando Tech Blog – Technical Articles Kaiser Anwar Shad

Comparing Redux, MobX & setState in React

Introduction

React is a declarative, efficient, and flexible JavaScript library for building user interfaces. Compared to other frontend libraries and frameworks, React’s core concept is simple but powerful: ‘React makes it painless to design simple views and renders by using virtual DOM’. However, I don’t want to go into detail about virtual DOM here. Rather, I want to show three ways how you can manage state in React. This post requires basic understanding about the following state management approaches. If not, check out the docs first.

  1. setState: React itself ships with built-in state management in the form of a component’s `setState` method, which will queue a render operation. For more infos => reactjs.org
  2. MobX:  This is a simple and scalable library applying tested functional reactive programming (TFRP), which stands for: ‘Anything that can be derived from the application state, should be derived. Automatically.’ For more infos => mobx.js.org
  3. Redux: Maybe the most popular state management solution for React. The core concepts are having a single source of truth, immutable state and that state transitions are initiated by dispatching actions and applied with pure functions (reducers). For more infos => redux.js.org


Location

  1. setState is used locally in the component itself. If multiple children need to access a parent’s local state, the data can either be passed from the state down as props or, with less piping, using React 16’s new Context API.
  2. MobX can be located in the component itself (local) or in a store (global). So depending on the use case the best approach can be used.
  3. Redux is providing the state globally. Means the state of the whole application is stored in an object tree within a single store.


Synchronicity

  1. setState is asynchronous.*
  2. MobX is synchronous.
  3. Redux is synchronous.

*Why asynchronous? Because delaying reconciliation in order to batch updates can be beneficial. However, it can also cause problems when, e.g., the new state doesn’t differ from the previous one. It makes it generally harder to debug issues. For more details, check out the pros and cons.


Subscription

  1. setState is implicit, because it directly affects the state of the component. Changing the state of child components can be done via passing props (or Context API in React 16).
  2. MobX is implicit, because it is similar to setState with direct mutation. Also component re-renders are derived via run-time usage of observables. To achieve more explicitness/observability, actions can (and generally should) be used to change state.
  3. Redux is explicit, because a state represents a snapshot of the whole application state at a point in time. It is easy to inspect as it is a plain old object. State transformations are explicitly labeled/performed with actions.

Mutability*

  1. setState is mutable because the state can be changed by it.
  2. MobX is mutable, because actions can change the state of the component.
  3. Redux is immutable, because state can’t be changed. Changes are made with pure functions which are transforming the state tree.

* With mutability the state can be changed directly, so the new state overrides the previous one. Immutability is protecting the state from changes and (in Redux) instead of directly changing the state it dispatches actions to transform the state tree into a new version.


Data structure

  1. setState -
  2. MobX Graph: multidirectional ways; loops can be used. The state stays denormalized and nested.
  3. Redux Tree: is a special kind of graph, which has only one way: from parent to child. The state is normalized like in a database. The entities only reference to each other by identifiers or keys.

Observing Changes

  1. setState -
  2. MobX: Reactions are not producing new values, instead they produce side effects and can change the state.
  3. Redux: An Object describes what happened (which action was emit).

Conclusion

Before starting to write your application you should think about which problem you want to solve. Do you really need an extra library for state management or is React’s built-in setState fulfilling your needs? Depending on the complexity you should extend it. If you love to go for the mutable way and expect the bindings automatically, then MobX can fit your needs. If you want to have a single source of truth (storing state in an object tree within a single store) and keep states immutable, then Redux can be the more suitable solution.

Hopefully this post gave you a brief overview about the different ways to manage state in React. Before you start with one of those libraries, I recommend to go through the docs of each. There are a lot more treasures to discover!


TL;TR:


This post is inspired by:

Check out our open Software Engineering positions on our jobs page.

Zalando Tech Blog – Technical Articles Holger Schmeisky

Sharing successful large scale agile experiences


Zalando has been known for radical approaches to agility since 2015. In order to keep growing and staying successful we took the next step in 2017 forming around 30 business units. Each business unit is formed around one particular business problem, topic or product with end2end responsibility. All disciplines needed are inside this business unit from commercial roles to tech teams.

Challenges in large scale product groups

Looking at this setup, we experience challenges. You’re probably familiar with this if you work in a similar setup or if your company has around the size of one of our business units (<100 people).

  • Who takes product decisions at this size with several teams and product people?
  • How to keep the focus on the actual product with so many technical components and intermediate steps?
  • How to enable 50 engineers to understand their everyday task contribution to the overall quarterly goals?
  • How to do sprint planning with 20 people?
  • How to handle cross-cutting concerns like 24/7 and platform work in a feature team setup?

By far the biggest question was however: How can this work inside Zalando?


Our Solution Approach

How to support these +30 business units to reach their business goals through agile working? Rome was not built in a day. We knew we had to work by process and collaboration.

We used the power of our network and collected successful solutions from all units. The first and most important observation was that no solution can be mechanically copied, but always has to be adapted to the specific needs of the unit (“There are no best practices, only practices that work in a specific context”). To enable this adaption and learning, in addition to the bare facts we collected:

  1. the story and motivation around the solutions
  2. the details of how they are adopted
  3. the (contact details of the) people who created them

For the first factor, we invited people from these teams for teachback sessions open for everyone to share their experiences in a try/avoid format.

Secondly, from these we created a 20 page guide on how to structure large teams with background details. Finally, we connected people we talked to who have similar challenges to the pioneers, because all advice needs to be adapted to the specific BU needs.

Concrete Examples

For example, the Fashion Store Apps group (5 teams) struggled with their narrow product definition: Each platform and the API were treated as separate products, with seperate teams, backlogs, product owners, etc. These needed to be managed, synchronized, and aligned, and code needed to be integrated. As you can imagine, somewhere along the way the focus on the customer gets hard to find. To address this, the team redefined the product as “Fashion Store Apps,” reorganized the teams to reflect this, and merged all backlogs into one.

Another example is how Personalization (6 teams) increased the understanding of the goals and unlocked possibilities. As is typical in a large organization, goals and concrete products were difficult to define for this department and usually the understanding did not transfuse to the engineering and data science teams. To tackle this, everyone (including engineers) took responsibility for creating or refining the press releases that underlie the epics for the upcoming quarter. Ideas to achieve goals are as likely to come from Product* as they are to come from delivery teams. The concrete outcome is an aligned and commonly understood overview of the next quarter’s sprints. This led to much higher involvement and identification during the quarter, and to more motivated teams.

A LeSS introduction backwards

These are only two examples from many more instances of how we scale agile at Zalando. The whole approach is somehow a LeSS introduction backwards. We make note of what trials work, and we find a high similarity to the LeSS framework without ever using the word or the whole framework. The practices emerged themselves as they made sense to the people inside the organization. As one engineering lead put it after reading a LeSS book, “It’s nice to see that we were not the only ones with these ideas.”

Our key learning directed to all fellow Agile Coaches and Agile Change Agents is to not implement frameworks, but to source from working solutions and share the successes.

Eventually we will end up in a form of LeSS organization without anybody inside Zalando connecting emotionally to the framework itself.

If you would like to learn more, feel free to reach out to agility-coaching@zalando.de or have a look at our open position.

Many thanks for the input and support of our colleagues Samir Hanna, Tobias Leonhard and Frank Ewert.

Zalando Tech Blog – Technical Articles Michal Michalski

Insights on Zalando's event-driven microservice architecture

As discussed in my previous blog post, Kafka is one of the key components of our event-driven microservice architecture in Zalando’s Smart Product Platform. We use it for sequencing events and building an aggregated view of data hierarchies. This post expands on what I previously wrote about the one-to-many data model and introduces more complex many-to-many relationships.

To recap: to ensure the ordering of all the related entities in our hierarchical data model (e.g. Media for Product and the Product itself) we always use the same partition key for all of them, so they end up sequenced in a single partition. This works well for a one-to-many relationship: Since there’s always a single “parent” for all the entities, we can always “go up” the hierarchy and eventually reach the topmost entity (“root” Product), whose ID we use to derive the correct partition key. For many-to-many relationships, however, it’s not so straightforward.

Let’s consider a simpler data model that only defines two entities: Products (e.g. Shoes, t-shirt) and Attributes (e.g. color, sole type, neck type, washing instructions, etc., with some extra information like translations). Products are the “core” entities we want to publish to external, downstream consumers and Attributes are meta-data used to describe them. Products can have multiple Attributes assigned to them by ID, and single Attributes may be shared by many Products. There’s no link to a Product in Attribute.

Given the event stream containing Product and Attribute events, the goal is to create an “aggregation” application, that consumes both event types: “resolves” the Attribute IDs in Product entities into full Attribute information required by the clients and sends these aggregated entities further down the stream. This assumes that Attributes are only available in the event stream, and calling the Attribute service API to expand IDs to full entities is not feasible for some reason (access control, performance, scalability, etc.).

Because Attributes are “meta data”, they don’t form a hierarchy with the Product entity; they don’t “belong” to them, they’re merely “associated” with them. It means that it’s impossible to define their “parent” or “root” entity and, therefore, there’s also no single partition key they could use to be “co-located” with the corresponding Products in a single partition. They must be in many (potentially: all) of them.

This is where Kafka API comes in handy! While Kafka is probably best known from its key-based partitioning capabilities (see: ProducerRecord(String topic, K key, V value) in Kafka’s Java API), it’s also possible to publish messages directly to the specific partition using the alternative, probably a less known ProducerRecord(String topic, Integer partition, K key, V value). This, on its own, allows us to broadcast an Attribute event to all the partitions in a given topic, but if we don’t want to hardcode the number of partitions in a topic, we need one more thing: producer’s ability to provide the list of partitions for a given topic using the partitionsFor method.

The complete Scala code snippet for broadcasting events could now look like this:

import scala.collection.JavaConverters._
Future.traverse(producer.partitionsFor(topic).asScala) { pInfo =>
 val record = new ProducerRecord[String, String](topic, pInfo.partition, partitionKey, event)

// send the record
}

I intentionally didn’t include the code to send the record, because the Kafka’s Java client returns Java Future, so converting this response to Scala Future would require some extra code (i.e. using Promise), which could clutter this example. If you’re curious on how this could be done without the awful, blocking  Future { javaFuture.get } or similar (please, don’t do this!), you can have a look at the code here.

This way we made the same Attribute available in all the partitions, for all the “aggregating” Kafka consumers in our application. Of course it carries consequences and there’s a bit more work required to complete our goal.

Because the relationship information is stored in Product only, we need to persist all the received Attributes somewhere, so when a new Product arrives, we can immediately expand the Attributes it uses (let’s call it “Attribute Local View”, to emphasise it’s a local copy of Attribute data, not a source of truth). Here is the tricky part: Because we’re now using multiple, parallel streams of Attribute data (partitions), we need an Attribute Local View per partition! The problem we’re trying to avoid here, which would occur in case of a single Attribute Local View, is overwriting the newer Attribute data coming from “fast” partition X, by older data coming from a “slow” partition Y. By storing Attributes per partition, each Kafka partition’s consumer will have access to its own, correct version of Attribute at any given time.

While storing Attributes per partition might be as simple as adding Kafka partition ID to the primary key in the table, it may cause two potential problems. First of all, storing multiple copies of the same data means – obviously – that the storage space requirements for the system are significantly raised. While this might not be a problem (in our case Attributes are really tiny comparing to the “core” entities), this is definitely something that has to be taken into account during capacity planning. In general, this technique is primarily useful for problems, where the broadcasted data set is small.

Secondly, by associating the specific versions of Attributes with partition IDs, the already difficult task of increasing numbers of partitions becomes even more challenging, as Kafka’s internal topic structure has now “leaked” to the database. However, we think that growing the number of partitions is already a big pain (breaking the ordering guarantees at the point where partitions were added!) that requires careful preparations and additional work (e.g. migrating to the new topic with more partitions, rather than adding partitions “in place” to the existing one), so it’s a tradeoff we accepted. Also, to reduce the risk of extra work we try to carefully estimate the number of partitions required for our topics and tend to overprovision a bit.

If what I just described sounds familiar to you, you might have been using this technique without even knowing what it is; it’s called broadcast join. It belongs to a wider category of so called map-side joins, and you can find different implementations of it in libraries like Spark or Kafka Streams. However, what makes this implementation significantly different is the fact that it reacts to the data changes in real-time. Events are broadcast as they arrive, and local views are updated accordingly. The updates to aggregations on product changes are instant as well.

Also, while this post assumes that only Product update may trigger entity aggregation, the real implementation we’re using is doing it on Attribute updates as well. While, in principle, it’s not a difficult thing to do (a mapping of Attribute-to-Product has to be maintained, as well as the local view of the last seen version of a Product), it requires significantly more storage space and carries some very interesting performance implications as single Attribute update may trigger an update for millions of Products. For that reason I decided to keep this topic out of the scope of this post.

As you just saw, you can handle many-to-many relationships in a event-driven architecture in a clean way using Kafka. You’ll benefit from not risking having outdated information and not resorting to direct service calls, which might be undesirable or even impossible in many cases. As usual, it comes at a price, but if you weigh pros and cons carefully upfront, you might be able to make a well-educated decision to your benefit.

Like Michal's work and want to be part of the action at our Fashion Insights Center in Dublin? Keep up to date with our Dublin jobs.

Zalando Tech Blog – Technical Articles Oleksandr Volynets

How we migrated the Zalando Logistics Operating Services to Java 8

“Never touch working code!” goes the old saying. How often do you disregard this message and touch a big monolithic system? This article tells you why you should ignore common wisdom and, in fact, do it even more often.


Preface

Various kinds of migration are a natural part of software development. Do you remember the case when the current database didn’t scale enough? Or maybe there is need for a new tech stack when the existing stack does not meet changing requirements? Or perhaps the migration from the monolithic application to the microservice architecture is hard. There could also be smaller-scale migrations like upgrading to a newer version of the dependency, e.g. Spring, or Java Runtime Environment (JRE). This is the story on how a relatively simple task of migration from Java 7 to Java 8 was performed on a large-scale monolithic application that has ultimate criticality to the business.

Zalos as the service for Logistics Operations

Zalos (Zalando Logistics System) is a set of Java services, backend and frontend, that contains submodules to operate most functions inside the warehouses operated by Zalando. The scale of Zalos can be summarized by the following statistics:

  • more than 80,000 git commits,
  • more than 70 active developers in 2017,
  • almost 500 maven submodules,
  • around 13,000 Java classes with 1.3m lines of code, plus numerous production and test resource files,
  • operates with around 600 PostgreSQL tables and more than 3,000 stored procedures.

Zalos 2, denoted as just Zalos below, is the second generation of the system, and has grown to this size over the past five years. Patterns that were, at the time, easy to adopt for scaling up architectural functionality, have quickly become a bottleneck with the growing number of teams maintaining it. It is deployed to all Zalando warehouses every second week, and every week there is a special procedure to create a new release branch. Each deployment takes about five hours, branching takes about the same time. When also considering urgent patches, it takes a significant portion of each team’s time to do regular deployment or maintenance operations.

Now, what happens if the system is left unmaintained for a while? The package dependencies and Java libraries become obsolete and, as a consequence, security instability grows. Then, one day one of the core infrastructure systems has to change the SSL certificate, and this causes some downtime in all relevant legacy systems operating a deprecated Java version. For the logistics services these problems might become a big disaster, and you start thinking: “What does it take to migrate Zalos from Java 7 to Java 8?”


Migration? Easy!

With some basic experience with Java 9, the option to go even further has been rejected pretty fast: a combination of Java-9 modularity and 500 sub-modules doesn’t look very positive. Well, bad luck. What else do you need to keep in mind for Java 8 support? Spring? Sure. GWT? Maybe. Guava? Oh yes. Generics? This too.

This is a good time to talk about the tech stack for Zalos. It contains backend as well as frontend parts, both running Spring 3. The backend uses PostgreSQL databases via the awesome sprocwrapper library. Both backend and frontend rely on Zalando-internal parent packages to take care of dependency management. The frontend engine is GWT 2.4 with some SmartGWT widgets. And, to mention a few more challenges, it uses Maven overlays with JavaScript but more on this later.

Our first strategy was to bump as many package dependencies as we can. Spring 4 which fully supports Java 8, GWT 2.8.2 that already has support for Java 9, Guava 23.0, etc. We use GWT 2.4; a jump of over five years development-wise. Hard dependency on our internal Zalando dependencies had ruled out the major Spring upgrade too. Guava 23 has deprecated some methods and we would need to change quite an amount of code: again, a failure.

Let’s try an another strategy then: bump as little as we can. This strategy worked much better. We only needed to have Spring 3.2.13 and Guava 20.0, plus required upgrades like javassist and org.reflections. The matrix of compatible versions is shown in the appendix. GWT dependency was left untouched, although it limits our client code to Java 7. A compromise but not a blocker: there is little active development of new GWT code anyway.

Now, overlays, or in our case Dependency Hell, is a feature of Maven to include dependencies from a WAR or a ZIP file and it “inlines” the complete package as is. And it does so with all its dependencies. As an example, this means, should an overlay have a different version of spring-core, you get two versions of spring-core in the final WAR artifact. When the application starts, it will get confused which version to use for which parts of the application, and various ClassNotFound exceptions will pop up. Bad luck, republishing all war-overlays with updated dependencies is required.


Go-live or don’t rush?

It took just two weeks of highly-motivated and self-driven work for two people to crack the problem and run the 500-module monolith on the laptop with Java 8. It took two more weeks to deploy it to the staging environment after fixing multiple issues. After that, it took two more months to finally deploy it to the production environment. Why so long? Because we deal with the utmost critical system that has several serious constraints, and here they are:

  1. Deployments. Deployment to production lasts up to five hours and it should not interfere with any other deployment, due to internal limitations of the deployment system. With absolute priority for production deployment there isn’t much time for experimenting with the migration. Solution? Tweaking the deployment service helped reduce deployment time by about one third to have some freedom for experimenting on a staging environment.
  2. Development. There are still about 25 commits per day in the main branch. Breaking it would have a significant impact on feature development, and it isn’t easy to experiment with JDK versions from the feature branch. This isn’t good, but still there is a more serious constraint.
  3. Warehouse operations. They are the backbone of an e-commerce company and should not be interrupted by the migration. The risk of any bug should be carefully minimized to maintain the service liveness.

To solve at least two constraints, we created a concrete three-step plan on how we execute the migration in a safe manner and be able to roll back at any time:

  1. Upgrades of all packages compatible with both Java 7 and 8 without changing runtime version. This ensured that there are no changes for deployment
  2. Switch to Java 8 runtime (JRE) keeping source code in Java 7 mode. This step ensured that we can safely change the deployment settings without touching the code and dependencies.
  3. Switch to Java 8 development mode to fully support Java 8 features. No major deployment changes were done with this step.

In addition, except for a staging environment, every step was carefully tested on a so-called beta environment which operates on production data.


Outlook

The migration was completed despite some failed attempts a few years ago. Several things have happened. The service has become a little more stable and secure. The code can now be written with lambdas, method references, etc. Deployment service has been improved too. But most importantly, the legacy system got attention. Even though we had one camp of people who said, “We tried that before, why do you want to try again?” there was also the second camp with, “You are crazy but yeah, do it”. No matter what was tried before and in what manner, it is never too late to try again.

Keep your legacy code under careful supervision: add code quality metrics, minimize maintenance efforts, optimize release cycles. With this you will stop having “Legacy Nightmares” but rather have a maintained piece of code.

Appendix

Here is a list Maven dependencies and related changes that finally made it working together:


In addition, the following compilation and runtime settings were required:

  • <source> and <target> properties for maven-compiler-plugin set to 1.8
  • tomcat 7, i.e. run services with “mvn tomcat7:run-war” and not “mvn tomcat:run-war” which uses tomcat 6 by default.

Come work with us! Have a look at our jobs page.

Zalando Tech Blog – Technical Articles Rohit Sharma

Using Akka cluster-sharding and Akka HTTP on Kubernetes

This article captures the implementation of an application serving data over HTTP which is stored in cluster-sharded actors and deployed on Kubernetes.

Use case: An application, serving data over HTTP and with a high request rate, and the latency of order of 10ms with limited database IOPS available.

My initial idea was to cache it in memory, which worked pretty well for some time. But this meant larger instances due to duplication of cached data in the instances behind the load balancer. As an alternative I wanted to use Kubernetes for this problem and do a proof of concept (PoC) of a distributed cache with Akka cluster-sharding and Akka-HTTP on Kubernetes.

This article is by no means a complete tutorial to Akka cluster sharding or Kubernetes. It outlines knowledge I gained while doing this PoC. The code for this PoC can be found here.

Let’s dig into the details of this implementation.

To form an Akka Cluster, there needs to a pre-defined ordered set of contact points often called seed nodes. Each Akka node will try to register itself with the first node from the list of seed nodes. Once, all the seed nodes have joined the cluster, any new node can join the cluster programmatically.

The ordered part is important here, because if the first seed node changes frequently then the chances of split-brain increases. More info about Akka Clustering can be found here.

So, the challenge here with Kubernetes was the ordered set of predefined nodes, and here comes StatefulSet(s) and Headless Services to the rescue.

StatefulSet guarantees stable and ordered pod creation, which satisfies the requirement of our seed nodes, and Headless Service is responsible for their deterministic discovery in the network. So, the first node will be “<application>-0” and the second will be “<application>-1” and so on.

  • <application> is replaced by the actual name of the application

The DNS for the seed nodes will be of the form:

<application-name>-<ordinal>.<service-name>.<namespace>.svc.cluster.local

Steps:

  1. Start with creating the Kubernetes resources. First, the Headless Service, which is responsible for deterministic discovery of seed nodes(Pods), can be created using the following manifest:
kind: Service
apiVersion: v1
metadata:
name: distributed-cache
labels:
  app: distributed-cache
spec:
clusterIP: None
selector:
  app: distributed-cache
ports:
  - port: 2551
    targetPort: 2551
    protocol: TCP

Note, that the “clusterIP” is set to “None.” Which indicates it’s a Headless Service.

2. Create a StatefulSet, which is a manifest for ordered pod creation:

apiVersion: "apps/v1beta2"
kind: StatefulSet
metadata:
name: distributed-cache
spec:
selector:
  matchLabels:
    app: distributed-cache
serviceName: distributed-cache
replicas: 3
template:
  metadata:
    labels:
      app: distributed-cache
  spec:
    containers:
     - name: distributed-cache
       image: "localhost:5000/distributed-cache-on-k8s-poc:1.0"
       env:
         - name: AKKA_ACTOR_SYSTEM_NAME
           value: "distributed-cache-system"
         - name: AKKA_REMOTING_BIND_PORT
           value: "2551"
         - name: POD_NAME
           valueFrom:
             fieldRef:
               fieldPath: metadata.name
         - name: AKKA_REMOTING_BIND_DOMAIN
           value: "distributed-cache.default.svc.cluster.local"
         - name: AKKA_SEED_NODES
           value: "distributed-cache-0.distributed-cache.default.svc.cluster.local:2551,distributed-cache-1.distributed-cache.default.svc.cluster.local:2551,distributed-cache-2.distributed-cache.default.svc.cluster.local:2551"
       ports:
        - containerPort: 2551
       readinessProbe:
        httpGet:
          port: 9000
          path: /health

3. Create a service, which will be responsible for redirecting outside internet traffic to pods:

apiVersion: v1
kind: Service
metadata:
labels:
  app: distributed-cache
name: distributed-cache-service
spec:
selector:
  app: distributed-cache
type: ClusterIP
ports:
  - port: 80
    protocol: TCP
    # this needs to match your container port
    targetPort: 9000

4. Create anIngress, which is responsible for defining a set of rules to route traffic from outside internet to services.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: distributed-cache-ingress
spec:
rules:
  # DNS name your application should be exposed on
  - host: "distributed-cache.com"
    http:
      paths:
        - backend:
            serviceName: distributed-cache-service
            servicePort: 80

And the distributed cache is ready to use:

Summary
This article covers Akka Cluster-sharding on Kubernetes with the pre-requirements of an ordered set of Seed Nodes and their deterministic discovery in the network, and how it can be solved with StatefulSet(s) and Headless Service(s).

This approach of caching data in a distributed fashion offered the following advantages:

  • Less database lookup, saving database IOPS
  • Efficient usage of resources; fewer instances as a result of no duplication of data
  • Lower latencies to serve data

This PoC opens up new doors to think about how we cache data in-memory. Give it a try (all steps to run it locally are mentioned in the Readme).

Interested in working at Zalando Tech? Our job openings are here.

Zalando Tech Blog – Technical Articles Hugh Durkin

How data science is becoming available ‘for the good of all’ businesses

In his 2010 Ted Talk “When Ideas Have Sex,” Matt Ridley posits that human prosperity was caused by one thing and one thing only; our unique human ability to specialise and exchange ideas and tools.

Ridley’s example of the invention of the reading light illustrates how far we’ve come. Thousands of years ago, making an hour of reading light required hunting an animal and killing it, before rendering it down to make a candle. Today, the average human earns an hour of reading light in less than half a second. The reclaimed time is spent relaxing, traveling, and working day to day in specialized industries for the benefit of other humans. Specialization and exchange creates new technologies faster, and at an ever decreasing cost.


The Democratization of Data Science

‘Data science as a service’ is the latest way for humans to specialize and exchange data science ideas and tools, and is fast accelerating a new wave of computing innovations at an ever decreasing cost. At Zalando, Europe’s leading online fashion platform, we’ve been ‘all in’ on data science almost since the start of our journey to ‘reimagine fashion for the good of all’; delivering customized experiences, quality search results, and contextually relevant recommendations through AI and Machine Learning. Today, we’re betting on ‘data science as a service’ as a new way to democratize the previously specialized power of data science to teams across Zalando.


Understanding Why Data Science Is Different

To democratize technologies, you must understand how this ‘new’ innovation is similar and different from legacy innovations that people already use; in this case, traditional ‘as a service’ API platforms.

First, most platform APIs typically enable users to do one of two things: perform CRUD-like operations to create, read, update, and delete information from a central source of truth (the platform), or ask questions of pre-defined and indexed datasets within a platform (most people call this ‘analytics’).


In the examples above, what “this” is could refer to unstructured text, images, video, or audio. “Meaningful groups” and “unusual things” could be subjective. Humans can be biased when answering questions like these. Machines don’t (yet) have human biases, so helping them to understand unstructured inputs, and create loosely structured outputs requires a different way for these machines to talk to each other, and to humans.

Second, most platform APIs deliver confident results in a binary way. As an example, querying an API for a set of records created within a date range will deliver back the correct set of records created within that date range, provided the data initially provided is accurate. Similarly, when an API is used to read a single record in a database, the API will confidently retrieve that record and its contents for you.

Again, data science APIs are different. Imagine someone stopping you on the street, showing you the photo above, and asking you, “What do you see in this picture?”  Your answers might begin with “I’m pretty sure I see...” (a boat on the Hudson River), or “I definitely see...” (the World Trade Center). You might also be asked clarifying questions like, “Where do think you see it?” As your answers will be either high, medium, or low confidence answers, ‘data science as a service’ APIs must also have a means to express their level of confidence.

Evolving the API Developer Experience, for Data Science

At Zalando, we’ve formed a deep understanding of why ‘data science as a service’ APIs are different through building our Fashion Content Platform Team. Simply put, our team of data scientists, engineers, designers, and product managers develop fashion-focussed AI models, capabilities and APIs to enable any team in Zalando to integrate self-serve ‘data science as a service’ APIs when building relevant and immersive experiences for customers. Here’s some key lessons we learned along the way.

Make it real


‘Data science as a service’ APIs are different, and for both technical and non-technical users, it’s important to understand why they’re different, by ‘making it real’. For non-technical users, easy to use demo UIs and a ‘Labs’ environment make it easy for any member of any team to understand what our deep learning and NLP models do, and how integrating them can help them deliver unique customer experiences. They also take the mystery out of data science, through familiar inputs, simple language, and visual responses with clear explanations. For technical users, ‘make it real’ happens through easy to use tools to call APIs from within the documentation. Certain fields are pre-filled with images and text to reduce time-to-first API call.

What you see, what you get

For image analysis deep learning APIs, developers must understand quickly how JSON responses – or features built with them – might surface to customers within their applications. We carry interactive and visual cues through the documentation and JSON responses are clear, and in-context too. Where relevant, Taxonomies are visual, using imagery to quickly articulate what ‘A-line’, ‘Cropped’, and ‘Paisley’ might mean. For NLP text analysis APIs like Entity Relations, JSON responses are structured for easy interpretation, and demo UIs are available for users to understand visually how Entities relate to each other.

Set expectations


Many developers ‘fail first time’ when using ‘data science as a service’ APIs, as they feel they’ve incorrectly integrated, or are not getting the results back they require. Like humans, machines are limited to what they know based on what they’ve seen before, and simple, visual explanations within documentation help developers understand what the machine knows now, and what it might be learning soon (the AI product roadmap). Providing examples of inputs that work, and inputs that do not, will help set expectations, and likely help fuel your AI product roadmap with new feature requests. Product limitations are always an opportunity to prioritise feature requests faster.

Explain the seemingly obvious


While terms like ‘confidence score’ and ‘features’ are part of day-to-day conversations amongst data science teams, it’s easy to forget that developers newer to data science may not understand what they mean, or what their JSON output represents. Stating the seemingly obvious not only helps developers adopt and integrate with APIs more quickly, it provides an opportunity for all types of developers to skill up and learn about new technologies, and hopefully will spark some ideas for them, too.

Data science as a service for the good of all

“Data science” in all its various forms has existed for more than 30 years, but the majority of businesses in the world today don’t understand what it is, or don’t understand the benefits data science can deliver for their business. ‘Data science as a service’ will address that knowledge and tools gap, enabling businesses everywhere to understand large datasets, automate manual processes, and deliver relevant customer experiences. We’re way closer to the beginning than the end of this journey at Zalando, and would love to hear from you if you’re as excited about the possibilities as we are.

Work with people like Hugh. Join the team at our Dublin tech hub.

Zalando Tech Blog – Technical Articles Terhi Hanninen

A product manager's insights on customization

Personalization is a common term with digital products. But what does it actually mean, why do we do it, and how does it affect the product manager?

To illustrate, let me tell a personal story. I have gone to the same hairdresser for 10 years. He has seen a big part of my life, with big changes and evolutions. He knows my preferences. I like to have my appointments in the morning. I like to try new styles, but I expect him to come up with the new ideas. Additionally, he knows my hair, so he is able to produce a great end result every time. He remembers all of this about all of his regular customers, and because of that, not only does he do great cuts, he is able to serve everyone in a slightly different, personal way.

So the basis of personalization is good memory plus the ability to use everything you remember about the person.

The story also illustrates why personalization is important. It is almost impossible to make me go anywhere else, because the cost of switching is just too high. The trust that has been built over time in this relationship is so strong, I don’t have any interest in exploring alternative service providers. No matter how technically superior some other hairdresser is, he can’t win without the relationship.


At platforms like Zalando, the challenge of personalization comes from three facts: the scale of stock and customers (300,000 items and 23 million customers), the requirement of automated relationship building, and the tricky balance between being cool or creepy.

How does a product manager’s work change when the product is personalized? If we define product management as the intersection of tech, business and design, we can observe the impact from these three angles.

Design is all about the user experience. If you haven’t been customer obsessed before, now is the time. You need to be able to understand your customers at a very deep level. What are the aspects of the experience that actually add value, if personalized? Is it just the item recommendations, or everything from marketing to delivery? What is the right level of personalization, what makes it feel good and valuable? This might vary a lot depending on the culture and the individual. What are the customer’s goals at different times? Are they exploring or exploiting? How do you recognize those goals and then help customers reach them? Personalization forces you to a new level of customer insight, user research, and design.

From a business point of view, you need to level up your KPI skills. Zalando is divided into independent product teams which are responsible for one functional part of the whole; think of home page, category page, product page, wishlist, search, etc. Personalization must run consistently through all of these. Personalization alone does not make one single sale. So what is the goal and how do you measure the value of personalization? Looking at the short term, such as click-through rate, might lead us to optimize for interest, but it’s not valuable, causing negative effects further along in the customer journey. Optimizing for the current session might also be harmful for long-term relationship building. If the vision is to build a relationship like I have with my hairdresser, then you need to go for the long-term KPIs like the customer lifetime value, and some measures on how deep the relationship is; maybe by how diverse the usage of the shop is, and how widely the user shops across categories. Measuring these kinds of KPIs and attributing them correctly is not trivial.

In tech, we turn to data. First of all, you need the data that is predictive to your problem. You might have to start by building something just to gather data that enables the next steps. You can get to a certain level with rule-based systems, but at some point you enter the world of Machine Learning. Being a Product Manager with a ML product requires understanding of both data science and machine learning. You will be modeling human behaviour with a machine, so you need to be able to facilitate that discussion with the design, business, and tech experts. Data Science lives up to its name; it is science, not your standard software development. Be prepared for model exploration, data gathering, cleaning, labeling, lots of iterations, and discussions on when the performance is good enough. Dig up your statistics and probability skills, because you need to understand how those relate to what the customer will experience.

In summary, personalization offers an exciting challenge for a product manager to stretch their skills in all aspects of the role. But like in every product, the core is still the same: Deep understanding of the customer problem, and an exciting vision for the solution will take you far.

Come work for us in our Helsinki tech hub. Take a look at our jobs here.

Zalando Tech Blog – Technical Articles Terhi Hanninen

A product manager's insights on customization

Personalization is a common term with digital products. But what does it actually mean, why do we do it, and how does it affect the product manager?

To illustrate, let me tell a personal story. I have gone to the same hairdresser for 10 years. He has seen a big part of my life, with big changes and evolutions. He knows my preferences. I like to have my appointments in the morning. I like to try new styles, but I expect him to come up with the new ideas. Additionally, he knows my hair, so he is able to produce a great end result every time. He remembers all of this about all of his regular customers, and because of that, not only does he do great cuts, he is able to serve everyone in a slightly different, personal way.

So the basis of personalization is good memory plus the ability to use everything you remember about the person.

The story also illustrates why personalization is important. It is almost impossible to make me go anywhere else, because the cost of switching is just too high. The trust that has been built over time in this relationship is so strong, I don’t have any interest in exploring alternative service providers. No matter how technically superior some other hairdresser is, he can’t win without the relationship.


At platforms like Zalando, the challenge of personalization comes from three facts: the scale of stock and customers (300,000 items and 23 million customers), the requirement of automated relationship building, and the tricky balance between being cool or creepy.

How does a product manager’s work change when the product is personalized? If we define product management as the intersection of tech, business and design, we can observe the impact from these three angles.

Design is all about the user experience. If you haven’t been customer obsessed before, now is the time. You need to be able to understand your customers at a very deep level. What are the aspects of the experience that actually add value, if personalized? Is it just the item recommendations, or everything from marketing to delivery? What is the right level of personalization, what makes it feel good and valuable? This might vary a lot depending on the culture and the individual. What are the customer’s goals at different times? Are they exploring or exploiting? How do you recognize those goals and then help customers reach them? Personalization forces you to a new level of customer insight, user research, and design.

From a business point of view, you need to level up your KPI skills. Zalando is divided into independent product teams which are responsible for one functional part of the whole; think of home page, category page, product page, wishlist, search, etc. Personalization must run consistently through all of these. Personalization alone does not make one single sale. So what is the goal and how do you measure the value of personalization? Looking at the short term, such as click-through rate, might lead us to optimize for interest, but it’s not valuable, causing negative effects further along in the customer journey. Optimizing for the current session might also be harmful for long-term relationship building. If the vision is to build a relationship like I have with my hairdresser, then you need to go for the long-term KPIs like the customer lifetime value, and some measures on how deep the relationship is; maybe by how diverse the usage of the shop is, and how widely the user shops across categories. Measuring these kinds of KPIs and attributing them correctly is not trivial.

In tech, we turn to data. First of all, you need the data that is predictive to your problem. You might have to start by building something just to gather data that enables the next steps. You can get to a certain level with rule-based systems, but at some point you enter the world of Machine Learning. Being a Product Manager with a ML product requires understanding of both data science and machine learning. You will be modeling human behaviour with a machine, so you need to be able to facilitate that discussion with the design, business, and tech experts. Data Science lives up to its name; it is science, not your standard software development. Be prepared for model exploration, data gathering, cleaning, labeling, lots of iterations, and discussions on when the performance is good enough. Dig up your statistics and probability skills, because you need to understand how those relate to what the customer will experience.

In summary, personalization offers an exciting challenge for a product manager to stretch their skills in all aspects of the role. But like in every product, the core is still the same: Deep understanding of the customer problem, and an exciting vision for the solution will take you far.

Come work for us in our Helsinki tech hub. Take a look at our jobs here.

Zalando Tech Blog – Technical Articles Dmytro Zharkov

Insights on making node APIs great

NodeJS is getting more and more popular these days. It’s gone through a long and painful history of mistakes and learning. By being a “window” for front-end developers to the “world of back-end,” it has improved the overall tech knowledge of each group of engineers by giving them the opportunity to write actual end-to-end solutions themselves using familiar approaches. It is still JavaScript, however, and that makes most back-end engineers nauseous when they see it. With this article and a number of suggestions, I would like to make NodeJS APIs look a bit better.

If you prefer looking at code over reading an article, jump to the sample project directly.

As a superset of JavaScript, TypeScript (TS) enhances ES6 inheritance with interfaces, access modifiers, abstract classes and methods (yeap, you read it correctly... abstract classes in JS), static properties, and brings strong typings. All of those can help us a lot. So, let’s walk through these cool features and check out how can we use them in NodeJS applications.

I split this post into two parts: an overview and actual code samples. If you know TS pretty well, you can jump to part two.

PART 1. OVERVIEW

INTERFACES, CLASSES, ABSTRACT CLASSES, AND TYPE ALIASES
When I first tried TS, sometimes I felt like it went nuts checking and applying types. It’s technically possible to define variable type with type aliases, interfaces, classes and abstract classes so they really look pretty similar–kind of twins or quadruplets in this case–but as I looked into TypeScript more, I found that just like siblings they are actually really individual.


Interfaces are “virtual structures” that are never transpiled into JS. Interfaces are playing a double role in TS. They can be used to check if class implements certain patterns, and also as type definitions (so called “structural subtyping”).

I really like how TS allows us to extend interfaces so we can always modify already existing ones to our own needs.

Say we have a middleware function that performs some checks on request and adds additional property to requests named “supeheroName.” TS compiler will not allow you to add it on a standard express request, so we can extend this interface with needed property.

import { Request, Response } from  "express";
interface SuperHeroRequest extends Request {
superheroName: string;
}

And then use it in a route:

app.router.get("/heroes", (req: SuperHeroRequest, res: Response) => {
if (req.superheroName) {
  res.send("I'm Batman")
}
});

Of course, let’s not forget about the main function of interfaces; enforcing classes to meet a particular contract.

interface Villain {
name: string;
crimes: string[];
performCrime(crimeName: string): void;
}
/* Compiler will ensure that all properties of IVillain interface are specified in implementing class and throw an errors on compile time if something is missing. */
class SuperVillain implements Villain {
public name: string;
public crimes: string[];

Abstract classes are usually used to define base level classes from which other classes may be derived.

abstract class Hero {
constructor(public name: string, public _feats: string[]) {
}
// Similar to interfaces we can specify method signature, that should be defined in derived classes.
abstract performFeat(feat: string): void;
// Unlike interfaces abstract classes can provide implementation along with method    signature.
getFeatsList() {
  return this._feats.join("\n");
}
}
class SuperHero extends Hero {
constructor(name: string, _feats: string[] = []) {
super(name, _feats);
}
performFeat(feat: string) {
this._feats.push(feat);
console.log(`I have just: ${feat}`);
}
}

const Thor: SuperHero = new SuperHero("Thor", ["Stop Loki"]);
Thor.performFeat("Save the world");
console.log(Thor.getFeatsList());


// Abstract classes can be used as a type as well.

const Hulk: Hero = new SuperHero("Bruce Banner");
Hulk.performFeat("Smash aliens");
console.log(Hulk.getFeatsList());


// A try to instantiate abstract class will not work
const Loki: Hero = new Hero("Thor", ["Stop Loki"]);

As you can see, we can potentially use all of those by specifying a variable type. So what should be used and when? Let's sum it up.


Type aliases can be used to define primitive and reference types: string, number, boolean, object. You can’t extend type aliases.

Interfaces can define only reference (object) types. TS documentation recommends that we use interfaces for object type literals. Interfaces can be extended and can have multiple merged declarations, so users of your APIs may benefit from it. Interface is a “virtual” structure that never appears in compiled JavaScript.

Classes, as opposed to interfaces, not only check how an object looks but ensure concrete implementation as well.

Classes allow us to specify the access modifiers of their members.

The TS compiler always transpiles classes to actual JS code, so they should be used if an actual instance of the class is created. EcmaScript native classes can be also used as a type definitions.

let numbersOnly: RegExp = /[0-9]/g;
let name: String = "Jack";

Abstract classes are really a mix of the previous two, but as it’s not possible to instantiate them directly you can only use them as a type, if an instance is created from a derived class that doesn’t provide any additional methods or properties.

ACCESS MODIFIERS
Unfortunately, JS doesn’t provide access modifiers so you can’t create, for example, a real private property. It’s possible to mock private property behaviour with closures and additional libraries, but such code looks a bit fuzzy and rather long. TS solves this issue just like any other Object Oriented Programming language. There are three access modifiers available in TS: public, private and protected.


PART 2. THE APPLICATION OR A DIVE INTO THE CODE.

So now, when we know and have all the tooling we need, we can build something great. For example, I would like to build a backend part of a MEAN (MongoDB, ExpresJS, Angular, NodeJS) stack; a simple RESTful service that will allow us to make CRUD operations with some articles. As including all the code will make this post too long, I’ll skip some parts, but you can always check the full version in the GitHub repository.

For project structure, see below:

To make code more declarative, easier to maintain and reusable, I’ll take advantage of ES6 classes and split the application into logical parts. I’m leaving most of the explanation in the comments.

./classes/Server.ts

import * as express from "express";
import * as http from "http";
import * as bodyParser from "body-parser";
import * as mongoose from "mongoose";
import * as dotenv from "dotenv";
import * as logger from "morgan";

/* Create a reusable server class that will bootstrap basic express application. */

export class Server {

/* Most of the core properties belove have their types defined by already existing interfaces. IDEs users can jump directly to interface definition by clicking on its name.  */

/* protected member will be accessible from deriving classes.  */
protected app: express.Application;

/* And here we are using http module Server class as a type. */
protected server: http.Server;

private db: mongoose.Connection;

/* restrict member scope to Server class only */
private routes: express.Router[] = [];
/*  This could be done using generics like syntaxis. You can choose which is looking better for you
private routes: Array<express.Router> = [];
*/

/* public modifiers are default ones and could be omitted. I prefer to always set them, so code  style is more consistent. */
public port: number;

constructor(port: number = 3000) {
  this.app = express();
  this.port = port;
  this.app.set("port", port);
  this.config();
  this.database();
}

private config() {
  // set bodyParser middleware to get form data
  this.app.use(bodyParser.json());
  this.app.use(bodyParser.urlencoded({ extended: true }));
  // HTTP requests logger
  this.app.use(logger("dev"));
  this.server = http.createServer(this.app);

  if (!process.env.PRODUCTION) {
    dotenv.config({ path: ".env.dev" });
  }
}

/* A simple public method to add routes to the application. */
public addRoute(routeUrl: string, routerHandler: express.Router): void {
  if (this.routes.indexOf(routerHandler) === -1) {
    this.routes.push();
    this.app.use(routeUrl, routerHandler);
  }
}

private database(): void {
  mongoose.connect(process.env.MONGODB_URI);
  this.db = mongoose.connection;
  this.db.once("open", () => {
    console.log("Database started");
  });
  mongoose.connection.on("error", () => {
    console.log("MongoDB connection error. Please make sure MongoDB is running.");
    process.exit();
  });
}

public start(): void {
  this.app.listen(this.app.get("port"), () => {
    console.log(("  App is running at http://localhost:%d in %s mode"), this.app.get("port"), this.app.get("env"));
    console.log("  Press CTRL-C to stop\n");
  });
}
}

export default Server;

I have set the server and app properties to “protected” as I want to keep them private, so it’s not possible to override or access them directly. They could be reachable from derived classes. For example, if we want to add web sockets support to our server, we can extend it with a new class and use “server” or an “app” properties as we need.

./classes/SocketServer.ts

import Server from "./Server";
import * as io from "socket.io";

class SocketServer extends Server {

/* this.server of a parent Server class is protected property, so we can access it to add a socket.  */
private socketServer = io(this.server);

constructor(public port: number) {
  super(port);
  this.socketServer.on('connection', (client) => {
    console.log("New connection established");
  });

}
}
export default SocketServer;

Going back to the application.

./app.ts

import Server from "./classes/Server";
import ArticlesRoute from "./routes/Articles.route";

const app = new Server(8080);
const articles = new ArticlesRoute();
app.addRoute("/articles", articles.router);
app.start();

As we can have multiple kinds of articles (products) e.g. electronic, fashion, digital, etc. and they might have rather different sets of properties, I’ll create a base abstract class with a number of default properties that should be common for all types of articles. All other properties can be defined in derived classes.

./classes/AbstractArticle.ts

// put basic properties into abstract class.

import ArticleType from "../enums/ArticleType";
import BaseArticle from "../interfaces/BaseArticle";
import * as uuid from "uuid";
import Price from "../interfaces/IPrice";

abstract class AbstractActrticle implements BaseArticle {
public SKU: string;
constructor(public name: string, public type: ArticleType, public price: Price, SKU: string) {
  this.SKU = SKU ? SKU : uuid.v4();
}
}

export default AbstractActrticle;

For this example, I’ll create a Shoe class that will derive from an AbstractArticle class and set its own properties.

./classes/Shoe.ts

import AbstractActrticle from "./AbstractArticle";
import ArticleType from "../enums/ArticleType";
import Colors from "../enums/Colors";
import FashionArticle from "../interfaces/FashionArticle";
import Price from "../interfaces/Price";
import Sizes from "../enums/Sizes";

class Shoe extends AbstractActrticle implements FashionArticle {
constructor(public name: string,
            public type: ArticleType,
            public size: Sizes,
            public color: Colors,
            public price: Price,
            SKU: string = "") {
  super(name, type, price, SKU);
}
}

export default Shoe;

You might have noticed that Shoe class implements FashionArticle interface. Let’s take a look at it and see how we can benefit from Interfaces and possibility to extend those.

./interfaces/BaseArticle.ts

import ArticleType from "../enums/ArticleType";
import Price from "./Price";

interface BaseArticle {
SKU: string;
name: string;
type: ArticleType;
price: Price;
}

Extension of interfaces allows us to extend our own interfaces with additional properties.

./interfaces/FashionArticle.ts

import Colors from "../enums/Colors";
import BaseArticle from "./BaseArticle";
import Sizes from "../enums/Sizes";

interface FashionArticle extends BaseArticle {
size: Sizes;
color: Colors;
}

We can also extend already existing interfaces. As an example, I’ll create an FashioArticleModel interface that will extend the Document interface from Mongoose and our  FashionArticle interface so we can use it when creating database schema.

./interfaces/FashionArticleModel.ts

import { Document } from "mongoose";
import FashionArticle from "./FashionArticle";

interface FashionArticleModel extends FashionArticle, Document {};
export default FashionArticleModel;

Using IFasionArticleModel interface in the schema allows us to create a model with properties from both the Mongoose Document and FashionArticle interfaces.

./schemas/FashionArticle.schema.ts

import { Schema, Model, model} from "mongoose";
import FashionArticleModel from "../interfaces/FashionArticleModel";

const ArticleSchema: Schema = new Schema({
name: String,
type: Number,
size: String,
color: Number,
price: {
  price: Number,
  basePrice: Number
},
SKU: String
});

// Use Model generic from mongoose to create a model of FashionArticle type.
const ArticleModel: Model<FashionArticleModel> = model<FashionArticleModel>("Article", ArticleSchema);
export {ArticleModel};

I hope this example application already shows how TypeScript can make your code more declarative, self documentable and potentially easier to maintain. Using TS is also a good exercise for frontend developers to learn and apply OOP paradigms in real life projects, and backend developers should find many familiar practices and code constructs.

Finally I would suggest to jump into Articles route class and check a CRUD functionality of the application.

./routes/Articles.route.ts

import { Request, Response, Router } from "express";
import ArticleType from "../enums/ArticleType";
import Colors from "../enums/Colors";
import Shoe from "../classes/Shoe";
import Sizes from "../enums/Sizes";
import { ArticleModel } from "../schemas/FashionArticle.schema";
import FashionArticleModel from "../interfaces/FashionArticleModel";

class ArticlesRoute {
public router: Router;

constructor() {
  this.router = Router();
  this.init();
}

// Putting all routes into one place makes it easy to search for specific functionality
// As this method will be called in a context of a different class, we need to bind methods objects to current class.
public init() {
  this.router.route("/")
    .get(this.getArticles.bind(this))=
    .post(this.createArticle.bind(this));

  this.router.route("/:id")
    .get(this.getArticleById.bind(this))
    .put(this.updateArticle.bind(this))
    .delete(this.deleteArticle.bind(this));
}
// I'm not a huge fan of JavaScript callbacks hell and especially of using it in NodeJS, so I'll use promises   instead.
public getArticles(request: Request, response: Response): void {
  ArticleModel.find()
    .then((articles: FashionArticleModel[]) => {
      return response.json(articles);
    })
    .catch((errror: Error) => {
      console.error(errror);
    })
}

public getArticleById(request: Request, response: Response): void {
  const id = request.params.id;
  ArticleModel
    .findById(id)
    .then((article: FashionArticleModel) => {
    return response.json(article);
  })
    .catch((error: Error) => {
      console.error(error);
      return response.status(400).json({ error: error });
  });
}

public createArticle(request: Request, response: Response): void {
  const requestBody = request.body;
  const article = new Shoe(requestBody.name, requestBody.type, requestBody.size, requestBody.color, requestBody.price);

  const articeModel = new ArticleModel({
    name:  article.name,
    type:  article.type,
    size:  article.size,
    color: article.color,
    price: article.price,
    SKU:   article.SKU
  });

  articeModel
    .save()
    .then((createdArticle: FashionArticleModel) => {
      return response.json(createdArticle);
    })
    .catch((error: Error) => {
      console.error(error);
      return response.status(400).json({ error: error });
    });
}

public updateArticle(request: Request, response: Response): void {
  const id = request.params.id;
  const requestBody = request.body;
  const article = new FashionArticle(requestBody.name, requestBody.type, requestBody.size, requestBody.color, requestBody.price, requestBody.SKU);

  ArticleModel.findByIdAndUpdate(id, article)
    .then((updatedArticle: FashionArticleModel) => {
      return response.json(updatedArticle);
    })
    .catch((error: Error) => {
      console.error(error);
      return response.json({ err: error });
    })
}

public deleteArticle(request: Request, response: Response): void {
  const articleId = request.params.id;
   ArticleModel.findByIdAndRemove(articleId)
    .then((res: any) => {
      return response.status(204).end();
    })
    .catch((error: Error) => {
      console.error(error);
      return response.json({ error: error });
    });
}
}
export default ArticlesRoute;

As a conclusion, TypeScript is a powerful tool that brings a really flexible, reach type checking system to your code. It also introduces enhanced well-known patterns like interfaces, abstract classes and access modifiers.

Of course, the application is not ready for production use, as we have to cover everything with tests and set up a proper development environment, but we can cover that in the future.

Work with engineers like Dmytro. Have a look at our jobs page.