Business Function Comparison |
|||||||
| The VDI (Verein Deutscher Ingenieure) publishes in the VDI 3601 Warehouse Management Systems specification, a complete list of requirements to modern WMS software systems including quantity- and location-management as well as transportation management and disposition. These requirement topics are listed below with a short description how it is realised in OpenWMS.org. | |||||||
Administratives environment |
|||||||
User and authorisation management (VDI3603-5.1) |
|||||||
| |||||||
Multiple languages (VDI3603-5.2) |
|||||||
| |||||||
Parameters (VDI3603-5.3) |
|||||||
| |||||||
Ergonomics (VDI3603-5.4) |
|||||||
| |||||||
Archiving (VDI3603-5.5) |
|||||||
| |||||||
Core functions |
|||||||
Warehouse structure (VDI3603-6.1) |
|||||||
| |||||||
Master data management (VDI3603-6.2) |
|||||||
| |||||||
Inventory management (VDI3603-6.3) |
|||||||
| |||||||
Transport management (VDI3603-6.4) |
|||||||
In OpenWMS.org each business function is represented by one or more business services. Almost always it is a 1:1 mapping where a business function is implemented by one single business service - a software building block. Every software component is part of one the four domains: WMS, TMS, COMMON or CORE.
| Software Component | Domain | Purpose | License |
|---|---|---|---|
| Service Registry | CORE | Public (Apache License v2) | |
| Configuration Service | CORE | Stores technical application parameters at one place. It reads those configurations from a local filesystem or from a remote Git repository. This has the advantage that all configuration changes are tracked and audited. Beside technical configuration any kind of parameters can be handled. The Configuration Service provides the technical configuration to all other services. Changes are directly propagated to the affected services and do not require a restart of those. This increases maintainability and availability. | Public (Apache License v2) |
| Auth Service | CORE | Private (GPLv3) | |
| User Account and Administration Service (UAA) | CORE | Public (Apache License v2) | |
| Preferences Service | CORE | Stores parameters in a relational database. Those parameters are the nature of business relevant kind instead of technical purpose. A parameter can be specific to an user only, to a group of users (role), to a service or valid for the whole application. For example, the default language setting of an user in the user interface is stored as a user scope parameter, whereas all available languages might be stored as application parameter. | Public (Apache License v2) |
| Printing & Spooling Service | CORE | Provides an API to create documents (PDF) from a template (JasperReports) and print those documents. After creation it stores the document before sending to the printer. It offers retry mechanism and managing basic printer settings. A relational database, the filesystem or MongoDB (NoSQL) can be configured as document store. | Private (GPL) |
| Translation Service | CORE | Central translation service for i18n and l10n support. | Private Preview (Apache License v2) |
| COP Web User Interface (Administration and Control) | CORE | The COP web application is a single-page application (SPA) built with Angular technology. It offers a variety of management and overview screens for the warehouse functionality. | Private (GPL) |
| BOB Mobile App (for operation) | CORE | BOB is a native application built for IOS and Android mobile devices. It offers a narrowed set of business processes for the essential warehouse functions, like goods receiving, inventory movement, picking etc. | Private (GPL) |
| Common Base Service | COMMON | The OpenWMS.org Common Service provides essential functionality to deal with Locations, LocationGroups and TransportUnits. For a simple example, it offers an API to move a TransportUnit from a Location A to a Location B. Besides that, also other secondary resources exist, like TransportUnitTypes or rule sets to define what type of TransportUnit can be put on what type of Location. | Public (Apache License v2) |
| PLC & IPC Communication (Socket) | COMMON | This kind of driver uses plain vanilla TCP/IP socket communication between PLC and WCS. Arbitrary project-specific messages structures can be used on top of the socket connection. It supports simplex and duplex communication between both partners. One single instance can connect to multiple parters. | Public (Apache License v2) |
| PLC & IPC Communication (OPCUA) | COMMON | This driver is based on the OPCUA architecture to exchange data between WCS and PLC/IPC. It supports reading from and writing to server nodes and subscriptions. | Private Preview (Apache License v2) |
| Business Transactions & Audit Logging | COMMON | The Transactions Service captures business transactions and stores them in the database. A business transaction is some important operation that is performed as part of the warehouse processes. For example, moving inventory between locations or changing the inventory quantity. |
Private Preview (Apache License v2) |
| Common Tasks | COMMON | The Common Tasks Service is used to manage human user tasks in a warehouse. These general tasks are not specific to the WMS domain and can be used very flexible whereas WMS specific tasks (as part of the WMS Tasks Service) are more specific and can cover more detailed WMS information. | Public (Apache License v2) |
| Transport Order Management | TMS | This service allows to create TransportOrders in automated warehouses. A TransportOrder is used to transport TransportUnits (e.g. pallets, boxes, cartons) between warehouse locations. TransportOrders are created when a customer order is received or goods enter at the warehouse. | Public (Apache License v2) |
| Transport Order Routing | TMS | The TMS Routing service is used in automatic warehouses to route TransportUnits on conveyors through the warehouse. It is responsible to handle incoming messages and selects the appropriate workflow process that finally executes the handling. The workflow is a BPMN 2.0 compliant workflow, with extensions of the used workflow engine. | Public (Apache License v2) |
| Receiving & Goods-In | WMS | The purpose of the Receiving Service is to handle goods receipts. It offers an API to create incoming orders, so called Receiving Orders (expected receipts). These kind of receipts are announced before goods are received. In contrast, blind receipts are also supported. Those don't require any announcements and allow to capture goods that are not expected to receive. | Public (Apache License v2) |
| Inventory Management | WMS | Physical inventory and product definitions are managed in the Inventory Management. Plugins for putaway and allocation can be linked to this service. Putaway strategies are used to find proper warehouse locations for products. Allocation algorithms are used to allocate warehouse inventory for customer orders and manage inventory reservations. |
Community version: Private Preview (Apache License v2) Enterprise version: Private (GPLv3) |
| Picking | WMS | The Picking Library can be linked to the Inventory Service to provide the functionality and an API for picking. Currently two picking strategies are implemented: Pick & Collect and Pick & Pack. | Private (GPLv3) |
| Manual Movements | WMS | The Movements Service is mainly used in manual warehouses to plan movements for replenishment and stocktaking. | Public (Apache License v2) |
| WMS Tasks | WMS | Private Preview (Apache License v2) | |
| Partner Management (Suppliers & Carriers) | WMS | new module - under development. The purpose of the OpenWMS.org Partner Service is to manage all kind of partner companies, either they are in the role of a customer or a supplier or a manufacturer or any other kind. | Private Preview (Apache License v2) |
| Truck Management | WMS | Truck management is responsible to schedule trucks and cargo. It also manages drivers and assignments to trucks. | Private Preview (Apache License v2) |
| Packing | WMS | new module - under development | Private Preview (Apache License v2) |
| Shipping & Goods-Out | WMS | The Shipping Service is responsible to handle outgoing orders, those leaving the warehouse. In particular these kind of orders are typically customer orders, where each order has multiple order positions. The OpenWMS.org Shipping Service creates smaller chunks of each order position - the so-called splits. | Private Preview (Apache License v2) |
OpenWMS.org is a flexible and free-to-use warehouse management and material flow control system. It is flexible because it can be tailored depending on the required functionality in the actual customer project. If, for example, a warehouse management is not required, this functionality can simply be omitted. Even more fine-grained tailoring is possible. If a project does not require Picking processes, the OpenWMS.org WMS Picking Service can be omitted either.
OpenWMS.org is free-to-use because the sourcecode is available as opensource under the Apache 2.0 opensource license. Hence consultancies, integrators or any other development team can download the software as source or pre-built container images, customize it for the end-customer projects need and sell it with the project.
Because the software is free-to-use there is not a superficial business model for the authors. What is the business driver or goals of this project? The authors offer consulting, coaching as well as development services based on OpenWMS.org.
The driver for the authors is to make OpenWMS.org a widely used framework to build projects in the intralogistics domain with.
The business goal is to even extend this vision to make OpenWMS.org a disruptive software framework in the warehouse management and logistics software and to replace any other closed source warehouse management and material flow control software.
The most important functional requirements of the OpenWMS.org project.
The existing and upcoming functionality of OpenWMS.org is comprised by the issues tracked in GitHubs issue tracker. Notice, that most of the requirements target on particular microservices only and are therefor directly put in one microservice' issue trackers.
In a nutshell
In detail
An increasing list o quality goals…
| Domain | Priority | Characteristic | Scenario |
|---|---|---|---|
| WMS | High | Accountability | The system must durably store who triggered when what kind of critical operation |
| TMS | High | Performance | Requests that drive the material flow control, in OSIP a REQ, must be answered in less than 200ms |
| TMS | High | Availability | Critical processes in TMS must not depend on the availability of WMS processes |
| General | High | Performance | Processing orders in each domain must not affect the performance the processing in other domains |
| General | Medium | Reliability, Maintainability | Software changes must be applied within zero downtime of the whole system and may not affect unchanged parts of the system in terms of availability |
| General | Medium | Portability | The system must not depend on any runtime platform to make it portable across different runtime environments |
| General | Medium | Installability, Performance | Solutions built on top of OpenWMS.org can be split into parts that run on different platforms. Some parts may require to run close to hardware in order to fulfill performance requirements |
| General | Low | Extendability | The system must be built in order to be highly extendable in customer projects |
The OpenWMS.org system consists of warehouse management and material flow control components. It is embedded into an ecosystem with a typical enterprise resource planning system, field controllers, manufacturing control systems and other counterparts that exist in the area of eCommerce and intralogistics warehousing.
Figure 1. Business Context
| Component | Description |
|---|---|
| ERP | The enterprise resource planning system is in this context responsible to serve OpenWMS.org with any kind of logistic orders, like orders for Picking, Shipping, Receiving etc |
| MES | The manufacturing execution system is another IT system that is connected to OpenWMS.org and is responsible to orders into production lanes in the manufacturing industry |
| PLC | A system of programmable logic controllers is directly connected to the TMS part of OpenWMS.org and is controlling and driving the automation hardware in warehouses |
| Printing System | Printer hardware could be connected directly to OpenWMS.org or via print servers to print delivery slips, tracking notes and reports |
| Operational UI | An user interface for human operators serves processes for Picking, Clearing, Receipts |
| Administration UI | An user interface for system administrators to create reports, manage orders and workplaces etc. |
The technical context focuses on the technical details of the interfaces between OpenWMS.org and surrounding systems.
Figure 2. Technical Context
| Component | Description |
|---|---|
| SCM | The Supply Chain Management module of an ERP |
| DB | A database with tables used to transfer data |
| User Interfaces | A group of all user interfaces connected to OpenWMS.org |
| Relationship | Name | Description |
|---|---|---|
| SCM-WMS | transfer tables | Tables to receive and send information between the ERP and WMS |
| SCM-WMS | RFC | A RFC connection over tcp/ip is used to transfer information between the ERP and WMS |
| SCM-WMS | webservice | A webservice connection over http is used to transfer information between the ERP and WMS |
| User Interfaces-WMS | REST | User interfaces communicate via REST API with OpenWMS.org services |
| User Interfaces-WMS | Events | User interfaces may also communicate via websockets and receive events from OpenWMS.org |
| MES-TMS | webservice | MES may control the TMS services via http webservices |
| PLC-TMS | RFC | A PLC is usually connected via RFC over tcp/ip to the TMS services |
| Printing System-WMS | RFC | A printing system is addressed over tcp/ip to print reports from WMS |
This chapter lists all common design principles valid for all OpenWMS.org microservices. Each microservice implementation may add specific rules and principles but overall rules are captured here.
Description |
Originally coming from a more technically organized application layout, in 2016 the step was taken towards a microservice architecture. Each microservice focus on its own functional use cases, business functions and business value. The whole OpenWMS.org system encompasses around 15 different services that serve different purpose and that can be composed differently in end-customer projects. |
|---|---|
Justification |
OSGi was a great technology to built modular applications. One of the most important requirements on OpenWMS.org is the ability to extend in a flexible way and to get changes into production without downtime. The best successive architecture style after OSGi is te microservice style, it helps us to deploy changes easily and without downtime and to implement new features quickly. |
Addressing |
Modularity, Flexibility, Extendability, Changeability |
Description |
Each microservice has its own set of database tables, either relational database tables or NoSQL collections/tables. Tables are differentiated by a naming pattern. Each table has a prefix that points to the owning microservice. All tables can be deployed into a single database, or in multiple databases or database schemas. This is a question of distribution and what need to be deployed where. Relationships between database tables of different microservices are forbidden. |
|---|---|
Justification |
It is absolutely fine to use one database for all the tables used in a system. It may also make sense to separate between database schemas, for example to separate WMS tables from TMS or BPMN tables. With this approach we do not get in conflict with the strict data separation enforced in microservice architecture style and still have the flexibility of different deployment forms |
Addressing |
Flexibility, Performance |
Event-Driven Architecture (EDA) is a software design pattern where system components communicate and react to events (changes in state or significant actions) rather than making direct synchronous requests. Beside events (that express what happened in a system) there might also be commands, to express or request an action from a system component.
Nowadays there’re two major architecture categories:
Traditional messaging with an Enterprise Message Broker (eg. RabbitMQ, AmazonMQ, Azure Service Bus)
Log-based messaging with a (Distributed) Event-Streaming Platform (eg. Apache Kafka, AmazonMSK)
The most obvious difference is, that in tradtional messaging systems the message is removed from the broker as soon as it is consumed, whereas in event-streaming platforms the message can be replayed.
Basically three high-level architectural styles exist as explained in the following. The data sent between producer and consumer can be subdivided into two types: Thin events and Fat events. The former type only carries an unique data identifier and (if required) some metadata information (eg. type, date), whereas the latter is used to transfer the full data representation as part of the event.
In general in Queue and Pub/Sub architectural style it is preferred to deliver Thin events. All consumers must query the producer to get the actual data instead of getting the data delivered as part of the event.
Queue (Point-to-Point Messaging)A queue delivers each message to only one consumer. | |
|---|---|
Characteristics |
|
Example Use Cases |
|
Pub/SubIn pub/sub, a message is sent to all subscribed consumers. Every subscriber gets its own copy of the message | |
|---|---|
Characteristics |
|
Example Use Cases |
|
Both styles have the following characteristics:
Event contains minimal data (ID, type, date)
Requires synchronous follow-up calls (requires existing request interfaces, eg. REST API)
Lightweight messages
Coupling between services due to callbacks
Queue |
Pub/Sub |
|---|---|
|
Fat events hurt less here because:
Cons
|
Only Thin events proposed, because of
|
Events are stored in ordered event logs, that can be replayed by consumers repeatedly. Consumers mutate their datastores based on these events. In contract to the first architectural style, the event contains all required data (Fat event) so consumers do NOT need to call the producer subsequently.
Characteristics |
|
|---|---|
Example Use Cases |
|
The downside of this log-based messaging systems, compared to traditional message brokers are:
Complex to setup, operate and maintain
Querying a large amount of data on the event log is not efficient, compared to a local datastore or cache
Event Sourcing is even a step further. System state is derived from the event history. Events are the source of truth and consumer' datastores are not - those can be eliminated or serve as local temporary store only. In this type of architecture the event log is seen as the “central nervous system” of the application (or even the organization) that stores all data changes over the time in the event log.
Characteristics |
|
|---|---|
Example Use Cases |
|
Technical summary: Service Bus moves work. Event Hubs moves data.
|
Azure Service Bus Message Broker (Queue & Pub/Sub) |
Azure Event Hubs Event Streaming Platform |
|
|---|---|---|
Designed for |
|
|
When to take |
|
|
The one doesn’t replace the other. Here a direct feature comparison:
Feature |
Azure Service Bus |
Azure Event Hubs |
|---|---|---|
Primary Purpose |
Enterprise messaging |
Big data event ingestion |
Category |
Message Broker |
Event Streaming Platform |
Messaging Model |
Queue + Topic/Subscription |
Partitioned event stream (log) |
Typical Message Size |
Small/medium business messages |
High-volume telemetry/events |
Throughput |
Thousands/sec |
Millions/sec |
Latency |
Low |
Very low |
Retention |
Message removed after consumption |
Time-based retention (1–90 days or more with Capture) |
Replay Events |
❌ No |
✅ Yes |
Ordering |
FIFO via sessions |
Ordering per partition |
Delivery Guarantee |
At-least-once, exactly-once workflows possible |
At-least-once |
Transactions |
✅ Supported |
❌ Limited |
Dead Letter Queue |
✅ Built-in |
❌ Not native |
Message Locking |
✅ Yes |
❌ No |
Backpressure Handling |
Built-in |
Consumer-managed |
Scaling Model |
Broker-managed |
Partition-based horizontal scale |
Kafka Protocol Support |
❌ No |
✅ Yes (native Kafka API compatible) |
OpenWMS.org is made up microservices that communicate between each other in a variety of protocols either in synchronous or asynchronous style. A directed synchronous communication should always happen from north to south and west to east, whereas asynchronous events, commands and messages may be interchanged in all directions to get information populated across the whole system.
Figure 3. Callgraph and informationflow between the four OpenWMS.org domains
Contained Building Blocks
| Component | Responsibility |
|---|---|
| ERP | The external Enterprise Resource Planning system is the host that sends higher level warehouse orders. Not part of OpenWMS.org |
| WMS Domain | The Warehouse Management System is the layer that interacts with the ERP and implements all common warehouse processes, like Receiving, Shipping, Inventory, Picking etc. |
| TMS Domain | The Transport Management System implements the Material Flow Control (MFC) in automatic warehouses and also the processes used in manual warehouses |
| COMMON Domain | This layer contains all essential processes and services for every warehouse solution, no matter if automatic or manual warehouses, with or without a WMS layer. These services are always part of each customer project |
| CORE Domain | The CORE domain contains infrastructure components, that are not specific to warehouse projects and can also be used in other arbitrary projects. Most of these services are optional to use and depend on the deployment scenario |
| Subsystems | Subsystems do not belong to OpenWMS.org, but are communication partners like PLC (Programmable Logic Controllers), scale units, robots etc. |
Each of the OpenWMS.org services can be assigned to one of the four domains: CORE, COMMON, TMS or WMS. A more concrete view with seven often used microservices looks like below. Notice that the information flow is visualized not the dependency graph. WMS services are not directly called from services of the COMMON or TMS domain.
Figure 4. Informationflow between 7 OpenWMS.org microservices
| Component | Responsibility |
|---|---|
| COMMON Service |
Manages The COMMON Service does not know about other surrounding services but sends out information to its outer components |
| TMS Service | Tracks the movement of TransportUnits with TransportOrders in automatic warehouses. The service directly uses the interfaces of the COMMON Service |
| COMMON Driver | This is the TCP/IP driver component used to communicate to subsystems that implements the OSIP protocol. It forwards incoming messages to a routing component, like TMS Routing in an automatic warehouse |
| TMS Routing | This is a routing component used in automatic warehouses where actually the material flow control logic is defined. For this it uses many API of other services indirectly |
| WMS Inventory | This service is responsible to manage warehouse inventory, like Products and is mainly triggered by changes in other components |
| WMS Receiving | Warehouse receiving processes, like receipt announcements are implemented and processed here. It consumes events from other services and directly calls API of others |
| WMS Shipping | Contains warehouse shipping processes that trigger movements |
Lets have a closer look and look inside the microservices to see how they interact between each other. Only the asynchronous communication path is shown with the messaging components like Message Filters, Message Queues and routing information.
Figure 5. Insight of communication between the 7 OpenWMS.org microservices
Important to notice is the component who owns a shared resource. Basically queues are never shared and always kept by the service internally. But intermediary components like Routers or Filters (here RabbitMQ exchanges) need to be accessed from both communication partners, hence a clear ownership must be defined. As an example take the exchange with name common.tu.commands into account. This is owned by the COMMON Service and used as an inbox and outbox for command messages. Consumers interested in outgoing commands may subscribe a queue to this exchange to gets updates on cache evicts or to send commands to create data managed by the COMMON service. The command structure itself is also given by the COMMON service.
An OpenWMS.org microservice implementation with Java technology is structured into source packages. Some generic and often used packages exist in all microservices with a common sense. In the example below the structure underneath the common package is also applied to any other domain package like core, tms or wms.
Figure 6. Java package organisation
Beside these packages exist dozens of others specific to every microservice. The Transportation service for example has also a separation into functional packages, like transports or routes. The packages and conventions shown here should be followed for every OpenWMS.org microservice implementation when possible.
| Package | Responsibility |
|---|---|
| org.openwms | Top level product package |
| core | All types that belong to the CORE domain |
| common | All types that belong to the COMMON domain |
| tms | All types that belong to the TMS domain |
| wms | All types that belong to the WMS domain |
| api | Types that are relevant for client applications or API consumers and that are cut off into a separate build artifact |
| app | Types and configuration artifacts that are responsible for the microservice lifecyle |
| commands | Contains all command types and handlers, processors of commands |
| config | Contains configuration classes responsible for the microservice external configuration, most often Spring Boot managed classes mapped to properties files |
| events | All event types used service internally and externally. External events must put under api.events |
| impl | The internal implementation details of the microservice API |
Most often a warehouse management system and material flow control are installed in a datacenter close to the warehouse and the hardware, either on a single server or on multiple ones. This on-premise deployment has the advantage of increased maintainability, operability and throughput.
The simplest way to deploy OpenWMS.org is the Single Box Deployment. All software components installed on one single physical or virtual server. All processes run and communicate in-memory and do not require external network access. In its simplest way all microservices can be installed as single instances as Unix daemons or MS Windows services. Scalability and elasticity are not an option in a typical warehouse project, therefore there is no need to scale out processes on demand.
Figure 7. Simplest deployment on single server
The services could also be installed as Docker containers on Docker Compose to increase operability and reliability but this is not a requirement.
| Motivation |
|
|---|---|
| Quality and/or Performance Features |
|
Mapping of Building Blocks to Infrastructure
| Component | Responsibility |
|---|---|
| Server | Either one physical or virtual server instance for the whole warehouse project |
| RabbitMQ Server | A RabbitMQ installation, running as a OS service |
| Virtual Host | Inside the RabbitMQ server a virtual host instance is created for the warehouse project |
| Database Server | A database server installation |
| Database Instance | A instance dedicated for the warehouse project, that contains all schemas and tables (the feature of schema is optional) |
| Microservice Network | A logical group of microservices. Has no |
Similar to the one-box deployment, OpenWMS.org can also be split and deployed on multiple machines. For this scenario we propose to run the microservices within Docker containers and let the container scheduling infrastructure distribute the instances as needed. For this scenario we also propose Docker Swarm as container scheduling runtime. But if customers have other schedulers in place, like Kubernetes or OpenShift this works the same way. The main point here is, that OpenWMS.org does not require to run on Kubernetes or any PaaS solution. The basic requirements of this scenario are met with Docker Swarm.
Figure 8. Deployment distributed on multiple servers
The benefit of a container scheduler in a distributed environment is tremendous. We do not need to care about low-level infrastructure details on our own and rely on proven scheduling technologies.
| Motivation |
A container scheduler is already in place A robust and more reliable setup is required, where process restarts and monitoring is required Load can be divided to multiple servers Processed can run at multiple instances One part of the system (e.g. TMS) is independent on the availability of other parts |
|---|---|
| Quality and/or Performance Features |
High degree of latency High level of reliability and failure tolerance High level of operability Advantages with multiple warehouses in the project A distributed system is more complex and this is a decrease of maintainability |
Mapping of Building Blocks to Infrastructure
In addition to the Single Box Deployment.
| Component | Responsibility |
|---|---|
| Server 1 | One managed Docker Swarm node (could also be a Kubernetes node) |
| Server 2 | A second managed Docker Swarm node (could also be a Kubernetes node) |
Both Docker Swarm nodes use several ports for cluster and container management. The database and the RabbitMQ are made transparently available to both nodes and microservices on each node.
For example all services on Azure Kubernetes Services (AKS) on the Microsoft Azure Cloud:
Figure 10. Deployment distributed on-premise and cloud
Communication between microservices is essential. Likewise the communication format, the protocol, is essential and absolutely required to use a well-defined structure that both communication partners, provider and consumer, understand.
Three kind of communication formats must be considered.
The actual transferred business payload between two services is the common case of microservice communication. The service provider is in the role of upstream service and feeds all consuming services (aka. downstream services). Hence the provider narrows the protocol and data structures that are possible for the communication. But the consumer may choose between options, like different representation formats (JSON, XML, vendor specific, etc.) or transport protocols.
Figure 11. Information Flow
The consumer is responsible to transform the received structures into it’s own internal used data structures. This often happens in a so-called ACL (Anti Corruption layer).
Beside the actual business payload context relevant information must be transmitted as part of each microservice communication. For example this is relevant to trace calls within a call chain and particular to support operationability and monitoring of a distributed system.
This contextual data is not specific to each microservice, like information data is, but has to follow a common structure that is applied in every microservice implementation. Each microservice acts as consumer and additional as producer, respectively enricher, of context data. Finally the transferred information must be sufficient to feed external monitoring systems with.
Context data of OpenWMS microservices is transferred in headers, either HTTP headers or RabbitMQ headers.
For example: When OpenWMS.org microservices are deployed in a multi-tenant environment the tenant identifier is required with each call. This information is typical context data that needs to be transferred between client and server. For a HTTP call the tenant identifier is propagated as custom HTTP header X-Tenant and for asynchronous AQMP messages the AMQP header osip_tenant transfers this information.
In failure situations a microservice might not respond with self-defined information data structure but with failure or error responses. The structure and the protocol of these error responses must be consist across all microservices in order to establish communication even in case of errors. As an example: It is not an option that Service A returns an error response to Service B that cannot be processed by Service B because the protocol or format is unknown to Service B.
With OpenWMS we use Spring Boot as implementation framework. Spring Boot already comes with it’s own format like it exposes errors to API consumers. For REST API over HTTP a typical Spring Boot error response looks like this:
{
"timestamp": 1568208641323,
"status": 500,
"error": "Internal Server Error",
"exception": "org.ameba.oauth2.InvalidTokenException",
"message": "JWT expired at 2019-09-11T13:30:41Z. Current time: 2019-09-11T13:43:27Z",
"path": "/transport-units/S000005270119225"
}
This works well with Spring Boot consumers on the client side. Even clients in JavaScript can deal with this format. The problem here is, that the format is baked into and driven by Spring Boot and may change between mayor framework releases.
Therefor a RFC was initiated, the Problem Details for HTTP APIs (RFC7807). By applying this RFC to all OpenWMS microservice implementations we ensure that we align to a standardized but flexible and extendable format.
{
"type": "https://api.openwms.dev/meta/v1/error/AlreadyExistsException",
"title": "AlreadyExistsException",
"status": 500,
"detail": "The TransportUnit with Barcode [S000005270119225] already exists and cannot be created",
"messageKey": "E0033",
"messageLanguage": "en-US",
"messageCategory": "ERROR",
"args": ["S000005270119225"],
"call": {
"headers": {
"x-span-id": "2485ec27856c56f4",
"x-trace-id": "1210be13194bfe5"
},
"uri": "https://api.openwms.dev/v1/transport-units/S000005270119225",
"path": "/transport-units/S000005270119225",
"method": "POST"
}
"timestamp": 1568208641323,
"error": "Internal Server Error",
"exception": "org.ameba.oauth2.InvalidTokenException",
"message": "JWT expired at 2019-09-11T13:30:41Z. Current time: 2019-09-11T13:43:27Z"
}
In a distributed system we must ensure that failure situations across the network are handled properly without impacting the robustness nor performance of the whole system negatively. A technical error handling can often be done with libraries, like Hystrix, resilience4j, feign or others. Also Spring AMQP cares about and offers different strategies to deal with errors. But it is not always possible to handle failures in one standardized and automated way. At least, we need to decide what failure resolution strategy from one of those libraries is chosen in case of an error.
Spring AMQP with RabbitMQ is used for the asynchronous messaging infrastructure. A publisher sends a message to an AMQP Exchange where a consumer process has connected a AMQP Queue to. A message is sent to the Exchange and published to one or more connected Queues. If a consumer fails to process the queued message there are several possibilities how to react: Re-Queue, Throw-Away or Forward.
Re-Queuing failed Messages In case of re-queueing the message is left in the queue and executed again. How often this happens and with what interval can be configured.
Throw-Away failed Messages Simply omit the error occurred and be silent could also be a consumer strategy. In this case the message is acknowledged to RabbitMQ and is gone from the queue - but without handling the message as demanded.
Forward failed Messages A consumer could also decided to forward a message it could not process to someone else or to just forward it into a message store without knowing about the consumers of that store - there might actually be no consumer. This pattern in basically called Dead-Letter Queueing and also RabbitMQ offers this technique.
The list of strategies is by for sure not complete but enough for what we need in OpenWMS.org. So here is a short comparison of these three strategies in relation to the use in OpenWMS.org:
| Strategy | Advantage | Disadvantage |
|---|---|---|
| Re-Queueing | The message is not lost. It stays in the queue even after the consumer gets updated or restarted. | May slow down the system cause of repetition. Blocks other messages in the queue from being processed |
| Throw-Away | Processing other messages goes on, the queue is not blocked | The thrown away message is lost but could have been worth to know for consumers. Throwing it away may lead to data inconsistencies |
| Forward | Processing other messages goes on, the queue is not blocked, the message is not lost | The process of handling this kind of failed messages must be defined and handling them must be established. May lead to problems when a message sequence is expected (e.g. OSIP UPDX telegrams must be processed and acknowledged before any other telegram can take place) |
So when to use what kind of strategy and what configuration to apply is often a decision specific to the use case.
Strategies to re-queue Messages
By default, Spring AMQP does not acknowledge a message to RabbitMQ that has failed to process. That means a listener is called over and over again. This behaviour can simply be stopped by throwing an org.springframework.amqp.AmqpRejectAndDontRequeueException within the application listener code like it is shown in Strategies to Throw-Away Messages.
But with Spring AMQP the handling is also configurable: How often a message is re-queued and the repeat interval with many other options. …
A typical Spring AMQP RabbitTemplate configuration of a service looks like:
@Bean
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
var rabbitTemplate = new RabbitTemplate(connectionFactory);
var backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setMultiplier(2);
backOffPolicy.setMaxInterval(15000);
backOffPolicy.setInitialInterval(500);
var retryTemplate = new RetryTemplate();
retryTemplate.setBackOffPolicy(backOffPolicy);
rabbitTemplate.setRetryTemplate(retryTemplate);
rabbitTemplate.setMessageConverter(jsonConverter());
return rabbitTemplate;
}
Use Cases This is often done in use cases where the consumer expects the message is going to be repeated by the sender after a period of time.
Strategies to Throw-Away Messages
Use Cases For sure we can throw away messages, silently acknowledge them, where we know the publisher expects no answer and repeats the message in case no answer is returned. So we depend on the frequency of the publisher how often the message is repeated. For example, the publisher may be a PLC that is configured to resend the initial message request every 150 milliseconds. In this case, this could have an impact on the overall performance. Whereas an PLC may also be configured to repeat the message only after e.g. 30 seconds what has not that much impact on performance.
Configuration A typical message listener consuming the message and throws it away on any exceptions looks like this:
@RabbitListener(queues = "${owms.commands.common.tu.queue-name}")
public void onCommand(@Payload TUCommand command) {
try {
handler.handle(command);
} catch (Exception ex) {
throw new AmqpRejectAndDontRequeueException(ex.getMessage(), ex);
}
}
The listener delegates to a message handler that is responsible to execute the business logic. In case of any errors the listener throws an AmqpRejectAndDontRequeueException to signal the Spring AMQP runtime to reject the message. So it is not processed anymore.
Strategies to Forward Messages
The use of java.util.Optional within API methods makes it easy to translate 404-Not Found into Optional.empty(). Without this pretty cool Spring & Jackson feature we must live with an null response object, that you might have seen at some point. Not the instance is null, but all fields of the instance are, because the sender (a Spring Boot app) sends a JSON response with http status == 404 but passes a ResponseBody, and that one is tried to be deserialised into a business object, therefor all fields of the business object are null.
Apache Maven build tool knows about a dependencies section and a dependencyManagement section.
Rule We declare dependencies with their version and classifiers but without scope within the dependencyManagement section and actually define the usage of the dependency within the dependencies section without the version but with the proper scope. It is not allowed to declare and define the usage in the dependencies section only.
Issue As we do with JSON properties in [D2] Use JsonProperty explicit we should also be explicit and name every database column. Without doing so we could run into trouble when we refactor field names but don’t want the columns to be renamed.
Rule Instead of ending with a mix of annotated and un-annotated fields, all the fields that are going to be mapped onto database columns, need to be annotated with @Column and a name must be set. The name must have the prefix C_ assigned. This is very handy to avoid cases where the Java names clashes with database keywords. For example: One could name a field user that ends in a column USER that is a keyword in RDBMS and forbidden to use. To avoid such clashes all columns have the C_ prefix.
Issue To simply define a Logger instance just use a static definition and don’t add some magic third party library like Project Lombok.
Example
private static final Logger LOGGER = LoggerFactory.getLogger(MyClazz.class);
This is simple, added fast and just uses Java and the logging library of choice. This line of code can be created with an IntelliJ live template dlog (define logger).
Convention - Always use slf4j as log API - Logger instances must be private to the class and static - Logger instances in OpenWMS.org code must be named as LOGGER
Issue In OpenWMS.org we have at least two or more domain models that needs to be mapped from one into the other. The inner business domain model of a microservice is often also the persistence model and thats fine. But outer public API have their own representation, i.e. domain model, like the VO model of the presentation of the MO model of the messaging layer. We could map in bidirectional way that makes often sense but sometime it only make sense to make in one direction.
Problem Our mapping library support the configuration of the mapping direction on attribute level but only with two options: bi-directional or one-way. The latter means from source (class-a) to destination (class-b). A explicit switch, like a-b or b-a does not exist.
Solution So we define that we always name the inner model as class-a and the outer model as class-b. By this convention it is clearly defined that we map between the rich domain model to the more simple domain model without object-graph resolution, just by traversing the source object and dereference the attribute of the destination class.
Example Here an correct example of a mapping from the inner class to the outer class:
<mapping>
<class-a>org.openwms...ReceivingOrderPosition</class-a>
<class-b>org.openwms...ReceivingOrderPositionVO</class-b>
<field type="one-way">
<a is-accessible="true">product.sku</a>
<b is-accessible="true">sku</b>
</field>
</mapping>
Issue Out of the box a FeignClient does not support HTTP PATCH requests.
Solution To get PATCH work with Feign another Maven dependencies needs to be added:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.4.0</version>
</dependency>
The correct version mut be used, that works with OpenFeign.
For better maintenance use clear and unique names for unique constraints and foreign keys:
@UniqueConstraint(name = "UC_LOC_ID", ...)
@ForeignKey(name = "FK_LOC_ACC")
Unique constraints start with UC, then the abbreviation for the hosting table and an identifier. Foreign keys start with FK, then the abbreviation for the hosting table and the referenced table/column.
Example:
@Entity
@Table(name = Location.TABLE)
class Location {
public static final String TABLE = "COM_LOCATION";
}
In case of refactoring the table name change can be done at one single place. In native queries the constant can be referenced instead of a String.
In classes serialized as JSON use @JsonProperty on properties explicitly. This ensures that refactoring and renaming the fields keeps the JSON names and the API stable.
Issue Java Exceptions should be translated into proper representations at the system boundary. @RestController and @Controller classes must translate exceptions into proper JSON structures (XML is not in focus).
Solution In order to implement Spring @ExceptionHandler or ControllerAdvice in every controller take advantage of a set of those by extending the class org.openwms.core.http.AbstractWebController. Own exception classes and those of the ameba-lib library are translated properly.
Issue To find performance and throughput bottlenecks it is a good start to measure the time execution of methods and persist this information so it can be monitored over time.
Solution This kind of logging is addressed with Technical Service Logging (TSL). A particular log appender is used to capture the MEASURED logs and forward to a dedicated logfile with the file extension .tslog.
TSL can be turned on by setting the log level of the MEASURED category to INFO or higher. The logs must be send to a predefined appender with name TSL:
<logger name="MEASURED" level="OFF">
<appender-ref ref="TSL"/>
</logger>
Like all other logs also the TSL is tenant-aware and a log file is written for each tenant. The log looks like:
spGZLZ COMMON-Service 2019-05-03 11:43:30.080 DEBUG [35m[63638/4] [0;39m [2m- [0;39m [SERVICE_LAYER_ACCESS ] : [S]>> Method call: execution(TransportUnitServiceImpl.create(..))
spGZLZ COMMON-Service 2019-05-03 11:43:45.098 DEBUG [35m[63638/1] [0;39m [2m- [0;39m [SERVICE_LAYER_ACCESS ] : [S]<< execution(TransportUnitServiceImpl.create(..)) took [16] (ms)
If you want to see the TSL on the console instead then just refer to STDOUT instead of TSL:
<logger name="MEASURED" level="INFO">
<appender-ref ref="TSL"/>
<appender-ref ref="STDOUT"/>
</logger>
Problem Incoming http requests with URL parameters or request body data aren’t encoded correctly - even the request is decoded right. For example a request parameter that is decoded to %E6%8C could be encoded to æŒ but not to the expected String.
Solution By default, Tomcat uses ISO-8859-1 character encoding. The HTTP Connector in Tomcat’s server configuration must be configured to set the URL encoding to UTF-8. Open the server’s server.xml, search for the http connector and add the attribute "URIEncoding" as follows:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>
This Developer Guide contains best practises and rules useful to know when developing applications, services and projects with OpenWMS.org. It is written in a notebook style, extended during development and does not claim to be complete or even finished.
The guide is written for developers that are maintaining, extending or studying the OpenWMS.org codebase or build own services on top of the codebase.
A progressive fragmentation of client devices and the evolution of API driven by new product features is the driver for a proper API lifecycle management. Consumers may not adopt to API changes that quickly and stick on a particular API as long as possible or manage an upgrade as part of their own application lifecycle.
Client applications of OpenWMS.org API are not fragmented in that degree we see the fragmentation with mobile devices but also in projects of the logistics domain we deal with arbitrary devices across varying customers. In addition to that software in production is updated rarely.
One approach to deal with these challenges is to always be backward compatible and never break existing API consumer code. Stripe.com does an excellent job in API cultivation & care and explains how this could work in a fantastic blog article. Nevertheless managing and taking care of many clients is complex and may blow up the API.
Another much more simpler and often used approach is to version the API in that the URI to an API resource contains (or is tagged with) a version identifier (e.g. v1). Precisely two URI that contain a different version identifier are not the same and do not point to the same resource.
If we decide to version our API resources and introduce a version identifier as part of the URI, we also need to question who takes responsibility of managing the API lifecycle for each microservice. Does the development team decide when breaking changes are introduced and a new version of an API is raised? Or does an overall product management coordinate and manage major API changes and defined the public product API?
Like often the answer is: It depends. For OpenWMS.org microservice API, the development team is responsible for the API lifecycle whereas a manager role focuses, manages and communicates the public API to particular parties. Several public exposed API may exist in an omni-channel architecture where each API is a composition or a new formation of existing microservice API. In this case, a management role is in charge to communicate upcoming changes and consider client requirements and feedback.
In general, the team that maintains a microservice is responsible for the API management of that service. But some overall principles exist that should be followed across all microservice API.
Maintain at least two active API versions. The newer version is the preferred version to use and the target for migration. The older version may be flagged as deprecated
Always strive for API evolution and avoid breaking changes. This is a trade-off and must be in balance with cost, efficiency and simplicity
A version identifier can be introduced in at least two different ways. Popular approaches have the version identifier as part of the query path or as separate HTTP header argument (constraint: REST over http).
The version identifier is either part of the HTTP query path or the authority.
Examples:
Query Path: https://api.foo.bar/v1/cards
Authority: https://v1.api.foo.bar/cards
For microservice API we prefer to use the version identifier as part of the query path
The version identifier starts with a 'v' character followed by an integer starting with 1
This is more convenient for a system of microservices that does not depend on hostnames nor authorities. The identifier as part of the query path determines the corresponding resource no matter where the service is hosted on.
OpenWMS.org microservices have the API version identifier as part of the HTTP query path, as a prefix to the primary resource identifier
For public API, or those with a product character (AaaP - API as a Product), the version may be introduced as part of the authority. In particular for API that are built as product and for the purpose of different channels this is often used. The diagram below shows one part of the current OpenWMS.org API.
In contrast to use a fixed version identifier as part of the URI it is also possible to encode the version or respectively the representation type within the HTTP Accept Header. A client requests the server for a specific resource in a particular representation format. That means the same URI could be requested with different representation formats to get different representations of the same resource.
In this example a Client asks for a representation of a TransportUnit of type application/vnd.openwms.transport-unit-v1+json. The server knows this format and sends back a JSON representation of that particular version.
A valid custom representation format has the following naming pattern:
application/vnd.<product>.<resource>-<version identifier>+(json|xml)
| application | Standardized prefix |
| vnd | Abbreviation that signals the vendor tree |
| <product> | Custom product identifier |
| <version-identifier> | Prefix “v” with increasing integer value |
| (json|xml) | Either JSON or XML response format requested |
all other symbols are fixed characters.
The approach to encode the requested version into the Accept header is rather the exceptional case and is only used in API that match the same query path but different representation formats are supported.
Public API
Internal API
Get help and support
https://www.cipherlab.com/en/product-274444/Industrial-Mobile-Computer-RK95-Series.html
Item |
RK95 |
RK95 Cold Storage |
|---|---|---|
OS |
Android 9 / Android 12 (GMS, AER certified with one later version upgradeable capability) |
|
CPU |
Octa-core over 2.2 GHz |
|
Memory |
64GB Flash / 4GB RAM |
|
Expansion |
Micro SD Slot With SDHC (up to 32 GB) and SDXC (up to 2TB) Support |
|
Operating power |
Standard: Rechargeable 3.7V, 3000 mAh / 6000 mAh Li-ion Battery |
|
Working hours1 |
Standard: Min. 8 working hours (3000 mAh), Min. 12 working hours (6000 mAh) |
|
Wireless communication |
WLAN: IEEE 802.11 a/b/g/n/ac/e/d/h/i/r/k/v/w, IPv4, IPv6, 2x2 MU-MIMO |
|
Barcode scanning |
Standard: Laser / 2D Imager/ Mid-Range 2D Imager / Extended-Range 2D imager |
|
Camera (Optional) |
Autofocus 13 Megapixels with LED flash |
|
Display |
4.3" WVGA color display, Capacity Touch supports bare/wet/gloved finger and stylus inputs, Corning Gorilla Glass |
|
Keypad |
28-Key Numeric, 38-Key Numeric/Function keypads, 52-Key Alphanumeric keypads, Terminal Emulation keypads (TN5250, TN3270 and VT) , with LED backlight |
|
Size |
216 mm x 80 mm x 30 mm |
|
Weight |
400 g (3000 mAh) / 445 g (6000 mAh), with 2D imager |
|
Operating Temperature |
Standard: -20°C to 50°C / -4°F to 122°F |
|
Impact resistance |
IP65 / 1000 (1 m) Tumbles |
|
The native mobile application is build for Android and IOS devices.
Delete unassigned shards (https://www.cyberithub.com/how-to-delete-elasticsearch-unassigned-shards/):
curl -XGET http://localhost:9200/_cat/shards | grep UNASSIGNED | awk {'print $1'} | xargs -i curl -XDELETE "http://localhost:9200/{}"
-p.jpg)
https://www.thebarcodewarehouse.co.uk/shop/zebra/mobile-computers/MC930P-GSFDG4RW/
https://www.shop-zebra.de/zebra-tc72-tc77
