How to standardize operations on sets -First class collection-

How to standardize operations on sets

Introduction

I've summarized what's popular in me these days.

Being able to read this content

Make operations on sets (lists, maps, etc.) meaningful

What was originally prepared as an operation on a set is ** too fine or too specific **, and it is difficult to understand what it means to the user of the software. Here, for a set We will summarize the operations at a granularity that is meaningful to the user.

Limit operations on sets (arrays, lists, etc.)

Various methods are defined in advance in the class (list or map) that represents a set. However, there may be methods that you do not want to use in your business logic. It is not necessary to write to the set. On the contrary, the state where it can be written may cause a bug. The implementation method introduced here can be used to limit the operations to the set.

Examples handled in the article

Assume a web application that aggregates the number of events (Push events) that occur on GitHub for each repository and shares it on Twitter.

architecture

Assume a multi-module configuration in which the following are divided into modules.

Module name Description
web There is a web package that handles the IO of the browser and an application package that realizes the specifications of the application..
domain Express business logic by entities and models.
infrastructure Implement the interface defined in the domain layer.

Outline of specifications

The code implements the logic to aggregate the number of Events that occurred on GitHub for each Git repository. The code is

The GitEvent class is defined as follows.

domain.GitEvent.scala


class GitEvent(val gitRepository: GitRepository, val eventType: GitEventType)

You can also get a collection of GitEvents through the getUserEvents method of GitEventClient.

domain.client.GitEventClient.scala


trait GitEventClient {
  def getUserEvents(gitAccount: GitAccount): Seq[GitEvent]
}

Before: Create an application service and aggregate.

code

At first, I used the application service to perform aggregation. Perhaps even in business, the application service often calls the method of the data access layer and processes the acquired data.

web.application.GitActivitySummaryService.scala


class GitActivitySummaryService(){
  def summarize(userId: UserId): Seq[GitActivitySummary] = {
    val accountService: GitAccountService = new GitAccountService()
    val gitAccount = accountService.getByUserId(userId)

    //Get events based on the Git account information you got.
    val eventClient: GitEventClient = new GitHubEventClient()
    val gitEvents: Seq[GitEvent] = eventClient.getUserEvents(gitAccount)
    
    //Group by repository,Repository information,Generate a collection of instances with the number of times
    events.groupBy(e => e.gitRepository).map(e => new GitActivitySummary(e._1, e._2.size)).toSeq
  }
}

problem

The biggest problem with this code is that the event aggregation logic can be implemented in different places. For example, until now, only real-time aggregation was performed on the Web, but what should we do if the following request arises?

Since the permissions to resources are different between the web application and batch, we decided to create a batch module.

Although batch requires the same aggregation logic as the web application, it cannot be referenced from batch because the logic was written in the web module when the web application was implemented.

I "I can't help it, do I copy the same class to the batch module?" Everyone "Wait, wait, wait."

Implementation by first class collection

code

I'm trying to automate the aggregation of Git events, so I think the aggregation logic is the biggest concern in this app. That's why I created the GitEvents class in the domain module. ..

domain.gitEvents.scala


class GitEvents (private val events: Seq[GitEvent]) {
  def countByRepository() = {
    val gitActivitySummaries = events.groupBy(e => e.gitRepository).map(e => new GitActivitySummary(e._1, e._2.size)).toSeq
    new GitActivitySummaries(gitActivitySummaries)
  }
}

Since GitEventClient also returns an instance of the GitEvents class, Seq [GitEvent] can no longer be operated directly outside the GitEvents class. Object-oriented encapsulation may be something like this.

domain.client.GitEventClient.scala


trait GitEventClient {
  def getUserEvents(gitAccount: GitAccount): GitEvents
}

Digression

If you expose the method of the field collection via the wrapper class as shown below, you can operate it as if you were dealing with the field collection directly.

def foreach(f: GitEvent => Unit) = events.foreach(f)

def map[T](f:GitEvent=>T)=events.map(f)

If you need to implement other methods defined in the collection of fields, you can provide the API to the calling class as if you were working directly with the collection. You can also write a for expression by implementing the iterator method.

def iterator = events.iterator

for ( event <- events) {
  println(event.eventType)
}

Recommended Posts

How to standardize operations on sets -First class collection-
How to deploy on heroku
How to use java class
How to deploy Laravel on CentOS 7
How to "hollow" View on Android
[Java] How to update Java on Windows
How to install ImageMagick on Windows 10
How to disassemble Java class files
How to use Ruby on Rails
How to deploy Bootstrap on Rails
How to run JavaFX on Docker
How to use Bio-Formats on Ubuntu 20.04
How to decompile java class files
How to install MariaDB 10.4 on CentOS 8
[Java] How to use LinkedHashMap class
Rails on Tiles (how to write)
How to install WildFly on Ubuntu 18.04
How to use class methods [Java]
How to build vim on Ubuntu 20.04
[Java] How to use Math class
How to check Java installed on Mac
A memorandum on how to use Eclipse
How to redo a deployment on Heroku
How to use Apache Derby on Eclipse
[Java] How to use the File class
[Ruby on Rails] How to use CarrierWave
How to standardize header footer in Thymeleaf
[Java] How to use the HashMap class
How to install Eclipse (Photon) on Mac
How to publish an application on Heroku
How to install production Metabase on Ubuntu
How to switch Java versions on Mac
How to install beta php8.0 on CentOS8
How to change the timezone on Ubuntu
How to define an inner class bean
[Processing × Java] How to use the class
[Ruby on Rails] How to use kaminari
How to install kafkacat on Amazon Linux2
[Java] How to use the Calendar class
How to send push notifications on AWS
How to disable user operations during asynchronous processing
[Java] How to use FileReader class and BufferedReader class
[Ruby on Rails] How to display error messages
How to deploy a container on AWS Lambda
How to configure ubuntu to be used on GCP
How to add / remove Ruby on Rails columns
How to make rbenv recognize OpenSSL on WSL
How to install network drivers on standalone Ubuntu
How to save images on Heroku to S3 on AWS
How to create a class that inherits class information
How to get Class from Element in Java
How to convert a solidity contract to a Java contract class
Notes on how to use each JUnit Rule
How to conditionally add html.erb class in Rails
Note how to rollback Mysql deployed on Heroku
How to install multiple JDKs on Ubuntu 18.04 LTS
[Rails MySQL] How to reset DB on heroku
[Ruby on Rails] How to install Bootstrap in Rails
How to build a Pytorch environment on Ubuntu
[Ruby on Rails] How to use session method
[Java] Memo on how to write the source