1 of 119

Velocity @ Scale | Platform Engineering

Platform Engineering Meetup #1

17:00 - 17:30 Gathering

17:30 - 18:20 Building a Platform for Real Engineers / Yuval Perry

18:20 - 19:10 A New Microservice in 1 hour / Savva Khalaman & Oded Apel

19:00 - 19:40 Open discussion

2 of 119

Velocity @ Scale | Platform Engineering

Yuval Perry

  • Head of R&D, Server Infrastructure Groups
  • Wix Server Guild Manager

yuvalp@wix.com

3 of 119

Velocity @ Scale | Platform Engineering

Platform Engineering

The discipline of designing and building toolchains and workflows that enable fast self-service capabilities for software engineering organizations

4 of 119

Velocity @ Scale | Platform Engineering

The Maslow pyramid of engineering needs

Business �logic

Frameworks

Containers

CI/CD

Production

5 of 119

Velocity @ Scale | Platform Engineering

To become the best Web creation platform �for any type of user and any type of business, anywhere in the world.

Wix’s north star goal

6 of 119

7 of 119

~1,500 Web Developers

@aviranm

EU

Ukraine

Israel

America

Vilnius

Kyiv

Tel-Aviv

USA

Krakow

Dnipro

Be’er Sheva

Canada

Berlin

 Lviv

Haifa

Amsterdam

8 of 119

Velocity @ Scale | Platform Engineering

Deployed Services

~3K

Lambda Functions

1K

Builds a day

~50K

Merges to master a day

~7K

Deployments�Per Minute (work hours)

>1

Deployment Per Day

>600

Total registered users

~238M

Premium Subscriptions

>6m

Wix in numbers (as of January 2023)

9 of 119

Velocity @ Scale | Platform Engineering

Iterate as we scale

We can always improve

Enabling the business needs

Focus on DevEx

10 of 119

Velocity @ Scale | Platform Engineering

An example

11 of 119

Velocity @ Scale | Platform Engineering

No staging environment

No integration environment

Wix’s Development Strategy, in a Nutshell

At the end of it, it’s your laptop and production.

12 of 119

TDD for high code coverage

A/B testing and feature toggles

Velocity @ Scale | Platform Engineering

No staging environment

No integration environment

Wix’s Development Strategy, in a Nutshell

At the end of it, it’s your laptop and production.

Gradual rollout (Automated canary)

13 of 119

Velocity @ Scale | Platform Engineering

Our next leap

Open Platform

14 of 119

Velocity @ Scale | Platform Engineering

Quality

Uniformity

Extensibility

Open Platform

15 of 119

Velocity @ Scale | Platform Engineering

Domain modeling�API Design

Authentication

Authorization

Input Validation

Object Mapping

RPC

Secrets

Data Access

Domain Events

Error Handling

A new microservice

needs ...

GDPR

PII

Plat. Query

Advance Search

Caching

Webhooks

Scheduled Tasks

BI

Logging

Testing Strategy & Tools

Scaffold

Build

Deploy

A/B Testing Support

Gradual Rollout

Production Ownership

Monitoring

Debugging

16 of 119

Framework

17 of 119

Framework

Identity

18 of 119

Framework

Visibility

Web Hooks

GDPR

Identity

19 of 119

Framework

Input validation

Visibility

Web Hooks

GDPR

Identity

Testing

API Design

Brokers & Events

Error Handling

Database

20 of 119

Framework

Events

Visibility

Web Hooks

GDPR

Identity

Testing

API Design

Brokers & Events

Error Handling

Database

Data integrity

gRPC

Webhooks

21 of 119

Death by a thousand papercuts

22 of 119

Velocity @ Scale | Platform Engineering

The Un-platform Red Flags

Lots of docs

Un-discoverability

Unmanaged concerns

Duplicate solutions

Lots of communication

Long waits (DB, routing, etc)

No emergency rollout

Low productivity

23 of 119

Velocity @ Scale | Platform Engineering

We need a platform

