-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: modernization use case example (DRAFT-DO NOT MERGE) #367
base: main
Are you sure you want to change the base?
Changes from all commits
d2dd2d8
630bbcb
8718cd3
82802fe
e3ef68a
c0a9702
772fee8
ae711f5
972dc52
6cf063f
4e479f4
4058716
1f66b0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.mparticle.modernization | ||
|
||
class BatchManager(private val api : MpApiClientImpl) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as other comment, this should take an |
||
|
||
suspend fun createBatch() : MpBatch? = MpBatch("", Math.random().toLong()) | ||
|
||
suspend fun uploadBatch(batch : MpBatch) { | ||
api.uploadBatch(batch) | ||
} | ||
|
||
} | ||
|
||
class MpBatch(data : String, batchId : Long){} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.mparticle.modernization | ||
|
||
internal abstract class MParticleCallback<S, E> { | ||
var isSuccessFul: Boolean = false | ||
var isCompleted: Boolean = false | ||
|
||
fun onSuccess(result: S) { | ||
isSuccessFul = true | ||
isCompleted = true | ||
} | ||
|
||
fun onError(error: E) { | ||
isSuccessFul = false | ||
isCompleted = true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.mparticle.modernization | ||
|
||
class MpApiClientImpl { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should have an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, just wanted to outline the idea of the data source itself. We might even have the ApiClient itself without interface if we don't intend to have multiple implementations of it (foe example multiple http clients as tools to resolve the implementation). In the future we could always create the abstraction and change this into an Impl. |
||
|
||
suspend fun uploadBatch(data : MpBatch) {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.mparticle.modernization | ||
|
||
import com.mparticle.modernization.core.MParticleMediator | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.launch | ||
|
||
internal fun MParticleMediator.launch(block: suspend CoroutineScope.() -> Unit) { | ||
this.coroutineScope.launch(this.coroutineDispatcher) { block } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.mparticle.modernization | ||
|
||
import android.content.Context | ||
import com.mparticle.MPEvent | ||
import com.mparticle.MParticleOptions | ||
import com.mparticle.commerce.CommerceEvent | ||
import com.mparticle.commerce.Product | ||
import com.mparticle.modernization.core.MParticle | ||
import com.mparticle.modernization.eventlogging.MParticleEventLogging | ||
|
||
|
||
class WalkthroughTest(private val context: Context) { | ||
|
||
private var eventLogging: MParticleEventLogging? = null | ||
|
||
//Only setup | ||
fun initialize() { | ||
val options = MParticleOptions.builder(context).credentials("key", "secret").build() | ||
MParticle.start(options) | ||
eventLogging = MParticle.getInstance().EventLogging() | ||
} | ||
|
||
init { | ||
initialize() | ||
} | ||
|
||
//=================== | ||
|
||
fun runTest() { | ||
logCommerceEvent() | ||
logNormalEvent() | ||
} | ||
|
||
fun logCommerceEvent() { | ||
val product = Product.Builder("testName", "testSku", 100.0).build() | ||
val commerceEvent = CommerceEvent.Builder(Product.CHECKOUT, product).build() | ||
eventLogging?.logEvent(commerceEvent) | ||
} | ||
fun logNormalEvent() { | ||
val customAttributes = | ||
mutableMapOf(Pair("key1", "value1"), Pair("key2", 4), Pair("key3", null)) | ||
val event = MPEvent.Builder("myEvent").customAttributes(customAttributes).build() | ||
eventLogging?.logEvent(event) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.mparticle.modernization.core | ||
|
||
import com.mparticle.MParticleOptions | ||
import com.mparticle.modernization.data.MParticleDataRepository | ||
import com.mparticle.modernization.eventlogging.MParticleEventLogging | ||
import com.mparticle.modernization.identity.MParticleIdentity | ||
import com.mparticle.modernization.kit.MParticleKitManager | ||
import com.mparticle.modernization.datahandler.MParticleDataHandler | ||
|
||
internal class MParticle private constructor(private val options: MParticleOptions) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What exactly does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This enables to use the dependency injection pattern, making the property accesible to be used in the class but restrict visibility from consumers. If options would have been public, MParticle.getInstance().options would have been valid.. Moreover by default properties have public setters/getters so, someone from the outside could eventually temper with the properties within MParticleOptions |
||
private var mediator: MParticleMediator = MParticleMediator(MParticleDataRepository()) | ||
|
||
init { | ||
mediator.configure(options) | ||
} | ||
|
||
companion object { | ||
private var _instance: MParticle? = null | ||
|
||
@Throws(Exception::class) | ||
fun getInstance(): MParticle = _instance ?: throw Exception("MParticle must be started before getting the instance") | ||
|
||
fun start(options: MParticleOptions) { | ||
_instance = MParticle(options) | ||
} | ||
} | ||
|
||
fun KitManager(): MParticleKitManager? = mediator.kitManager as MParticleKitManager? | ||
fun Identity(): MParticleIdentity? = mediator.identity as MParticleIdentity? | ||
fun EventLogging(): MParticleEventLogging? = mediator.eventLogging | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package com.mparticle.modernization.core | ||
|
||
internal interface MParticleComponent |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package com.mparticle.modernization.core | ||
|
||
import com.mparticle.MParticleOptions | ||
import com.mparticle.modernization.BatchManager | ||
import com.mparticle.modernization.MpApiClientImpl | ||
import com.mparticle.modernization.data.MParticleDataRepository | ||
import com.mparticle.modernization.eventlogging.MParticleEventLogging | ||
import com.mparticle.modernization.eventlogging.example.MParticleEventLoggingImpl | ||
import com.mparticle.modernization.identity.InternalIdentity | ||
import com.mparticle.modernization.identity.example.MParticleIdentityImpl | ||
import com.mparticle.modernization.kit.KitManagerInternal | ||
import com.mparticle.modernization.kit.MParticleKit | ||
import com.mparticle.modernization.kit.MParticleKitManagerImpl | ||
import com.mparticle.modernization.kit.example.MpKit | ||
import com.mparticle.modernization.datahandler.MParticleDataStrategyManagerImpl | ||
import com.mparticle.modernization.datahandler.MParticleDataHandlerStrategy | ||
import com.mparticle.modernization.datahandler.example.MParticleCommerceHandler | ||
import kotlinx.coroutines.CloseableCoroutineDispatcher | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.SupervisorJob | ||
import kotlinx.coroutines.asCoroutineDispatcher | ||
import java.util.concurrent.Executors | ||
|
||
internal class MParticleMediator(private val dataRepository: MParticleDataRepository) { | ||
internal var eventLogging: MParticleEventLogging? = null | ||
internal var identity: InternalIdentity? = null | ||
internal var kitManager: KitManagerInternal? = null | ||
internal var batchManager : BatchManager = BatchManager(MpApiClientImpl()) | ||
|
||
private var mParticleUploadingStrategies: List<MParticleDataHandlerStrategy<*,*>> = listOf( | ||
MParticleCommerceHandler(dataRepository, batchManager) | ||
) | ||
|
||
internal lateinit var coroutineScope: CoroutineScope | ||
internal lateinit var coroutineDispatcher: CloseableCoroutineDispatcher | ||
|
||
/** | ||
* Mediator register kits and components, acting as a "common layer" for the components internally, | ||
* and also providing a single instance of the available and visible components to the MParticle | ||
* facade. This will help also controlling which this we want to make accesible and which one we doesn't. | ||
*/ | ||
fun configure(options: MParticleOptions) { | ||
/** | ||
* Creation of auto-cancellable thread-pool using coroutines. | ||
* Using a utility, and due to the fact that the relationship between mediator-mPaticle instance | ||
* is 1:1, we would be able to launch async operation managed by the thread pool of each mparticle | ||
* instance | ||
*/ | ||
coroutineScope = CoroutineScope(SupervisorJob()) | ||
coroutineDispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher() | ||
|
||
var kits: MutableList<MParticleKit> = registerKits(options) | ||
/** | ||
* Mediator has a reference to each component. The components mostly are instanciated with a | ||
* dependency to the mediator, meaning that, if component A (running action 1) needs an action 3 from component B | ||
* to be executed to complete action 1; component A can use the mediator to access component B | ||
*/ | ||
registerComponent(MParticleKitManagerImpl(kits)) | ||
registerComponent(MParticleIdentityImpl(this)) | ||
registerComponent(MParticleEventLoggingImpl(this)) | ||
} | ||
|
||
private fun registerKits(options: MParticleOptions): MutableList<MParticleKit> = | ||
mutableListOf(MpKit(this, MParticleDataStrategyManagerImpl(mParticleUploadingStrategies))) | ||
|
||
private fun registerComponent(component: MParticleComponent) { | ||
when (component) { | ||
is InternalIdentity -> identity = component | ||
is KitManagerInternal -> kitManager = component | ||
is MParticleEventLogging -> eventLogging = component | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.mparticle.modernization.data | ||
|
||
import com.mparticle.internal.messages.BaseMPMessage | ||
import com.mparticle.modernization.MpBatch | ||
|
||
/** | ||
* This is based on the repository pattern to manage data sources (apiClients, daos, | ||
* sharedPreferences, cache, memory, etc). | ||
* Ideally we would inject our data sources here, and the repository would be incharge of preprocessing | ||
* the data interacting with the data source, and post-process the data coming from data sources. | ||
*/ | ||
internal class MParticleDataRepository { | ||
|
||
suspend fun insertCommerceDTO(data: BaseMPMessage) { | ||
//TODO parses the event and saves it into the db | ||
} | ||
|
||
suspend fun getEventsByType() : List<BaseMPMessage>? = emptyList() | ||
|
||
suspend fun getBatch() : MpBatch? = null | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.mparticle.modernization.datahandler | ||
|
||
enum class DataHandlerType { | ||
MP_EVENT, COMMERCE_EVENT, BREADCRUMB; | ||
|
||
companion object { | ||
fun getType(clazz: Class<*>): DataHandlerType? { | ||
return when (clazz.javaClass.simpleName) { | ||
"com.mparticle.MpEvent" -> MP_EVENT | ||
"com.mparticle.commerce.CommerceEvent" -> COMMERCE_EVENT | ||
else -> null | ||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.mparticle.modernization.datahandler | ||
|
||
import com.mparticle.modernization.core.MParticleComponent | ||
import org.jetbrains.annotations.NotNull | ||
|
||
internal interface MParticleDataHandler : MParticleComponent { | ||
/** | ||
*Save set of data using provided strategies. Decision made base on [type] | ||
* | ||
* @param data any type of data. Each strategy is responsible of converting and handling the data | ||
* @param immediateUpload true or false depending if we want to force immediate data upload. By | ||
* default this is false | ||
*/ | ||
suspend fun saveData( | ||
@NotNull data: Any, | ||
@NotNull immediateUpload: Boolean = false | ||
) | ||
|
||
suspend fun configure() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.mparticle.modernization.datahandler | ||
|
||
|
||
import com.mparticle.modernization.BatchManager | ||
import org.jetbrains.annotations.NotNull | ||
|
||
/** | ||
* Data uploading strategies for different data types | ||
*/ | ||
internal interface MParticleDataHandlerStrategy<I, O> { | ||
/** Upload set of data | ||
* | ||
* @param data any type of data to upload | ||
* @param immediateUpload true or false depending if we want to force immediate data upload. By | ||
* default this is false | ||
* @param uploadingConfiguration to handle auto-data uploads or other custom implementation based | ||
* on a configuration. | ||
*/ | ||
suspend fun saveData( | ||
@NotNull data: Any, | ||
@NotNull immediateUpload: Boolean | ||
) | ||
|
||
suspend fun retrieveData(): List<O> | ||
|
||
fun I.toDto(): O? | ||
|
||
fun O.toModel(): I? | ||
|
||
/** | ||
* @return strategy id | ||
*/ | ||
fun type(): DataHandlerType | ||
|
||
} | ||
|
||
abstract class BaseMParticleDataHandlerStrategy<I, O>(protected val batchManager: BatchManager) : | ||
MParticleDataHandlerStrategy<I, O> { | ||
protected open suspend fun createAndUploadBatch() { | ||
with(batchManager) { | ||
createBatch()?.let { this.uploadBatch(it) } | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.mparticle.modernization.datahandler | ||
|
||
internal class MParticleDataStrategyManagerImpl( | ||
private val strategies: List<MParticleDataHandlerStrategy<*, *>>, | ||
) : MParticleDataHandler { | ||
|
||
/** | ||
* Based on the data type we will choose the corresponding strategy provided at config type, and | ||
* execute an action on it. | ||
*/ | ||
override suspend fun saveData(data: Any, immediateUpload: Boolean) { | ||
data.javaClass.getStrategy()?.saveData(data, immediateUpload) | ||
} | ||
|
||
private fun Class<*>.getStrategy(): MParticleDataHandlerStrategy<*, *>? = | ||
strategies.firstOrNull { it.type() == DataHandlerType.getType(this) } | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on latest discussions, we should use the term "nextgen" instead of "modernization"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure