All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fraktalio.fmodel.domain.View.kt Maven / Gradle / Ivy

Go to download

Fmodel provides just enough tactical Domain-Driven Design patterns, optimised for Event Sourcing and CQRS. The domain model library is fully isolated from the application layer and API-related concerns. It represents a pure declaration of the program logic.

There is a newer version: 3.6.0
Show newest version
/*
 * Copyright (c) 2021 Fraktalio D.O.O. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.fraktalio.fmodel.domain

/**
 * An interface of the [_View]
 *
 * @param Si Input_State type
 * @param So Output_State type
 * @param E Event type
 *
 * @author Иван Дугалић / Ivan Dugalic / @idugalic
 */
interface I_View {
    val evolve: (Si, E) -> So
    val initialState: So
}

/**
 * A convenient typealias for the [I_View] interface. It is specializing the three parameters [I_View] interface to only two parameters interface [IView].
 *
 * `Si=So -> S`
 * `E -> E`
 *
 * @author Иван Дугалић / Ivan Dugalic / @idugalic
 */
typealias IView = I_View

/**
 * [_View] is a datatype that represents the event handling algorithm,
 * responsible for translating the events into denormalized state,
 * which is more adequate for querying.
 *
 * It has three generic parameters [Si], [So], [E], representing the type of the values that [_View] may contain or use.
 * [_View] can be specialized for any type of [Si], [So], [E] because these types does not affect its behavior.
 * [_View] behaves the same for [E]=[Int] or [E]=YourCustomType, for example.
 *
 * [_View] is a pure domain component
 *
 * @param Si Input_State type
 * @param So Output_State type
 * @param E Event type
 * @property evolve A pure function/lambda that takes input state of type [Si] and input event of type [E] as parameters, and returns the output/new state [So]
 * @property initialState A starting point / An initial state of type [So]
 * @constructor Creates [_View]
 *
 * @author Иван Дугалић / Ivan Dugalic / @idugalic
 */
data class _View(
    override val evolve: (Si, E) -> So,
    override val initialState: So,
) : I_View {
    /**
     * Left map on E/Event parameter - Contravariant
     *
     * @param En Event new
     * @param f
     */
    inline fun  mapLeftOnEvent(crossinline f: (En) -> E): _View = _View(
        evolve = { si, en -> this.evolve(si, f(en)) },
        initialState = this.initialState
    )

    /**
     * Dimap on S/State parameter - Contravariant on the Si (input State) - Covariant on the So (output State) = Profunctor
     *
     * @param Sin State input new
     * @param Son State output new
     * @param fl
     * @param fr
     */
    inline fun  dimapOnState(
        crossinline fl: (Sin) -> Si,
        crossinline fr: (So) -> Son
    ): _View = _View(
        evolve = { sin, e -> fr(this.evolve(fl(sin), e)) },
        initialState = fr(this.initialState)
    )

    /**
     * Left map on S/State parameter - Contravariant
     *
     * @param Sin State input new
     * @param f
     */
    inline fun  mapLeftOnState(crossinline f: (Sin) -> Si): _View =
        dimapOnState(f) { it }

    /**
     * Right map on S/State parameter - Covariant
     *
     * @param Son State output new
     * @param f
     */
    inline fun  mapOnState(crossinline f: (So) -> Son): _View =
        dimapOnState({ it }, f)
}

// ### Extension functions ###
// We could have included these functions inside the _View data type if we had changed _View’s type arguments to be invariant but this would cascade poorly for inference.
// If we move it to an extension then E, Si, ... are no longer coming from the instance but from the environment in invariant position which is acceptable.


/**
 * Apply on S/State parameter - Applicative
 *
 * @param Si State input type
 * @param So State output type
 * @param E Event type
 * @param Son State output new type
 * @param ff
 */
fun  _View.applyOnState(ff: _View Son, E>): _View = _View(
    evolve = { si, e -> ff.evolve(si, e)(this.evolve(si, e)) },
    initialState = ff.initialState(this.initialState)
)

/**
 * Product on S/State parameter - Applicative
 *
 * @param Si State input type
 * @param So State output type
 * @param E Event type
 * @param Son State output new type
 * @param fb
 */
fun  _View.productOnState(fb: _View): _View, E> =
    applyOnState(fb.mapOnState { b: Son -> { a: So -> Pair(a, b) } })

/**
 * Combines [_View]s into one bigger [_View]
 *
 * Possible to use when [E] and [E2] have common superclass [E_SUPER]
 *
 * @param Si State input of the first View
 * @param So State output of the first View
 * @param E Event of the first View
 * @param Si2 State input of the second View
 * @param So2 State output of the second View
 * @param E2 Event of the second View
 * @param E_SUPER super type for [E] and [E2]
 * @param y second View
 * @return new View of type [_View]<[Pair]<[Si], [Si2]>, [Pair]<[So], [So2]>, [E_SUPER]>
 */
inline fun  _View.combine(
    y: _View
): _View, Pair, E_SUPER> {

    val viewX = this
        .mapLeftOnEvent { it as? E }
        .mapLeftOnState> { pair -> pair.first }

    val viewY = y
        .mapLeftOnEvent { it as? E2 }
        .mapLeftOnState> { pair -> pair.second }

    return viewX.productOnState(viewY)
}

/**
 * A typealias for [_View], specializing the [_View] to two generic parameters: S and E, where Si=S, So=S, E=E
 */
typealias View = _View




© 2015 - 2024 Weber Informatics LLC | Privacy Policy