24 of 119

Before

Velocity @ Scale | Platform Engineering

Exactly

After

Way After

Day 1

25 of 119

Day 1

Velocity @ Scale | Platform Engineering

״A product company’s success has a direct correlation with the speed of development - �If we don’t move faster than our competition, we’ll lose.

26 of 119

Velocity @ Scale | Platform Engineering

NILE

Streamlines platformized �microservice development

Introducing:

27 of 119

Nile Layers and tools

  • Framework
  • Automapper
  • Messaging
  • Async Task Queue
  • RPC

  • Data Access
  • Search
  • SPI
  • Logging
  • Metrics

28 of 119

Tools

Libraries

Guidelines

Documentation

Production

Consulting & Support

Velocity @ Scale | Platform Engineering

CI processes

Samples

Internal Development Platform�enables users to focus on delivering real business value

29 of 119

Velocity @ Scale | Platform Engineering

  • Harvesting Platform

Factor out the common areas from multiple well designed applications.

  • Foundation Platform

Build a platform prior to any application.

Once complete, build applications on top of it.

Platform building strategies

30 of 119

Velocity @ Scale | Platform Engineering

  • identify and define the product
  • Prioritization
  • Great communication skills
  • Collect and respond to feedback

Infrastructure Engineer

Requirements from an

  • Marketing
  • Creating engagement
  • Adoption
  • Everything we expect from an engineer

+

Product

31 of 119

Velocity @ Scale | Platform Engineering

Platform is a Product

Engineers are clients

32 of 119

Velocity @ Scale | Platform Engineering

Our Principles

33 of 119

Velocity @ Scale | Platform Engineering

  • Boots on the ground

Pave the road

Measure to improve

Our Principles

34 of 119

Velocity @ Scale | Platform Engineering

  • Identifying user needs

  • Prioritizing

  • Engaging users

Avoiding Shelfware

Boots on the ground

35 of 119

Velocity @ Scale | Platform Engineering

Boots on the ground

The art of escaping ivory towers

36 of 119

Velocity @ Scale | Platform Engineering

  • Commit on adoption

  • Pair program with users

  • Onboard in users’ teams

  • Workgroups & Advisory boards

Boots on the ground

  • Design together with users

  • User code spotlight sessions

  • Take part in ETAs sessions

  • Treat your platform as an internal OSS

The art of escaping ivory towers

37 of 119

Velocity @ Scale | Platform Engineering

Find the road that most engineers take - �and make it nice and cozy.

Minimal cognitive load

Just enough documentation

Zero boilerplate

Prefer defaults and conventions

Pave the road

38 of 119

Velocity @ Scale | Platform Engineering

Yet you can't improve what you don't measure.

Merge requests per day?

MTTRs?

Lines of code?

Deployment Frequency?

Onboarding time?

No KPI is Perfect

39 of 119

Velocity @ Scale | Platform Engineering

DON’T OVER COMPLICATE.

Find the most trivial KPI for your situation and evolve in it.

  • LoC
  • Build Time
  • staging environment availability
  • Failed deployments

40 of 119

Velocity @ Scale | Platform Engineering

  • Boots on the ground

Pave the road

Measure to improve

Main Principles

41 of 119

Velocity @ Scale | Platform Engineering

The speed paradox

Adding one person to a team of two �adds not only 50% more workforce, �but also 50% more communication

42 of 119

Platform Engineering Meetup #1

17:00 - 17:30 Gathering

17:30 - 18:20 Building a Platform for Real Engineers / Yuval Perry

18:20 - 19:10 A New Microservice in 1 hour / Savva Khalaman & Oded Apel

19:00 - 19:40 Open discussion

43 of 119

Savva Khalaman

  • 5.5 years at Wix
  • Dev/TL in the infra group from the day one
  • Technical product manager for the infra group

Oded Apel

  • 5.5 years at Wix
  • Was the lead architect for eCom for 2 years
  • Technical product manager for the infra group

odeda@wix.com

savvak@wix.com

Velocity @ Scale | Platform Engineering

44 of 119

A New Microservice in One Hour

Velocity @ Scale | Platform Engineering

45 of 119

Velocity @ Scale | Platform Engineering

Domain modeling�API Design

Authentication

Authorization

Input Validation

Object Mapping

Secrets

RPC

Data Access

Domain Events

This takes more than an hour…

How can we help?

Advance Search

Error Handling

GDPR

PII

Plat. Query

A/B Testing Support

Testing Strategy & Tools

Caching

Webhooks

Scheduled Tasks

Scaffold

Build

Deploy

Gradual Rollout

Production Ownership

Logging

Monitoring

BI

Debugging

46 of 119

Velocity @ Scale | Platform Engineering

Domain modeling�API Design

Authentication

Authorization

Input Validation

Object Mapping

Secrets

RPC

Data Access

Domain Events

Advance Search

Error Handling

GDPR

PII

Plat. Query

A/B Testing Support

Testing Strategy & Tools

Caching

Webhooks

Scheduled Tasks

Domain modeling�API Design

Authentication

Authorization

Input Validation

Object Mapping

Secrets

RPC

Data Access

Domain Events

Advance Search

Error Handling

GDPR

PII

Plat. Query

A/B Testing Support

Testing Strategy & Tools

Caching

Webhooks

Scheduled Tasks

Scaffold

Build

Deploy

Gradual Rollout

Production Ownership

Logging

Monitoring

BI

Debugging

Today we will focus on these:

47 of 119

Velocity @ Scale | Platform Engineering

Domain modeling�API Design

Authentication

Authorization

Input Validation

Object Mapping

Secrets

RPC

Data Access

Domain Events

Advance Search

Error Handling

GDPR

PII

Plat. Query

A/B Testing Support

Testing Strategy & Tools

Caching

Webhooks

Scheduled Tasks

Domain modeling�API Design

Authentication

Authorization

Input Validation

Object Mapping

Secrets

RPC

Data Access

Domain Events

Advance Search

Error Handling

GDPR

PII

Plat. Query

A/B Testing Support

Testing Strategy & Tools

Caching

Webhooks

Scheduled Tasks

Scaffold

Build

Deploy

Gradual Rollout

Production Ownership

Logging

Monitoring

BI

Debugging

But in reality we already have good solutions for all of these :)

48 of 119

Some basic background about protobuf

Velocity @ Scale | Platform Engineering

49 of 119

Protobuf is ...

rpc GetProduct (GetProductRequest) returns (GetProductResponse) {

option (google.api.http) = { get: "/v1/products/{id}" };

option (wix.api.permission).name = "WIX_STORES.READ_PRODUCTS";

}

An IDL (interface definition language) used to describe APIs

message Product {

string id = 1 [(wix.api.readOnly) = true];

google.protobuf.StringValue name = 2 [(wix.api.minLength) = 1, (wix.api.maxLength) = 80];

}

Velocity @ Scale | Platform Engineering

50 of 119

message Product {

string id = 1 [(wix.api.readOnly) = true];

google.protobuf.StringValue name = 2 [(wix.api.minLength) = 1, (wix.api.maxLength) = 80];

}

Additional Wix semantics, e.g., validations and permissions

my.proto file

Velocity @ Scale | Platform Engineering

51 of 119

This was the experience of developing a new service in Wix two years ago

Velocity @ Scale | Platform Engineering

52 of 119

Start a new service

New Service

Application().start(args)

Write just one line of code to get a working Web server in production.

Developer Needs To

...

Velocity @ Scale | Platform Engineering

53 of 119

Application(myApiImplementation).start(args)

(defined in the Proto file)

New Service

+ APIs

Expose APIs

Implement myAPI �(generated from proto)

Developer Needs To

...

Velocity @ Scale | Platform Engineering

54 of 119

New Service

+ RPC

+ APIs

Build RPC Clients

