pretty

Saturday, 5 November 2022

Accessing CoreData from a single background context

The usual scheme when working with Core Data is using one or more of the background contexts for modifying data, and one viewContext for reading and displaying it.

Using multiple background contexts may seem like a good idea because it allows data to be modified simultaneously from different threads. However, doing so increases the complexity of the program. Properly configuring Core Data’s merge policies to handle all possible cases can be tricky, which may lead to concurrency-related issues.

The following unit test recreates the basic concurrency issue on purpose, when the same data entity is being modified from those two background contexts on lines 59 and 75:

The test will pass if line 78 is uncommented, because it sets a merge policy that resolves conflicts by keeping the persisted version instead of the newer changes. However, a more practical approach is to use a single background context for all modifications, executing them sequentially. This simplifies concurrency handling and avoids merge conflicts, making the code easier to manage and reason about.

To employ sequential CoreData writing scheme it's convenient to have a wrapper class that would hold the required single background context. The wrapper class in this sample is named CoreDataInventory and should be used for every CoreData interaction in the application. CoreDataInventory wrapper class in this design is a singleton:

Having this class let's change the unit test, and call the new method CoreDataInventory.instance.perform(...) instead of performBackgroundTask(...). The CoreData merge policy in the test is also the default one (used by CoreData when no specific policy is set), and should throw the error if there is a merge conflict.

This test would pass without merge conflicts.

In conclusion, many background contexts may be used only when it is proven that concrete application performs a high amount of time-consuming data writes, and practically benefits from having multiple background contexts. Otherwise, more practical alternative is to use a single background context for writing to CoreData.

Thursday, 14 July 2022

Http status codes: 401 vs 403

401 is about failed Authentication - I say who I am, do you believe me? If not - respond with 401 Unauthenticated

Example: login failed due to invalid credentials.


403 is about failed Authorization - my authentication is accepted, can I access this? If not - respond with 403 Unauthorized

Example: access to specific resource is not permitted with my role.

Wednesday, 24 February 2021

Kotlin contravariance samples

Contravariance is used to allow adding into collection values of type T and T subtypes. The collection itself, however, is allowed to contain items of type T and any super types of T (because of this it is not safe to get from this collection, with the exception of getting the root of type hierarchy Any?).

1. Use site variance

Sample: list with contravariance acts as a consumer.
It is safe to add into list the object of declared type B, or it's subtypes. It is not safe to retrieve object of type B from the list, because list of objects with super types of B may be provided. However, it is safe to retrieve the Kotlin most basic type Any? from the list.
 
open class A
open class B : A()
class C : B()

// Can receive list of B or its super types (A, Any, Any? types in this sample).
// Allows to put B or B subtypes into list (C in this sample).
// Cannot get fom List - except, can only get most generic type Any? from list.

fun listAsConsumer(list: MutableList<in B>) {
     // Type mismatch: inferred type is Any? but B was expected
     // val b: B = list[0] 
     
     // Type mismatch: inferred type is A but B was expected 
     // list.add(A())  
     
     // Cannot get fom List - except, can only get most generic type Any? from list.
     val any: Any? = list[0]
     
     // Allowed to add B or its subtypes into list
     list.add(B())
     list.add(C())
}

fun main() { 
    listAsConsumer(mutableListOf<A>(A())) 
} 

Sample: contravariance in Comparator.
Compare list of Ints using Comparator<Number>.
fun <T> Iterable<T>.sortedWithWrapper(comparator: Comparator<in T>): List<T> {
    return this.sortedWith(comparator)
}

fun main() { 
    val comparator: Comparator<Number> = Comparator { o1: Number, o2: Number ->
        o1.toDouble().compareTo(o2.toDouble())
    }
    val intList: List<Int> = listOf(4, 7)
    val result: List<Int> = intList.sortedWithWrapper(comparator)
}

