DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • WebSocket vs. Server-Sent Events: Choosing the Best Real-Time Communication Protocol
  • Reactive Event Streaming Architecture With Kafka, Redis Streams, Spring Boot, and HTTP Server-Sent Events (SSE)
  • Competing Consumers With Spring Boot and Hazelcast
  • Reactive Kafka With Streaming in Spring Boot

Trending

  • A Guide to Container Runtimes
  • Building Scalable and Resilient Data Pipelines With Apache Airflow
  • Google Cloud Document AI Basics
  • Unlocking AI Coding Assistants Part 4: Generate Spring Boot Application
  1. DZone
  2. Coding
  3. Frameworks
  4. Beyond Linguistics: Real-Time Domain Event Mapping with WebSocket and Spring Boot

Beyond Linguistics: Real-Time Domain Event Mapping with WebSocket and Spring Boot

Build a scalable real-time notification system using Sprint Boot and WebSocket, focusing on domain event mapping, system design, and more.

By 
Soham Sengupta user avatar
Soham Sengupta
·
May. 06, 25 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
1.2K Views

Join the DZone community and get the full member experience.

Join For Free

By definition, a markup language is a system used for annotating a document in a way that is syntactically distinguishable from the text. Essentially, it provides a way to structure and format text using tags or symbols that are embedded within the content. Markup languages are used to define elements within a document, such as headings, paragraphs, lists, links, and images. Hype Text Markup Language (HTML) is the most common of them. There are many other such as XML, SGML, Markdown, MathML, BBCode, to name a few.

This article articulates the need of and presents a minimally working version to what the term “domain markup event mapping” is conferred. Lest an unfamiliar terminology introduced abruptly make the audience assume otherwise, let us illustrate the experience as a real-time commentary of an event, say a cricket match on popular online news media. ESPN, Yahoo cricket, Cricbuzz.com, Star Sports, and BBC are among the top players in this area. I remember how they used to be 15 years ago and and now, they've evolved to cater real-time updates. With advanced backend systems, communication protocols, better design approach and of course, modern browser technologies, they have always been on the top to provide their users the best intuitive updates compensating the absence of audio and video.  

Our audience must have noticed that Google and other websites have already adopted animated and visually intuitive UI components to make the user experience better.

This article focuses on the need to provide a domain specific markup event mapper for all such use cases and illustrates an approach to create a minimalistic update system using Spring boot and WebSocket.

Domain Specific Markup Event Mapper: 


An image showing Domain Specific Markup Event Mapper.


The client (such as the web browser) receives an event. For the sake of generality, neither the communication protocol nor message format (e.g., whether it is a text or binary message) is assumed. The message converter yields an event object that the client understands, such as a JSON object that the browser side script knows how to handle and render. We now must agree that not all notifications and the events they carry, belong to the same category and therefore, be rendered in the same way. 

A running commentary, for example, may be rendered like a scrolling hypertext while a boundary or an over boundary or a fall of a wicket might require special effect as they’re rendered, to stand distinct. The role of the markup look-up engine is to identify a suitable engine given the category of an event. It delegates the event to a specific rendering strategy if one for the category is registered (known) to the system (client-side UI such as a browser). If none is found, there needs to be a fallback strategy. The four components that appear black in the image above are abstractions that the domain landscape must provide as we propose in this article. The rendering techniques for a cricket match must differ from a soccer match and coverage of an electoral poll must be presented in a different way than sports.

We must now wear the developers’ hat and gear up to put the theory into practice. We aim to make the notification system minimalist with the following:

  • A media operator section that posts updates.
  • The intermediary backend that sends the notifications.
    • For simplicity, we will not use any broker or third party cloud messaging system.
    • We've chosen vanilla WebSocket as mode of communication although other approaches such as periodical long polls, server-sent-event, SockJS can be used with their respective pros and cons.
  • The viewers’ section to consume (experience) the notification.

We create a spring boot application with spring-boot-starter-websocket and spring-boot-starter-thymeleaf.  


XML
 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.4.1</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.soham.demo</groupId>
	<artifactId>visually-appealing-realtime-update</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>visually-appealing-realtime-update</name>
	<description>visually-appealing-realtime-update</description>
	<url />
	<licenses>
		<license />
	</licenses>
	<developers>
		<developer />
	</developers>
	<scm>
		<connection />
		<developerConnection />
		<tag />
		<url />
	</scm>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>

			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>

		</dependency>
	</dependencies>

	<build>
		<finalName>${project.artifactId}</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<annotationProcessorPaths>
						<path>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</path>
					</annotationProcessorPaths>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>


We expose a WebSocket end point where clients can connect to establish a WebSocket session. 

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13


The server responds with a status code 101 (Switching Protocols):

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=


We expose an open (non-restricted ) endpoint "score" for clients to connect and configure it to allow traffic from anywhere. This is just for illustration and is not suitable on production grade.

Java
 
@Configuration
@EnableWebSocket
public class CricketScoreWebSocketConfig implements WebSocketConfigurer {

	private static final String PATH_SCORE = "score";

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(new TextNotificationHandler(), PATH_SCORE).setAllowedOrigins("*");
	}

}


To pour in minimal effort, assuming that there will be exactly one bulletin entry operator,  we'll create the class TextNotificationHandler.

Java
 
@Slf4j
public class TextNotificationHandler extends TextWebSocketHandler {
	
	
	private Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());

	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		log.debug("afterConnectionEstablished :: session established remote host: {}",session.getRemoteAddress());
		sessions.add(session);
		log.debug("afterConnectionEstablished :: connection from: {} is added. Current Open session count : {}",session.getRemoteAddress(),sessions.size());
	}

	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		for (WebSocketSession webSocketSession : sessions) {
			if (webSocketSession.isOpen()) {
				webSocketSession.sendMessage(message);
			}
		}
	}

	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		log.debug("afterConnectionEstablished :: session closed remote host: {}",session.getRemoteAddress());
		sessions.remove(session);
		log.debug("afterConnectionEstablished :: connection from: {} is removed. Current Open session count : {}",session.getRemoteAddress(),sessions.size());
	}
}


Now, we create the two HTML files under src/resource/templates. 

HTML
 
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Media Operator For XYZ Television Cricket Match IND vs ENG</title>
	<style>
		body {
			margin: 0;
			height: 100%;
			width: 100%;
		}

		canvas {
			display: block;
		}

		.container {
			display: flex;
			height: 100vh;
			width: 100%;
		}

		.left-section {

			background-color: lightblue;
			width: 80%;
			padding: 20px;
			box-sizing: border-box;

		}

		.right-section {
			width: 20%;
			background-color: lightcoral;
			padding: 20px;
			box-sizing: border-box;
		}
	</style>
</head>