val currencyConverter = CurrencyConverter.stub(grpcChannel,

_.withAuthority("com.wixpress.platform.currency-converter")

)

Application(myApiImplementation).start(args)

Know which library to use (out of 3), depending on which API we’re connecting to

Developer Needs To

...

Velocity @ Scale | Platform Engineering

55 of 119

+ Messaging

New Service

+ RPC

+ APIs

Work with our Messaging Queue

Create it�

Create producers \ consumers�

Call produce when you want to send a message�

Subscribe to some topic and wire up a handler

Developer Needs To

...

val client = AdminClient.create()

val result1: Future[Unit] = client.createTopic("my-topic-name")

val producer: GreyhoundProducer = GreyhoundBuilder.producer()

val greyhound = GreyhoundBuilder().withProducers(producer).build

producer.produce("some-topic", SomeMessage("message"))

Velocity @ Scale | Platform Engineering

56 of 119

laboratory.conductExperiment(classOf[SomeFeatureToggle], false, new BooleanConverter)

+ A/B Tests

+ Messaging

New Service

+ RPC

+ APIs

Conduct Experiments

Velocity @ Scale | Platform Engineering

57 of 119

val db = MySql.newHikariDataSource(dbName = "my-db-name").build()

+ A/B Tests

+ Messaging

+ DB

New Service

+ RPC

+ APIs

Create and Manage DB Connections

Velocity @ Scale | Platform Engineering

58 of 119

Many more things to take care of

  • GDPR
  • PII encryption
  • Permission checks
  • Validating API input

+ A/B Tests

+ Messaging

+ DB

New Service

+ RPC

+ APIs

Velocity @ Scale | Platform Engineering

59 of 119

+ A/B Tests

+ Messaging

+ DB

New Service

+ RPC

+ APIs

If each library is well written, simple, and easy to use,

What is the Problem?

Velocity @ Scale | Platform Engineering

60 of 119

+ A/B Tests

+ Messaging

+ DB

New Service

+ RPC

+ APIs

What it really means to wire RPC

Developer Needs To

...

  1. Write the actual code
    1. look in readme
    2. copy \ paste (3 options)
  2. (Sometimes) Create a configuration file
  3. Read from it
  4. Mocking
    • Code needs to allow wiring up a mock instead of building the real RPC client
  5. Test the wiring code
    • Start up my service and a mock of the other service (3 options)

Velocity @ Scale | Platform Engineering

61 of 119

  • Write the actual code
    • look in readme
    • copy \ paste (3 options)
  • (Sometimes) Create a configuration file
  • Read from it
  • Mocking
    • Code needs to allow wiring up a mock instead of building the real RPC client
  • Test the wiring code
    • Start up my service and a mock of the other service (3 options)

for every RPC

+ A/B Tests

+ Messaging

+ DB

New Service

+ RPC

+ APIs

Developer Needs To

...

Velocity @ Scale | Platform Engineering

62 of 119

+ A/B Tests

+ Messaging

+ DB

New Service

+ RPC

+ APIs

for all other examples

Velocity @ Scale | Platform Engineering

63 of 119

Here’s what it looks like

object SomeService {

val rpc = <wiring code>

val laboratory = <wiring code>

val kafkaProducer = <wiring code>

val kafkaConsumer = <wiring code>

val service = SomeService(rpc, laboratory, kafkaProducer, kafkaConsumer)

}

Starting up the service

class SomeService� (rpc, laboratory, kafkaProducer, kafkaConsumer) {

<Business logic>

}

Actual service code

Velocity @ Scale | Platform Engineering

64 of 119

class Test {

<Test some business logic>

def buildTestEnv = {

val rpc = <create mock>

val laboratory = <create mock>

val kafkaProducer = <create mock>

val kafkaConsumer = <create mock>

val service = SomeService(rpc, laboratory, kafkaProducer, kafkaConsumer)

}

}

class E2ETest {

<Test a flow that checks if the wiring code works>

def buildTestEnv = {

val rpc = <start mock service>

val laboratory = <start mock laboratory>

val kafka = <start mock Kafka>

val service = <start actual service>

}

}