The same sample without contravariance for Comparator is below. Without contravariance sortedWithWrapper() function could not return List<Int>, but only List<Number>. This happens because List<Int> is upcasted to List<Number>, to match the invariant T of the Comparator.
fun <T> Iterable<T>.sortedWithWrapper(comparator: Comparator<T>): List<T> {
    return this.sortedWith(comparator)
}

fun main() { 
    val comparator: Comparator<Number> = Comparator { o1: Number, o2: Number ->
        o1.toDouble().compareTo(o2.toDouble())
    }
    val intList: List<Int> = listOf(4, 7)
    
    // Type mismatch: inferred type is List<Number> but List<Int> was expected
    // val result: List<Int> = intList.sortedWithWrapper(comparator)
    val result: List<Number> = intList.sortedWithWrapper(comparator)
}


2. Declaration site variance

Declaration site variance is available in Kotlin, unlike Java where only use-site variance is possible.

Sample: Class with contravariance. Consumer cannot have contravariant type T in invariant or out positions.
open class A
open class B : A() 
class C : B() 
 
class Consumer<in T> {
    
    // Type parameter T is declared as 'in' but occurs in 'invariant' 
    // position in type T?
    // var x: T? = null

    fun consume(x: T) {}  
     
    // Ok, List is defined as List<out T>,
    // so it is a producer for this Consumer class
    fun consume(x: List<T>) {} 
     
    // Type parameter T is declared as 'in' but occurs in 'invariant' position 
    // in type Array<T>
    // fun consume(x: Array<T>) {} 
     
    // Type parameter T is declared as 'in' but occurs in 'out' position
    // in type T?
    // fun produce() : T? { return x }   
}

fun main() {   
    // Consumer of B (can be assigned Consumer of supertypes (A, Any, Any?)),
    // but now minimum B or its subtypes are accepted into consume()
    val consumerB: Consumer<B> = Consumer<A>() 
    consumerB.consume(C())
    
    // Type mismatch: inferred type is A but B was expected
    // consumerC.consume(A()) 
    
    // Type mismatch: inferred type is Consumer<B> but Consumer<A> was expected
    // val consumerA: Consumer<A> = Consumer<B>()  
}

Consumer class with contravariant T cannot accept another consumer with contravariant T.
class AnotherConsumer<in T> {
     fun consume() {}
}

class Consumer<in T> {
   
    // Type parameter T is declared as 'in' but occurs in 'out' position
    // in type AnotherConsumer
    // fun consume(x: AnotherConsumer<T>) {}  
}

This is due to AnotherConsumer can be extended by the class that cancels the contravariant 'in' modifier:
open class AnotherConsumer<in T> {
     open fun consume(x: T) {}
}

class AnotherConsumerChild: AnotherConsumer<T>() {
    var x: T? = null
    
    override fun consume(x: T) {
        this.x = x
    }  
    
    fun getT(): T? {
        return x
    }
}

Allowing to accept another contravariant consumer would lead to ClassCastException.
As an example of the above, in this snippet AnotherConsumerC would be called with the wrong type A (omitting compiler variance checking with @UnsafeVariance annotation).
open class A
open class B : A() 
class C : B() {
    fun c() {}
} 

open class AnotherConsumer<in T> {
     open fun consume(x: T) {}
}

// Overrides contravariance in parent type
class AnotherConsumerC: AnotherConsumer<C>() {
    override fun consume(x: C) {
        x.c()
    }  
}

open class Consumer<in T> {  
    open fun consume(anotherConsumer: AnotherConsumer<@UnsafeVariance T>) {}  
}  

open class ConsumerA: Consumer<A>() {  
    override fun consume(anotherConsumer: AnotherConsumer<A>) {
        anotherConsumer.consume(A())
    }  
}  

fun main() {   
    val consumerA = ConsumerA()
    
    // Allowed because parent Consumer<in T> is contravariant
    val consumerC: Consumer<C> = consumerA
    
    // Produces Exception in thread "main" 
    // java.lang.ClassCastException: A cannot be cast to C
    consumerC.consume(AnotherConsumerC())
}