Benjamin Nothdurft
Software Craftsman, DevOps, @jenadevs @jugthde Founder/Speaker, Traveller & Cyclist – focusing on Continuous Delivery Pipelines, Automation, Docker, Selenium, Java
Benjamin Nothdurft
codecentric AG
General Observation
2006
2011
2012
2015
2017
2017
Definition after Analysis
A cloud-native application (CNA) is a distributed, elastic
and horizontal scalable system composed of (micro)services
which isolates state in a minimum of stateful components.
The application and each self-contained deployment unit of
that application is designed according to cloud-focused de-
sign patterns and operated on a self-service elastic platform.
considers the IDEAL attributes (2014)
considers assumptions for motivation of CNA architecture application (Kratz, Quint 2014)
Balalie converted these motivational assumptions in applications- and infrastructure approaches (2016)
Katz and Quint also include other popular definitions
(1/3)
Katz and Quint also include other popular definitions
(2/3)
Katz and Quint also include other popular definitions
(3/3)
The traditional IT “best practices” are counterproductive because they solve a completely different problem
What are the new drivers?
What are the new goals?
How should organizations look like?
What are the implications?
1. One codebase, one application
2. API first
3. Dependency management
4. Design, build, release, and run
5. Configuration, credentials, and code
6. Logs
7. Disposability
8. Backing services
9. Environment parity
10. Administrative processes
11. Port binding
12. Stateless processes
13. Concurrency
14. Telemetry
15. Authentication and authorization
Chis Richardson
Text
SOLID Principles for Cloud Native Applications (2018) by Red Hat
product added to cart
domain event (past tense)
orange sticky note
relevant for domain experts
command (present tense)
blue sticky note
triggers a domain event
product add to cart
command (present tense)
blue sticky note
triggers a domain event
product add to cart
aggregate/entity
yellow sticky note
data that is interacted with
product
find borders where a domain model has a different meaning
domain events flow between different models
bounded context/sub-domain
red sticky note
core name that is relevant/valid
checkout
narrative/stories/user journey
line(s)
multiple storytellings
common language for everyone
provider-ui
storefront-ui
merchant-ui
Frontend
api proxy
tenant
auth
Core
business unit
site
shop
Shop Admin
Product
product
. . .
. . .
. . .
. . .
Model Flow
Upstream Patterns
Open Host Service
Event Publisher
Downstream Patterns
Customer / Supplier
Conformist
Anticorruption Layer
In-Between Patterns
Shared Kernel
Published Language
Seperate Ways
Upstream System
Down-stream System
Bounded Context offers defined set of services that expose functionality
Any downstream system can implement their own integration
Especially useful with many systems that want to integrate
SOAP/REST
Upstream System
Down-stream System
@RepositoryRestController
@RequiredArgsConstructor
public class SiteCustomController {
@NonNull
private final SiteService siteService;
@NonNull
private final EntityLinks entityLinks;
@PostMapping("sites")
public ResponseEntity<Resource<Site>> create(@Valid @RequestBody SiteWithAdminUser body) {
SiteCreateResource siteBody = body.getSite();
Site transientSite = Site.builder()
.active(siteBody.isActive())
.hostname(siteBody.getHostname())
.locale(siteBody.getLocale())
.build();
Site site = siteService.create(transientSite, body.getAdminUser());
return ResponseEntity
.created(entityLinks.linkForSingleResource(Site.class, site.getId()).toUri())
.body(new Resource<>(site));
}
@DeleteMapping("sites/{siteId}")
public ResponseEntity<Void> delete(@PathVariable("siteId") Optional<Site> site) {
site.ifPresent(siteService::delete);
return ResponseEntity.noContent().build();
}
}
Bounded Context published Domain Events
Example: Messaging or Feeds
Other bounded context may subscribe to those events to react upon them.
Message
Upstream System
Down-stream System
@Component
@Slf4j
@RequiredArgsConstructor
public class MailMessagePublisher {
private final DomainMessagePublisher domainMessagePublisher;
private final DomainMessageFactory domainMessageFactory;
public void publishMailSentEvent(ClientResponse response) {
publishMessage("mailgateway.mail.sent.event", response);
}
private void publishMessage(String messageType, ClientResponse response) {
MailSendEventMessage mail = MailSendEventMessage.builder()
.messageId(response.getId())
.status(QUEUED)
.build();
DomainMessage<MailSendEventMessage> message = domainMessageFactory.create(messageType, mail);
domainMessagePublisher.publish(message);
log.info("Published message of type {}", message.getMessageType());
}
@AllArgsConstructor
@Builder
@Getter
@JsonTypeName("mail-sent-event")
static class MailSendEventMessage implements DomainMessagePayload {
private String messageId;
private MailStatus status;
}
}
@Slf4j
@MessagePublisher
@RequiredArgsConstructor
class ImageMessagePublisher(
private val domainMessageFactory: EntityDomainMessageFactory,
private val domainMessagePublisher: DomainMessagePublisher,
private val storageApiUriBuilder: StorageApiUriBuilder
) {
val log: Logger = LoggerFactory.getLogger(javaClass)
fun publishCreatedEvent(image: Image) {
publishMessageWithoutChangeSet(image, "shop.image.created.event")
}
fun publishDeletedEvent(image: Image) {
publishMessageWithoutChangeSet(image, "shop.image.deleted.event")
}
private fun publishMessageWithoutChangeSet(image: Image, messageType: String) {
val message = domainMessageFactory
.prepareDomainMessageForType(messageType)
.withEntity(image)
.withPayloadType("image")
.withCurrentTenant()
.withoutChangeSet()
.withAdditionalProperty("dataUri", toRelativeUri(image))
.build()
log.info("publishing message {}", message)
domainMessagePublisher.publish(message)
}
private fun toRelativeUri(image: Image?): String? =
image?.let { storageApiUriBuilder.getRelativeImageDownloadUri(it.dataUri).expand().toString() }
}
Two teams share a customer / supplier relation
The downstream team is considered to be the customer
Veto rights may be applicable
Veto
Upstream System
Down-stream System
@Test
@PactVerification()
public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() {
// when
ResponseEntity<String> response = new RestTemplate()
.getForEntity(mockProvider.getUrl() + "/pact", String.class);
// then
assertThat(response.getStatusCode().value()).isEqualTo(200);
assertThat(response.getHeaders().get("Content-Type").contains("application/json")).isTrue();
assertThat(response.getBody()).contains("condition", "true", "name", "tom");
}
Consumer-Driven Contracts with Pact
The downstream conforms to the model of the upstream team
No translation of models
No veto rights
Upstream System
Down-stream System
@RunWith(SpringRunner.class)
@ShopApplicationTest
public class SiteEventContractTest {
@Autowired
private MessageStubTrigger messageStubTrigger;
@MockBean
private SiteEventSubscriber siteEventSubscriber;
@Captor
private ArgumentCaptor<DomainMessage<SitePayload>> siteEventCaptor;
@Captor
private ArgumentCaptor<DomainMessage<SiteFeaturesPayload>> siteFeaturesEventCaptor;
@Test
public void should_process_site_created_event() {
messageStubTrigger.trigger("site.site.created.event");
thenVerify(siteEventSubscriber).should().consumeSiteCreatedEvent(siteEventCaptor.capture());
then(siteEventCaptor.getValue().getTenantId()).isNotNull();
then(siteEventCaptor.getValue().getPayload().getLocale()).isNotNull();
}
@Test
public void should_process_site_deleted_event() {
messageStubTrigger.trigger("site.site.deleted.event");
thenVerify(siteEventSubscriber).should().consumeSiteDeletedEvent(siteEventCaptor.capture());
then(siteEventCaptor.getValue().getTenantId()).isNotNull();
}
@Test
public void should_process_site_features_updated_event() {
messageStubTrigger.trigger("site.site-features.updated.event");
thenVerify(siteEventSubscriber).should().consumeSiteFeaturesUpdatedEvent(siteFeaturesEventCaptor.capture());
then(siteFeaturesEventCaptor.getValue().getTenantId()).isNotNull();
then(siteFeaturesEventCaptor.getValue().getPayload().getFeatures()).isNotEmpty();
}
}
A layer that isolates a client model from the provided model by translation
external vs. internal model
Upstream System
Down-stream System
public enum GoogleAvailability {
@JsonProperty(value = "in stock")
IN_STOCK("in stock"),
@JsonProperty(value = "out of stock")
OUT_OF_STOCK("out of stock"),
@JsonProperty(value = "preorder")
PRE_ORDER("preorder");
private String name;
GoogleAvailability(String name) {
this.name = name;
}
public static GoogleAvailability fromAvailabilityState(AvailabilityState availability) {
if (availability == null
|| availability.equals(AvailabilityState.IN_STOCK)
|| availability.equals(AvailabilityState.LOW_STOCK)) {
return IN_STOCK;
} else if (availability.equals(AvailabilityState.OUT_OF_STOCK)
|| availability.equals(AvailabilityState.NOT_AVAILABLE)) {
return OUT_OF_STOCK;
} else {
return PRE_ORDER;
}
}
}
Two team share a (subset of the) domain model
can be shared lib or a database
Upstream System
Down-stream System
public class DomainMessage<T extends DomainMessagePayload> {
private String messageType;
private LocalDateTime timestamp;
private Integer tenantId;
@JsonInclude(NON_EMPTY)
private Collection<DiffItem> changeSet = emptyList();
private T payload;
private DomainMessage() {
// needed by Jackson
}
@NotNull
public LocalDateTime getTimestamp() {
return timestamp;
}
@Nullable
public Integer getTenantId() {
return tenantId;
}
@NotNull
public String getMessageType() {
return messageType;
}
@NotNull
public Collection<DiffItem> getChangeSet() {
return changeSet;
}
@NotNull
public T getPayload() {
return payload;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this) //
.add("messageType", messageType) //
.add("timestamp", timestamp) //
.add("tenantId", tenantId) //
.add("changeSet", changeSet) //
.add("payload", payload) //
.toString();
}
@NotNull
public static <T extends DomainMessagePayload> DomainMessageBuilder<T> builder(@NotNull String messageType) {
return new DomainMessageBuilder<>(messageType);
}
@JsonIgnore
public boolean isChangeSetRelevantForPayload() {
Set<String> possiblePathNames = Arrays.stream(BeanUtils.getPropertyDescriptors(payload.getClass()))
.map(PropertyDescriptor::getName)
.map(property -> "/" + property)
.collect(toSet());
return changeSet.stream()
.map(DiffItem::getPath)
.anyMatch(possiblePathNames::contains);
}
public static class DomainMessageBuilder<T extends DomainMessagePayload> {
private final String messageType;
private Integer tenantId;
private LocalDateTime timestamp = LocalDateTime.now();
private Collection<DiffItem> changeSet = emptyList();
@NotNull
private DomainMessageBuilder(@NotNull String messageType) {
this.messageType = checkNotNull(messageType);
}
@NotNull
public DomainMessageBuilder<T> timestamp(@NotNull LocalDateTime timestamp) {
this.timestamp = checkNotNull(timestamp);
return this;
}
@NotNull
public DomainMessageBuilder<T> tenantId(@Nullable Integer tenantId) {
this.tenantId = tenantId;
return this;
}
@NotNull
public DomainMessageBuilder<T> changeSet(@Nullable Collection<DiffItem> changeSet) {
this.changeSet = changeSet;
return this;
}
@NotNull
public DomainMessage<T> build(@NotNull T payload) {
requireJsonTypeInformation(checkNotNull(payload).getClass());
final DomainMessage<T> domainMessage = new DomainMessage<>();
domainMessage.messageType = this.messageType;
domainMessage.timestamp = this.timestamp;
domainMessage.tenantId = this.tenantId;
domainMessage.payload = payload;
domainMessage.changeSet = changeSet;
return domainMessage;
}
}
}
dependencyManagement {
imports {
mavenBom "com.example.demo:shared-parent:1.2.3"
}
}
dependencies {
compile ("com.example.demo:shared-message")
}
@NoArgsConstructor(access = PRIVATE)
@AllArgsConstructor(access = PRIVATE)
@EqualsAndHashCode(callSuper = false)
@Getter
@Builder
@Embeddable
public class Identifier implements Serializable {
private static final long serialVersionUID = -123242542L;
@Enumerated(STRING)
@NotNull
@Column(name = "TYPE", length = 10, nullable = false)
private IdentifierType type;
@NotNull
@Column(name = "VALUE", length = 255, nullable = false)
private String value;
}
public enum IdentifierType {
EAN,
UPC,
ISBN,
MPN
}
No connection between bounded contexts exists
Teams can find their own solution for their domain
Upstream System
Down-stream System
In 2015 the Cloud Native Computing Foundation
(CNCF, www.cncf.io) was founded under the umbrella of the Linux Foundation with companies like
Cloud Foundry, Core OS, Docker, Google, Mesosphere, Red Hat, Twitter, VMWare oder Weaveworks
and over 100.000 members.
Goal: Standardization of existing technologies and cultivate a culture of open source to simplify starting with cloud native
Agreed Definition by CNCF Members
Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.
These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, they allow engineers to make high-impact changes frequently and predictably with minimal toil.
The Cloud Native Computing Foundation seeks to drive adoption of this paradigm by fostering and sustaining an ecosystem of open source, vendor-neutral projects. We democratize state-of-the-art patterns to make these innovations accessible for everyone.
Modern Definitions
by Tool Companies
Cloud Native Trailmap
Cloud Native Landscape
Graduated Projects
Incubating Projects
Business and IT today are the same side of the same coin.
The other side is the market.
General Recommendation
IT has changed
The role of IT has changed
New drivers
New goals
New building blocks
DevOps results in a re-org of IT
Dev(Sec)Ops drives implementation of a new IT
Slides
3 Core Topics for Companies
By Benjamin Nothdurft
On a Hunt for the Traces of the Next Step in IT Evolution: History, Fundamentals and Future of Cloud Native - https://www.meetup.com/de-DE/codecentric-Dortmund-tech-talk/events/268080863/
Software Craftsman, DevOps, @jenadevs @jugthde Founder/Speaker, Traveller & Cyclist – focusing on Continuous Delivery Pipelines, Automation, Docker, Selenium, Java