Here’s what it looks like

Tests

Velocity @ Scale | Platform Engineering

65 of 119

Define the Problem

We want developers to �focus on business value.

Instead, time is wasted on wiring things and writing the same things over and over again

Velocity @ Scale | Platform Engineering

66 of 119

How Platform Engineers Can Improve DevEx

Talk to users

    • Identify common concerns
    • Hunt time wasters

Velocity @ Scale | Platform Engineering

67 of 119

Don’t test the infra

Recommended way of testing things

Single entity per service

Make infra opinionated

How Platform Engineers Can Improve DevEx

Talk to users

    • Identify common concerns
    • Hunt time wasters

Velocity @ Scale | Platform Engineering

68 of 119

  • Move as much wiring code as possible to be configuration
  • Validate this configuration to avoid developer mistakes being discovered in runtime
  • Generate boilerplate code based on this configuration

Now that we understand it we decide to

How Platform Engineers Can Improve DevEx

Velocity @ Scale | Platform Engineering

69 of 119

Show Me Some Code Please!

Velocity @ Scale | Platform Engineering

70 of 119

Every service would have the following, which tells the framework 2 interesting things

Single entity per service

prime_app(

name = "some-service",

artifact = "com.wixpress.some-test-service",

rpcs = [

"com.wixpress.platform.currency-converter",

],

service = "com.wixpress.some.service.api",

)

Velocity @ Scale | Platform Engineering

71 of 119

prime_app(

name = "some-service",

artifact = "com.wixpress.some-test-service",

rpcs = [

"com.wixpress.platform.currency-converter",

],

service = "com.wixpress.some.service.api",

)

Every service would have the following, which tells the framework 2 interesting things

  1. That service wants to RPC into another service called currency-converter
  1. That service is exposing the API some.service.api (defined in proto)

Velocity @ Scale | Platform Engineering

Single entity per service

72 of 119

Now, look what we can do :)

We can generate SomeServiceAPI

Once created, it gets automatically wired

class SomeServiceAPI(currencyConverter: CurrencyConverter) extends SomeServiceAPI {}

New Service

Velocity @ Scale | Platform Engineering

73 of 119

+ RPC