<body>
	<div class="container">
		<div class="left-section">
			<canvas id="cricketField"></canvas>
		</div>
		<div class="right-section">
			<textarea rows="10" cols="50" id="tb_Comment" placeholder="Message here"></textarea>
			<button onclick="sendTextMsg()">Send</button>
			<fieldset>
				<legend>Quick Panel</legend>
				<button onclick="sendToastMsg('boundary')">Boundary</button>
				<button onclick="sendToastMsg('over-boundary')">Over-boundary</button>
				<button onclick="sendToastMsg('out')">OUT!!</button>
				<button onclick="sendToastMsg('100')">100* NOT OUT</button>
			</fieldset>

		</div>
	</div>
	<script>
		let socket;
		let dto;
		window.onload = function () {
			socket = new WebSocket("ws://"+window.location.host+"/score");
			socket.onmessage = function (event) {
				console.log(event);
			};

		};


		const canvas = document.getElementById('cricketField');
		const ctx = canvas.getContext('2d');

		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight;

		function drawCricketField() {
			ctx.fillStyle = 'green';
			ctx.fillRect(0, 0, canvas.width, canvas.height);

			const centerX = canvas.width / 2;
			const centerY = canvas.height / 2;
			const fieldWidth = 600;
			const fieldHeight = 400;

			// Draw the oval cricket field
			ctx.fillStyle = 'lightgreen';
			ctx.beginPath();
			ctx.ellipse(centerX, centerY, fieldWidth / 2, fieldHeight / 2, 0, 0, Math.PI * 2);
			ctx.fill();

			// Draw pitch and creases
			ctx.fillStyle = 'white';
			ctx.fillRect(centerX - 3, centerY - 150, 6, 300); // Pitch
			ctx.fillRect(centerX - 150, centerY - 3, 300, 6); // Crease

			// Draw stumps
			ctx.fillRect(centerX - 6, centerY - 160, 4, 20);
			ctx.fillRect(centerX - 2, centerY - 160, 4, 20);
			ctx.fillRect(centerX + 2, centerY - 160, 4, 20);

			ctx.fillRect(centerX - 6, centerY + 140, 4, 20);
			ctx.fillRect(centerX - 2, centerY + 140, 4, 20);
			ctx.fillRect(centerX + 2, centerY + 140, 4, 20);
		}

		let drawing = false;
		let startX = 0;
		let startY = 0;
		let currentX = 0;
		let currentY = 0;

		canvas.addEventListener('mousedown', (e) => {
			const rect = canvas.getBoundingClientRect();
			const mouseX = e.clientX - rect.left;
			const mouseY = e.clientY - rect.top;
			// console.log(mouseX+" " +mouseY+ctx.isPointInPath(mouseX, mouseY));
			//  if (ctx.isPointInPath(mouseX, mouseY)) {
			drawing = true;
			startX = mouseX;
			startY = mouseY;
			currentX = mouseX;
			currentY = mouseY;
			// }
		});

		canvas.addEventListener('mousemove', (e) => {
			if (drawing) {
				const rect = canvas.getBoundingClientRect();
				const mouseX = e.clientX - rect.left;
				const mouseY = e.clientY - rect.top;
				currentX = mouseX;
				currentY = mouseY;
				clearCanvas();
				drawLine(startX, startY, currentX, currentY);
			}
		});

		canvas.addEventListener('mouseup', () => {
			drawing = false;
		});

		canvas.addEventListener('mouseout', () => {
			drawing = false;
		});

		function clearCanvas() {
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			drawCricketField();
		}

		function drawLine(startX, startY, endX, endY) {
			ctx.beginPath();
			ctx.moveTo(startX, startY);
			ctx.lineTo(endX, endY);
			ctx.strokeStyle = 'red';
			ctx.lineWidth = 5;  // Increase the line width for better visibility
			ctx.stroke();
			ctx.closePath();
			dto = {};


			dto.startX = startX;
			dto.startY = startY;
			dto.endX = endX;
			dto.endY = endY;
			sendMessage("VISUAL", dto);
		}

		function sendMessage(strType, dto) {

			dto.id = Date.now();
			dto.type = strType;
			socket.send(JSON.stringify(dto));
		}
		function sendTextMsg() {
			dto = {};
			dto.message = document.getElementById("tb_Comment").value;

			sendMessage("TEXT", dto);
		}
		function sendToastMsg(msg) {
			dto = {};
			dto.message = msg;

			sendMessage("TOAST", dto);
		}
		drawCricketField();
	</script>
</body>

</html>




The full source code is available here .  You can also run the application from Docker hub by this command using the target port that you prefer.

Dockerfile
 
docker run -p 9876:9876 sohamsg/dockerhub:websocket-cricket-match-commetary 


However, the proposition the author made at the very beginning can now be revisited to understand its usage and need. Currently, the code is written to cater to selected specific use cases considering a cricket match. However, all these codes are created by individual teams/developers, though they were targeting the same thing of course, in different ways and USPs. To help visualize the components, let us take this enum, which is used in the mark up classifier below:

Java
 
public enum EvtType {

