aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: e65edf5b564a667fb1d916dfcc67b084e6aade43 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# Driver Core Library [![Build Status](https://travis-ci.com/drivergroup/driver-core.svg?token=S4oyfBY3YoEdLmckujJx&branch=master)](https://travis-ci.com/drivergroup/driver-core)

Core library is used to provide ways to implement practices established in [Driver service template](http://github.com/drivergroup/driver-template) (check its [README.md](https://github.com/drivergroup/driver-template/blob/master/README.md)).

## Components

 * `core package` provides `Id` and `Name` implementations (with equal and ordering), utils for ScalaZ `OptionT`, and also `make` and `using` functions and `@@` (tagged) type,
 * `tagging` Utilities for tagging primitive types for extra type safety, as well as some tags that involve extra transformation upon deserializing with spray,
 * `config` Contains method `loadDefaultConfig` with default way of providing config to the application,
 * `domain` Common generic domain objects, e.g., `Email` and `PhoneNumber`,
 * `messages` Localization messages supporting different locales and methods to read from config,
 * `database` Method for database initialization from config, `Id`, `Name`, `Time`, `Date` etc. mapping, schema lifecycle and base classes to implement and test `Repository` (data access objects),
 * `rest` Wrapper over call to external REST API, authorization, context headers, XSS protection, does logging and allows to add Swagger to a service,
 * `auth` Basic entities for authentication and authorization: `User`, `Role` `Permission` `AuthToken`, `AuthCredentials` etc.,
 * `swagger` Contains custom `AbstractModelConverter` to customize Swagger JSON with any Scala  JSON formats created by, for instance, Spray JSON,
 * `json` Json formats for `Id`, `Name`, `Time`, `Revision`, `Email`, `PhoneNumber`, `AuthCredentials` and converters for GADTs, enums and value classes,
 * `file` Interface `FileStorage` and file storage implementations with GCS, S3 and local FS,
 * `app` Base class for Driver service, which initializes swagger, app modules and its routes.
 * `generators` Set of functions to prototype APIs. Combines with `faker` package,
 * `stats` Methods to collect system stats: memory, cpu, gc, file system space usage,
 * `logging` Custom Driver logging layout (not finished yet).

Dependencies of core modules might be found in [Dependencies of the Modules diagram](https://github.com/drivergroup/driver-template/blob/master/Modules%20dependencies.pdf) file in driver-template repository in "Core component dependencies" section.

## Examples

### Functions `make` and `using`
Those functions are especially useful to make procedural legacy Java APIs more functional and make scope of its usage more explicit. Runnable examples of its usage might be found in [`CoreTest`](https://github.com/drivergroup/driver-core/blob/master/src/test/scala/xyz/driver/core/CoreTest.scala), e.g.,

    useObject(make(new ObjectWithProceduralInitialization) { o =>
      o.setSetting1(...) // returns Unit
      o.setSetting2(...) // returns Unit
      o.setSetting3(...) // returns Unit
    })

    // instead of
    val o = new ObjectWithProceduralInitialization
    o.setSetting1(...)
    o.setSetting2(...)
    o.setSetting3(...)

    useObject(o)

and

    using(... open output stream ...) { os =>
      os.write(...)
    }

    // it will be close automatically

### `OptionT` utils
Before

```
OptionT.optionT[Future](service.getRecords(id).map(Option.apply))
OptionT.optionT(service.doSomething(id).map(_ => Option(())))

// Do not want to stop and return `None`, if `produceEffect` returns `None`
for {
  x <- service.doSomething(id)
  _ <- service.produceEffect(id, x).map(_ => ()).orElse(OptionT.some[Future, Unit](())))
} yield x
```

after

```
service.getRecords(id).toOptionT
service.doSomething(id).toUnitOptionT

// Do not want to stop and return `None` if `produceEffect` returns `None`
for {
  x <- service.doSomething(id)
  _ <- service.produceEffect(id, x).continueIgnoringNone
} yield x
```

### `@@` or Tagged types

For type definitions, the only import required is

```scala
import xyz.driver.core.@@
```

which provides just the ability to tag types: `val value: String @@ Important`. Two `String`s with different tags will
be distinguished by the compiler, helping reduce the possibility of mixing values passed into methods with several
arguments of identical types.

To work with tags in actual values, use the following convenience methods:

```scala
import xyz.driver.core.tagging._

val str = "abc".tagged[Important]
```

or go back to plain (say, in case you have an implicit for untagged value)

```scala
// val trimmedExternalId: String @@ Trimmed = "123"

Trials.filter(_.externalId === trimmedExternalId.untag)
```

### `Time` and `TimeProvider`

**NOTE: The contents of this section has been deprecated - use java.time.Clock instead**

Usage examples for `Time` (also check [TimeTest](https://github.com/drivergroup/driver-core/blob/master/src/test/scala/xyz/driver/core/TimeTest.scala) for more examples).

    Time(234L).isAfter(Time(123L))

    Time(123L).isBefore(Time(234L))

    Time(123L).advanceBy(4 days)

    Seq(Time(321L), Time(123L), Time(231L)).sorted

    startOfMonth(Time(1468937089834L)) should be (Time(1467381889834L))

    textualDate(Time(1468937089834L)) should be ("July 19, 2016")

    textualTime(Time(1468937089834L)) should be ("Jul 19, 2016 10:04:49 AM")

    TimeRange(Time(321L), Time(432L)).duration should be(111.milliseconds)


### Generators
Example of how to generate a case class instance,

    import com.drivergrp.core._

    Consumer(
         generators.nextId[Consumer],
         Name[Consumer](faker.Name.name),
         faker.Lorem.sentence(word_count = 10))


For more examples check [project tests](https://github.com/drivergroup/driver-core/tree/master/src/test/scala/xyz/driver/core) or [service template](http://github.com/drivergroup/driver-template) repository.

### App

To start a new application using standard Driver application class, follow this pattern:

    object MyApp extends App {

      new DriverApp(BuildInfo.version,
                    BuildInfo.gitHeadCommit.getOrElse("None"),
                    modules = Seq(myModule1, myModule2),
                    time, log, config,
                    interface = "::0", baseUrl, scheme, port)
                   (servicesActorSystem, servicesExecutionContext).run()
    }

### REST
With REST utils, for instance, you can use the following directives in [akka-http](https://github.com/akka/akka-http) routes, as follows

    sanitizeRequestEntity { // Prevents XSS
      serviceContext { implicit ctx => // Extracts context headers from request
        authorize(CanSeeUser(userId)) { user => // Authorizes and extracts user
          // Your code using `ctx` and `user`
        }
      }
    }

### Swagger
Swagger JSON formats built using reflection can be overriden by using `CustomSwaggerJsonConverter` at the start of your application initialization in the following way:

    ModelConverters.getInstance()
      .addConverter(new CustomSwaggerJsonConverter(Json.mapper(),
         CustomSwaggerFormats.customProperties, CustomSwaggerFormats.customObjectsExamples))

### Locale
Locale messages can be initialized and used in the following way,

    val localeConfig = config.getConfig("locale")
    val log = com.typesafe.scalalogging.Logger(LoggerFactory.getLogger(classOf[MyClass]))

    val messages = xyz.driver.core.messages.Messages.messages(localeConfig, log, Locale.US)

    messages("my.locale.message")
    messages("my.locale.message.with.param", parameter)


### System stats
Stats it gives access to are,

    xyz.driver.core.stats.SystemStats.memoryUsage

    xyz.driver.core.stats.SystemStats.availableProcessors

    xyz.driver.core.stats.SystemStats.garbageCollectorStats

    xyz.driver.core.stats.SystemStats.fileSystemSpace

    xyz.driver.core.stats.SystemStats.operatingSystemStats


## Running

1. Compile everything and test:

        $ sbt test

2. Publish to local repository, to use changes in depending projects:

        $ sbt publishLocal

3. In order to release a new version of core, merge your PR, tag the HEAD master commit with the next version
   (don't forget the "v." prefix) and push tags - Travis will release the artifact automatically!