object SomeServiceAPI {

def apply(contextBuilder: SomeServiceAPIContextBuilder) = {

val context = contextBuilder.build()

val service = new SomeServiceAPI(context.rpc.currencyConverter)

}

When you start up the service, you get a ready-to-use RPC client

New Service

Velocity @ Scale | Platform Engineering

74 of 119

+ Tests

+ RPC

New Service

For the tests, we generate a contextBuilder which you can pass to your service

val contextBuilder = SomeServiceAPITestContextBuilder()

val service = contextBuilder.blockingPlatformized(SomeServiceAPI(contextBuilder))

You can then easily mock the RPC results and test your flow end to end

context.rpc.currencyConverter(EUR, USD) returns 3.55

service.callSomeAPI

Don’t test the infra

Recommended way of testing things

Velocity @ Scale | Platform Engineering

75 of 119

class Test {

<Test some business logic>

def buildTestEnv = {

val rpc = <create mock>

val laboratory = <create mock>

val kafkaProducer = <create mock>

val kafkaConsumer = <create mock>

val service = SomeService(rpc, laboratory, kafkaProducer, kafkaConsumer)

}

}

class E2ETest {

<Test a flow that checks if the wiring code works>

def buildTestEnv = {

val rpc = <start mock service>

val laboratory = <start mock laboratory>

val kafka = <start mock Kafka>

val service = <start actual service>

}

}

Reminder - this is what it used to look like

Velocity @ Scale | Platform Engineering

+ Tests

+ RPC

New Service

76 of 119

You write your business logic

class SomeServiceAPI(currencyConverter: CurrencyConverter) extends SomeServiceAPI { {

override def SomeAPI(request: SomeAPIRequest): Future[SomeAPIResponse] = {

// Business logic goes here

}

}

+ Tests

+ RPC

New Service

Velocity @ Scale | Platform Engineering

77 of 119

Request data is enriched (with more data e.g., caller identity and site context) & Input validation & permission checks based on the proto declaration

+ request data

+ Input validation

+ Permission checks

You get:

+ Tests

+ RPC

New Service

You write your business logic and before your code is even reached

class SomeServiceAPI(currencyConverter: CurrencyConverter) extends SomeServiceAPI { {

override def SomeAPI(request: SomeAPIRequest): Future[SomeAPIResponse] = {

// Business logic goes here

}

}

Velocity @ Scale | Platform Engineering

78 of 119

+ Producing

Producing is easy

context.domainEvents.sendCreatedEvent(<id>, <entity>)

Since we decided that every service is about a single main entity, producing is just as simple as

contextBuilder.domainEventsCollector.allSentEvents <assertion>

Testing it is also easy

+ request data

+ Input validation

+ Permission checks

+ Tests

+ RPC

New Service

Velocity @ Scale | Platform Engineering

79 of 119

+ Consuming

+ Producing

+ request data

+ Input validation

+ Permission checks

+ Tests

+ RPC

New Service

rpc ConsumePaymentEvents (.wix.common.domainevents.DomainEvent) returns (google.protobuf.Empty) {

option (.wix.api.subscription) = {

entity: "com.wix.ecom.orders.payments.v1.OrderTransactions"

};

}

In your proto file, you declare that you want to consume messages from another entity.

No need to know which Kafka topic it is

Consuming

Velocity @ Scale | Platform Engineering

80 of 119

+ Consuming

+ Producing

+ request data

+ Input validation

+ Permission checks

+ Tests

+ RPC

New Service

As a result, you get this stub in your service to implement. It looks exactly like a regular endpoint in your service, so testing it is straightforward.

override def consumePaymentEvents (event: DomainEvent[OrderTransactions])

Consuming

Velocity @ Scale | Platform Engineering

81 of 119

+ Experiments

+ Consuming

+ Producing

+ request data

+ Input validation

+ Permission checks

+ Tests

+ RPC

New Service

context.petri.conductSpecsSomeSpec.contains(true)

And then you can just do:

prime_app(

petri_specs = [

"specs.someSpec",

],

)

Experiments

Every service would have the following, which tells the framework 2 interesting things:

82 of 119

How did we do it?

  1. We talked to our users, found common concerns that everyone is doing over and over again

+ Experiments

+ Consuming

+ Producing

+ request data

+ Input validation

+ Permission checks

+ Tests

+ RPC

New Service

Velocity @ Scale | Platform Engineering

83 of 119

+ Input validation

+ Tests

+ Permission checks

+ Experiments

+ Consuming

+ Producing

+ request data

+ RPC

New Service

  1. We allowed to do these things declaratively (RPC service name, validations and more)

How did we do it?

  • We talked to our users, found common concerns that everyone is doing over and over again

Velocity @ Scale | Platform Engineering

84 of 119

  1. We generated a fully working test environment

Test env

and then...

+ Input validation

+ Tests

+ Permission checks

+ Experiments

+ Consuming

+ Producing

+ request data

+ RPC

New Service

  • We allowed to do these things declaratively (RPC service name, validations and more)

How did we do it?

  • We talked to our users, found common concerns that everyone is doing over and over again

Velocity @ Scale | Platform Engineering

85 of 119

  1. We auto generated opinionated code based on these decelerations:

+ Input validation

+ Permission checks

+ Tests

+ Experiments

+ Consuming

+ Producing

+ request data

+ RPC

New Service

  • Validations and permission checks always happen before your business logic and you can’t opt out
  • Infra is responsible to respond with the correct error codes
  • You use that test environment to test YOUR logic, you DO NOT test the infra

Platform

Velocity @ Scale | Platform Engineering

86 of 119

The Result

Happy developers :)

Focused on providing value, not writing boilerplate code

New Service

Velocity @ Scale | Platform Engineering

87 of 119

Savva Khalaman