	VISUAL,TEXT,TOAST , // Keep adding your event types for another domain
}
class MarkUpClassifierService{
   
  public Optional<EvtType> classifyMessagge(AbstractMessage message){
       return classifierStgragey.apply(message);
   }
  
   /**
     Define your strategy to extract the category of the message.
     return empty Optional unless a category is found
   */
  private Function<AbstractMessage,Optional<EvtType>> classifierStgragey;
   

}


The mark up look-up engine looks for a mark up strategy and the mark up implementation simply renders them, fetching the strategies from the server to browser/client only once. A CDN can be used, too! 

The perinodal structure looks like this:

Java
 
interface IMarkup{
  public void markup(AbstractMessage message, OutputStream outStream);
}

@Service
@SLF4j
@RequiredArgsConstructor
// We use it as regsitered bean in Spring but this is not specific to any framework   

class MarkupLookupService{
  private final MarkupRegistry regitry; // We use it as regsitered bean in Spring but this is not specific to any framework   
  public Optional<IMarkup> lookupMarkup(EvtType evtType){
     if(regitry.supports(evtType)){
       return registry.get(evtType);//write your look up logic
     }else{
        return Optional.<IMarkup>empty();
     }
  }

}
HTML
 
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Cricket Match IND vs ENG </title>
	<style>
		body {
			margin: 0;
			overflow: hidden;
		}

		canvas {
			display: block;
		}

		.container {
			display: flex;
			height: 100vh;
			width: 100%;
		}

		.left-section {

			background-color: lightblue;
			width: 80%;
			padding: 20px;
			box-sizing: border-box;

		}

		.right-section {
			width: 20%;
			background-color: white;
			padding: 20px;
			box-sizing: border-box;
		}

		#toast {
			visibility: hidden;
			min-width: 250px;
			margin-left: -125px;
			background-color: #333;
			color: #fff;
			text-align: center;
			border-radius: 5px;
			padding: 16px;
			position: fixed;
			z-index: 1;
			left: 50%;
			bottom: 30px;
			font-size: 37px;
		}

		#toast.show {
			visibility: visible;
			animation: fadein 0.5s, fadeout 0.5s 2.5s;
		}

		@keyframes fadein {
			from {
				top: -50px;
				opacity: 0;
			}

			to {
				top: 30px;
				opacity: 1;
			}
		}

		@keyframes fadeout {
			from {
				top: 30px;
				opacity: 1;
			}

			to {
				top: -50px;
				opacity: 0;
			}
		}
	</style>
</head>

