Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Jul 21, 2024
1 parent c570469 commit 6887c1a
Show file tree
Hide file tree
Showing 12 changed files with 478 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"languageToolLinter.enabled": false
}
42 changes: 42 additions & 0 deletions docs/arsitektur/01-layering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: Arsitektur
---

Aplikasi didesain menggunakan DDD (_domain-driven design_) dan _onion
architecture_. _Onion architecture_ adalah arsitektur yang memisahkan antara
business logic dengan teknologi yang digunakan. Dengan _onion architecture_,
aplikasi akan lebih mudah diuji, dikembangkan, dan dipelihara.

Aplikasi kita dibagi menjadi beberapa layer, dimulai dari layer terdalam:

* Layer **domain**. Namespace `App\Domain`. Terdiri dari entity, value object
dan objek-objek pendukung, seperti collection, domain event, exception, dan
lain-lain.

* Layer **domain service**. Namespace `App\DomainService`. Terdiri dari
repository, event listener, dan service lain yang hanya memiliki dependency ke
layer domain.

* Layer **application service**. Namespace `App\ApplicationService`. Terdiri
dari application-level event listener, messenger handler, dan service
semacamnya.

* Layer-layer luar, terdiri dari:

* Layer **infrastructure**. Namespace `App\Infrastructure`. Terdiri dari
implementasi dari repository, event listener, dan service lain yang
berinteraksi dengan teknologi eksternal.

* Layer **front end**. Berkaitan dengan interaksi aplikasi dengan pengguna.
Terdiri dari:

* Layer **web front end**. Namespace `App\WebFrontEnd`. Terdiri dari
web controller, form, template, dan service lain yang berhubungan dengan
web.

* Layer **API front end**. Namespace `App\ApiFrontEnd`. Terdiri dari
API resource, API state, konfigurasi mapper, dan sebagainya.

* Layer **console front end**. Namespace `App\Command`. Terdiri dari
console command, dan service lain yang berhubungan.

7 changes: 7 additions & 0 deletions docs/arsitektur/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Arsitektur
---

import DocCardList from '@theme/DocCardList';

<DocCardList />
1 change: 0 additions & 1 deletion docs/intro.md

This file was deleted.

242 changes: 242 additions & 0 deletions docs/tutorial/01-domain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
---
title: Domain
---

Untuk memulai, pertama-tama kita perlu desain domain-nya. Komponen utama domain
adalah entity dan value object. Bedanya, entity memiliki state, dan biasanya
akan disimpan di database. Sedangkan value object tidak memiliki state.

:::caution

Pada tahap ini kita tidak perlu memikirkan teknologi yang digunakan, seperti
database dan sebagainya. Kita hanya perlu fokus pada model bisnisnya saja.

:::

## Entity `Post`

Berikut contoh entity untuk `Post`:

```php
namespace App\Domain;

use Rekalogika\CommonBundle\Domain\Entity\AbstractAggregateRoot;
use Symfony\Component\Clock\DatePoint;

class Post extends AbstractAggregateRoot
{
private \DateTimeInterface $createdTime;
private string $title;
private string $content;

public function __construct(
string $title,
string $content,
) {
$this->title = $title;
$this->createdTime = new DatePoint();
}
}
```

Terlihat bahwa class ini sudah bisa mewakili fungsi dasar dari blog post. Setiap
blog post memiliki judul, konten, dan waktu pembuatan. Waktu pembuatan otomatis
dibuat saat objek dibuat. Sedangkan terlihat `title` dan `content` adalah
property yang wajib ada (tidak boleh kosong), dan harus ada saat objek pertama
kali dibuat.

:::info

`DateTimeInterface` adalah interface dari semua objek waktu di PHP.
Implementasi di PHP berupa `DateTimeImmutable` dan `DateTime`. Di sini kita
menggunakan `DatePoint` dari Symfony Clock. Alasannya karena `DatePoint` lebih
mudah diuji daripada `DateTimeImmutable` dan `DateTime`.

:::

Properti `createdTime` tidak boleh diubah setelah, hanya bisa dilihat, maka kita
jadikan `readonly` dan hanya kita buatkan getternya, tidak perlu setter:

```php
class Post extends AbstractAggregateRoot
{
private readonly \DateTimeInterface $createdTime;

public function getCreatedTime(): \DateTimeInterface
{
return $this->createdTime;
}
}
```

:::info

Objek `DateTimeInterface` adalah salah satu contoh dari value object.

:::

Karena `title` dan `content` boleh diubah setelah pos dibuat, maka kita buatkan
getter dan setternya:

```php
class Post extends AbstractAggregateRoot
{
public function getTitle(): string
{
return $this->title;
}

public function setTitle(string $title): void
{
$this->title = $title;
}

public function getContent(): string
{
return $this->content;
}

public function setContent(string $content): void
{
$this->content = $content;
}
}
```

## Entity `Comment`

Karena setiap pos bisa memiliki komentar, maka kita buat entity komentar:

```php
namespace App\Domain;

use Rekalogika\CommonBundle\Domain\Entity\AbstractEntity;
use Symfony\Component\Clock\DatePoint;

class Comment extends AbstractEntity
{
private string $content;
private readonly \DateTimeInterface $createdTime;

public function __construct(string $content)
{
$this->content = $content;
$this->createdTime = new DatePoint();
}

public function getCreatedTime(): \DateTimeInterface
{
return $this->createdTime;
}
}
```

Kurang lebih sama perilakunya dengan `Post`. Komentar memiliki konten dan waktu.

## Relasi Pada Sisi `Post`

Karena setiap pos bisa memiliki beberapa komentar, maka kita perlu membuat
relasinya. Kita bisa membuat property `comments` di `Post`:

```php
use Doctrine\Common\Collections\Collection;
use Rekalogika\Contracts\Collections\Recollection;
use Rekalogika\Domain\Collections\ArrayCollection;
use Rekalogika\Domain\Collections\RecollectionDecorator;

class Post extends AbstractAggregateRoot
{
/**
* @var Collection<string,Comment>
*/
private Collection $comments;

public function __construct(
// ...
) {
// ...
$this->comments = new ArrayCollection();
}

public function addComment(Comment $comment): void
{
$this->getComments()->add($comment);
$comment->setPost($this);
}

/**
* @return Recollection<string,Comment>
*/
public function getComments(): Recollection
{
return RecollectionDecorator::create(
collection: $this->insurables,
count: new PrecountingStrategy($this->insurablesCount),
indexBy: 'id',
orderBy: ['id' => Order::Descending]
);
}

public function removeComment(Comment $comment): void
{
if ($this->getComments()->removeElement($comment)) {
if ($comment->getPost() === $this) {
$comment->setPost(null);
}
}
}
}
```

Yang perlu diperhatikan:

* Untuk menampung komentar, harus menggunakan tipe `Collection`, tidak boleh
array biasa.
* Di atasnya kita tambahkan `@var Collection<string,Comment>` untuk memberi tahu
bahwa `comments` adalah collection dari beberapa objek `Comment` dengan key
bertipe `string`.

:::info Perbedaan Dengan Symfony dan Doctrine Standard

Kita menggunakan `ArrayCollection` versi kita, bukan yang dari Doctrine,
[penjelasannya di
sini](https://rekalogika.dev/collections/implementations/array-collection).

Untuk `getComments()`, kita menggunakan `RecollectionDecorator`, bukan plain
`Collection`. Alasannya:

* Mengimplementasikan `PageableInterface` yang bisa digunakan langsung untuk
batch processing dan pagination
* Otomatis melakukan limit, sehingga tidak akan menghabiskan memori.

Cek infonya [di sini](https://rekalogika.dev/collections).

Yang mengakses `$this->comments` hanyalah method `getComments()`. Method lain
harus menggunakan method `getComments()` untuk mengakses data komentar. Ini kita
lakukan untuk menghindari kesalahan pemrograman yang dapat mengakibatkan
out-of-memory.

:::

## Relasi Pada Sisi `Comment`

Lalu untuk `Comment`, kita bisa menambahkan property `post`:

```php
class Comment extends AbstractEntity
{
private ?Post $post = null;

public function setPost(?Post $post): void
{
$this->post = $post;
}

public function getPost(): Post
{
return $this->post;
}
}
```

Jika `$post` null, maka komentar tersebut tidak terhubung dengan pos manapun.
10 changes: 10 additions & 0 deletions docs/tutorial/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Tutorial
---

import DocCardList from '@theme/DocCardList';

Untuk tutorial ini, kita akan membuat aplikasi blog sederhana. Aplikasi ini
terdiri dari dua entitas utama: `Post` dan `Comment`.

<DocCardList />
9 changes: 8 additions & 1 deletion docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const config = {
organizationName: 'rekalogika', // Usually your GitHub org/user name.
projectName: 'docs', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
onBrokenMarkdownLinks: 'throw',
i18n: {
defaultLocale: 'id',
locales: ['id'],
Expand Down Expand Up @@ -50,6 +50,12 @@ const config = {
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
docs: {
sidebar: {
hideable: true,
// autoCollapseCategories: true,
},
},
// Replace with your project's social card
// image: 'img/docusaurus-social-card.jpg',
navbar: {
Expand Down Expand Up @@ -121,6 +127,7 @@ const config = {
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
additionalLanguages: ['diff', 'php', 'bash', 'twig', 'yaml', 'json', 'markup', 'handlebars', 'liquid', 'diff-php']
},
}),
};
Expand Down
28 changes: 14 additions & 14 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@

/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-color-primary: #1a5fb4;
--ifm-color-primary-dark: #1755a2;
--ifm-color-primary-darker: #165199;
--ifm-color-primary-darkest: #12427e;
--ifm-color-primary-light: #1d68c6;
--ifm-color-primary-lighter: #1e6dcf;
--ifm-color-primary-lightest: #2075dd;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}

/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--ifm-color-primary: #99c1f1;
--ifm-color-primary-dark: #76acec;
--ifm-color-primary-darker: #65a1ea;
--ifm-color-primary-darkest: #3182e3;
--ifm-color-primary-light: #bcd6f6;
--ifm-color-primary-lighter: #cde1f8;
--ifm-color-primary-lightest: #ffffff;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
4 changes: 2 additions & 2 deletions src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export default function Home() {
const {siteConfig} = useDocusaurusContext();
return (
<Layout
title={`Hello from ${siteConfig.title}`}
description="Dokumentasi Dev Rekalogika">
title={`${siteConfig.title}`}
description="Dokumen Asli">
<HomepageHeader />
</Layout>
);
Expand Down
Loading

0 comments on commit 6887c1a

Please sign in to comment.