  • 5.5 years at Wix
  • Dev/TL in the infra group from day one
  • Technical product manager for the infra group

Oded Apel

  • 5.5 years at Wix
  • Was the lead architect for eCom for 2 years
  • Technical product manager for the infra group

odeda@wix.com

savvak@wix.com

Velocity @ Scale | Platform Engineering

88 of 119

Data Layer

Can it be simple?

... at least for users

Velocity @ Scale | Platform Engineering

89 of 119

Micro Service Layers

Controller

Business Logic

Data Layer

Velocity @ Scale | Platform Engineering

90 of 119

Micro Service Layers

Controller

Business Logic

Data Layer

Velocity @ Scale | Platform Engineering

91 of 119

“…is a pattern that provides an abstract interface to some type of database or other persistence mechanism”

Micro Service Layers

Controller

Business Logic

Data Access Object

Velocity @ Scale | Platform Engineering

92 of 119

BUT WHY?

Micro Service Layers

Controller

Business Logic

Data Access Object

“…is a pattern that provides an abstract interface to some type of database or other persistence mechanism”

93 of 119

Take 1

“…to ease moving to another database!”

A quote from numerous hot discussions

Data Access Object

Well… NO

94 of 119

Take 2

  • Single point of responsibility
  • Separates the data access from the application needs

Data Access Object

OK...

95 of 119

Take 2

  • Single point of responsibility
  • Separates the data access from the application needs

Data Access Object

Wait.. is it completely true?

96 of 119

Common Concerns

Data Access Object

  • Table schema
  • Multi tenancy
  • Optimistic locking
  • Exceptions (application & system)
  • Connections management
  • Logs & metrics

Velocity @ Scale | Platform Engineering

97 of 119

Common Concerns

Data Access Object

  • Table schema
  • Multi tenancy
  • Optimistic locking
  • Exceptions (application & system)
  • Connections management
  • Logs & metrics

Velocity @ Scale | Platform Engineering

Technical

98 of 119

Common Concerns

Data Access Object

  • Table schema
  • Multi tenancy
  • Optimistic locking
  • Exceptions (application & system)
  • Connections management
  • Logs & metrics
  • PII data and encryption
  • GDPR
  • Soft Delete
  • Authorised access
  • Cloning of data
  • and many more…

Velocity @ Scale | Platform Engineering

Business

99 of 119

Common Concerns

Data Access Object

  • Table schema
  • Multi tenancy
  • Optimistic locking
  • Exceptions (application & system)
  • Connections management
  • Logs & metrics
  • PII data and encryption
  • GDPR
  • Soft Delete
  • Authorised access
  • Cloning of data
  • and many more…

Velocity @ Scale | Platform Engineering

  • Many things to take care of
  • Multiple implementations
    • need in Wix Standard Solutions
  • Application needs shape data access
    • wire in Business semantics

100 of 119

Simple

Data Layer

(SDL)

Velocity @ Scale | Platform Engineering

101 of 119

Core Ideas

  • Single entity per SDL
  • Easy to use; hard to make a mistake
  • Uniformity

Velocity @ Scale | Platform Engineering

102 of 119

So… What have we got?

Velocity @ Scale | Platform Engineering

103 of 119

prime_app(

...

sdl = {

"my_cluster": {

"my_db": {

"persons": {

"entity": "com.example.Person",

"optimistic_locking": True,

"trash_bin": True,

},

},

},

}

Starting with the App macro, which tells the framework 2 interesting things

  • Entity that SDL works with�com.example.Person
  • Core features that SDL should be configured with (used for code gen)

Velocity @ Scale | Platform Engineering

104 of 119

case class Person(@id id: Option[UUID], name: String, revision: Option[Long], updatedDate: Timestamp)

So… what have we got?

Definition of the persisted class

105 of 119

case class Person(@id id: Option[UUID], name: String, revision: Option[Long], updatedDate: Timestamp)

So… what have we got?

Definition of the persisted class

System Fields

106 of 119

val person: Person = …�context.sdl.update(person)

And then you just:

case class Person(@id id: Option[UUID], name: String, revision: Option[Long], updatedDate: Timestamp)

So… what have we got?

Definition of the persisted class

107 of 119

val person: Person = …�context.sdl.update(person)

And then you just:

case class Person(@id id: Option[UUID], name: String, revision: Option[Long], updatedDate: Timestamp)

Example: Updating an entity

Definition of the persisted class

  • Implicitly extracts the tenant
  • Checks the optimistic locking
  • Sends an updated event
  • Updates system fields
  • * Encrypts data (if needed)
  • Logs / metrics
  • Exceptions

108 of 119

Velocity @ Scale | Platform Engineering

How does it work?

109 of 119

Persistence

Common Table Layout

Tenant (implicit, not a part of the method signatures)

tenant_id

entity_id

entity

215a3b09-0858-....

136baec8-560e-...

{

"name": "John",

"revision": 31,

}

Data as JSON

Velocity @ Scale | Platform Engineering

110 of 119

DDL deduction. Enables:

    • Automatic schema propagation
    • Build-time backwards compatibility check
    • Single choke point

@indices(Set(

Index(Property("name")),

Index(Property("age", IndexDirection.Ascending)),

))

case class Person(

@id id: Option[UUID],

name: String,

age: Int,

revision: Option[Long] = None

)

What if I need an index?

Velocity @ Scale | Platform Engineering

111 of 119

PII/GDPR Support

  • PII - Personal Identifiable Information
  • Needs to be encrypted

@indices(Set(

Index(Property("name")),

Index(Property("age", IndexDirection.Ascending)),

))

case class Person(

@id id: Option[UUID],

@pii name: String,

age: Int,

revision: Option[Long] = None

)

Velocity @ Scale | Platform Engineering

112 of 119

And many more

features that we are happy to brag about…

  • Table structure
  • Data separation (aka tenancy)
  • Optimistic locking
  • Exceptions (application & system)
  • PII and data encryption
  • Connections management
  • Logs & metrics
  • GDPR
  • Soft Delete
  • Authorised access
  • Cloning of data
  • and many more…

Velocity @ Scale | Platform Engineering

113 of 119

Functionality CAN NOT come at the expense

of Dev Experience…

Velocity @ Scale | Platform Engineering

114 of 119

Build-time feedback whenever possible

Clear API, no ambiguity

Example:

  • Method signatures can change based on configuration

Dev Experience

Velocity @ Scale | Platform Engineering

115 of 119

Example:

  • Optimistic locking may change SDL’s method signature (i.e. with/without revision)
  • Naive overloading would be confusing

Dev Experience

With Optimistic Locking

Velocity @ Scale | Platform Engineering

Without Optimistic Locking

116 of 119

Query DSL

    • Uniformity
    • API First
    • Opinionated & predictable
        • Supported operations
        • Validations
        • Response control

Velocity @ Scale | Platform Engineering

val query = QueryV2()

.withFilter( and(equal("name", "John"), gt("age", 16)) )

.withSort( Sorting("age", SortOrder.ASC) )

.withCursorPaging()

context.sdl.query.fromPlatformizedQuery(query).execute()

117 of 119

#1 Treat your platform as a product; your engineers as users

#2 Solve real problems

#3 Hyper Velocity - lies in solving common business problems via higher level abstractions

Summary

Velocity @ Scale | Platform Engineering

118 of 119

Thank You!

Any questions?

odeda@wix.com

savvak@wix.com

119 of 119

Platform Engineering Meetup #1

17:00 - 17:30 Gathering

17:30 - 18:20 Building a Platform for Real Engineers / Yuval Perry

18:20 - 19:10 A New Microservice in 1 hour / Savva Khalaman & Oded Apel

19:00 - 19:40 Open discussion