<body>
	<div class="container">
		<div class="left-section">
			<canvas id="cricketField"></canvas>
			<div id="toast"></div>
		</div>
		<div class="right-section">
			<textarea rows="10" cols="50" id="tb_Comment" placeholder="Message here"
				style="color:gold;background-color:black;read-only:true" readonly></textarea>
			<button onclick="sendTextMsg()">Send</button>
		</div>
	</div>
	<script>
		let socket;
		let dto;
		window.onload = function () {
			socket = new WebSocket("ws://"+window.location.host+"/score");
			socket.onmessage = function (event) {

				var d = event.data;
				var data = JSON.parse(d);
				console.log(data + "  " + data.startX);
				clearCanvas();
				if ("VISUAL" === data.type) {
					drawLine(data.startX, data.startY, data.endX, data.endY);
				} else if("TEXT"===data.type){

					document.getElementById("tb_Comment").value += data.message + "\n";
				}else if("TOAST"===data.type){
					showToast(data.message);
				}else{
					console.error("Unsupported message type "+d);
				}
			};

		};
		const canvas = document.getElementById('cricketField');
		const ctx = canvas.getContext('2d');

		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight;

		function drawCricketField() {
			ctx.fillStyle = 'green';
			ctx.fillRect(0, 0, canvas.width, canvas.height);

			const centerX = canvas.width / 2;
			const centerY = canvas.height / 2;
			const fieldWidth = 600;
			const fieldHeight = 400;

			// Draw the oval cricket field
			ctx.fillStyle = 'lightgreen';
			ctx.beginPath();
			ctx.ellipse(centerX, centerY, fieldWidth / 2, fieldHeight / 2, 0, 0, Math.PI * 2);
			ctx.fill();

			// Draw pitch and creases
			ctx.fillStyle = 'white';
			ctx.fillRect(centerX - 3, centerY - 150, 6, 300); // Pitch
			ctx.fillRect(centerX - 150, centerY - 3, 300, 6); // Crease

			// Draw stumps
			ctx.fillRect(centerX - 6, centerY - 160, 4, 20);
			ctx.fillRect(centerX - 2, centerY - 160, 4, 20);
			ctx.fillRect(centerX + 2, centerY - 160, 4, 20);

			ctx.fillRect(centerX - 6, centerY + 140, 4, 20);
			ctx.fillRect(centerX - 2, centerY + 140, 4, 20);
			ctx.fillRect(centerX + 2, centerY + 140, 4, 20);
		}

		let drawing = false;
		let startX = 0;
		let startY = 0;
		let currentX = 0;
		let currentY = 0;

		canvas.addEventListener('mousedown', (e) => {
			const rect = canvas.getBoundingClientRect();
			const mouseX = e.clientX - rect.left;
			const mouseY = e.clientY - rect.top;
			// console.log(mouseX+" " +mouseY+ctx.isPointInPath(mouseX, mouseY));
			//  if (ctx.isPointInPath(mouseX, mouseY)) {
			drawing = true;
			startX = mouseX;
			startY = mouseY;
			currentX = mouseX;
			currentY = mouseY;
			// }
		});

		canvas.addEventListener('mousemove', (e) => {
			if (drawing) {
				const rect = canvas.getBoundingClientRect();
				const mouseX = e.clientX - rect.left;
				const mouseY = e.clientY - rect.top;
				currentX = mouseX;
				currentY = mouseY;
				clearCanvas();
				drawLine(startX, startY, currentX, currentY);
			}
		});

		canvas.addEventListener('mouseup', () => {
			drawing = false;
		});

		canvas.addEventListener('mouseout', () => {
			drawing = false;
		});

		function clearCanvas() {
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			drawCricketField();
		}

		function drawLine(startX, startY, endX, endY) {

			ctx.beginPath();
			ctx.moveTo(startX, startY);
			ctx.lineTo(endX, endY);
			ctx.strokeStyle = 'red';
			ctx.lineWidth = 5;  // Increase the line width for better visibility
			ctx.stroke();
			ctx.closePath();
		}

		function showToast(message) {
			var toast = document.getElementById("toast");
			toast.className = "show";
			toast.textContent = mapToVisual(message);
			setTimeout(function () {
				toast.className = toast.className.replace("show", "");
			}, 3000);
		}
		
		function mapToVisual(msg){
			switch(msg){
				case "100": return "100*";
				case "out": return "OUT!!";
				case "boundary": return "Boundary";
				case "over-boundary": return "Over-boundary";
			}
			return "";
		}
		drawCricketField();
	</script>
</body>

</html>


The logic to translation of marking up different types of event differently to the client can be done in multiple ways and we list down only a few:

  • Write the strategy in client side code as a library (e.g., a JavaScript library). 
    • The downside is that updating the logic is prone to errors, as with any scripting.
    • Caching and CDN—Ensuring the updates reflect and is not cached except beyond current session. 
  • Writing the strategy in the server side and sending transpiled Script back. 
    • The client side code and the  backend is no more loosely coupled then.

We will cover each approach in detail some other time. 

WebSocket Event Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • WebSocket vs. Server-Sent Events: Choosing the Best Real-Time Communication Protocol
  • Reactive Event Streaming Architecture With Kafka, Redis Streams, Spring Boot, and HTTP Server-Sent Events (SSE)
  • Competing Consumers With Spring Boot and Hazelcast
  • Reactive Kafka With Streaming in Spring Boot

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: