Menjadi pemula berarti menjelajahi cakrawala baru dalam pemrograman, melangkah ke hal yang tidak diketahui, berharap bahwa ada tempat yang lebih baik.
Saya pikir Anda akan setuju bahwa memulai proyek dengan teknologi baru seringkali cukup menyenangkan. Masalah yang Anda hadapi dan coba selesaikan tidak selalu mudah, meskipun masalah tersebut merupakan bagian integral dari perjalanan Anda untuk menjadi seorang guru.
Jadi apa yang saya bicarakan. Hari ini saya di sini untuk berbagi dengan Anda pengalaman pertama saya dalam membuat sistem dari CMS, API, dan blog Hedless. Karena kurangnya jumlah materi semacam itu, terutama dalam bahasa Rusia, saya harap artikel ini akan membantu Anda membuat sistem seperti itu sendiri, menghindari kesalahan yang saya buat.
Saya akan memberi tahu Anda bagaimana saya merakit sistem dalam balok dan apa hasilnya. Saya tidak akan menjelaskan informasi latar belakang, tetapi saya akan meninggalkan tautan ke sumber daya di mana Anda dapat mempelajari lebih lanjut. Kadang-kadang sulit menemukan sumber berbahasa Rusia, tetapi saya akan mencobanya. Selain itu, Anda dapat menonton ceramah (dalam bahasa Inggris) atau membaca artikel ini (yang paling dekat artinya) jika Anda tidak yakin tentang keunggulan layanan mikro dibandingkan arsitektur monolitik.
API ( , ):
Vidzhel/Bluro
, , , - , - . .
( ) . , , , «» . .
, , . . - , .
, , . , Headless () CMS, Bluro. «Hello world» , «TechOverload» .
-, , , .
, . . . , , , , .
, :
, ,
, , ,
, ,
,
, ,
, , , , :
, , . , , . , , : , , Headless CMS, , .
- , Python Django. , , .
, YouTube, .
, , . — , URL (, ). - .
, , . , , .
API. - , .
JavaScript, NodeJS React . , .
Bluro CMS
Headless CMS , (UI). , . CMS API (REST API , ), .
, , , API — , — , . , , , , URL-, , .
, http . , , .
— MVC (Model View Controller). ( ).
, , , , .
CMS .
, - API, CMS. , , , , .
- , .
. .
Main , .
ORM
, — ORM (Object Relational Mapper).
, , , - ? , , . , . , — .
— «». , , SQL .
, , . : (, ), ( ), , . , , . , - « ».
. , Model ( ), . Model . , , .
, ORM. , .
. , , . . , , - . , , - , . , - : ).
, . Sequelize API Django, . ORM.
Entities — , ( , ). Model QuerySet , . , QuerySet Statement, API . StatementsBuilder — , Statement . , .
« », , .
, , . , , , ORM.
ORM. , .
const Model = DependencyResolver . getDependency ( null , "Model" ) ;
const ARTICLE_STATES = {
PUBLISHED : "PUBLISHED" ,
PENDING_PUBLISHING : "PENDING_PUBLISHING" ,
} ;
const VERBOSE_REGEXP = /^ [ 0-9a-z-._~] * $ /i ;
class Article extends Model {
static STATES = ARTICLE_STATES ;
// There can be other methods
// that fetch data for you or process it in some way
}
// Define model with schema
Article . init ( [
{
columnName : "user" ,
foreignKey : {
table : "User" ,
columnName : "id" ,
onDelete : Model . OP . CASCADE ,
onUpdate : Model . OP . CASCADE ,
} ,
type : Model . DATA_TYPES . INT ( ) ,
} ,
{
columnName : "dateOfPublishing" ,
verboseName : "Date of publishing" ,
type : Model . DATA_TYPES . DATE_TIME ( ) ,
nullable : true ,
validators : Model . CUSTOM_VALIDATORS_GENERATORS . dateInterval ( ) ,
} ,
{
columnName : "dateOfChanging" ,
verboseName : "Date of changing" ,
type : Model . DATA_TYPES . DATE_TIME ( ) ,
validators : Model . CUSTOM_VALIDATORS_GENERATORS . dateInterval ( ) ,
} ,
...
{
columnName : "state" ,
verboseName : "Article state" ,
type : Model . DATA_TYPES . VARCHAR ( 18 ) ,
possibleValues : Object . values ( ARTICLE_STATES ) ,
} ,
] ) ;
// Somewhere else
const set = await Article . selector
. orderBy ( { dateOfPublishing : "DESC" } )
. limit ( offset , count )
. filter ( {
firstValue : "dateOfChanging" ,
operator : Operators . between ,
innerCondition : {
firstValue : "10.11.2020" ,
operator : Operators . and ,
secondValue : "11.11.2020" ,
} ,
} )
. filter ( {
user : "userId" ,
state : Article . STATES . PUBLISHED ,
} )
. fetch ( ) ;
const resulte = await set . getList ( ) ;
. , .
, , . , . , , Django.
, CMS, , . , , , . , , . , . , , , .
GIT, , .
. , .
{
" migrated" : true ,
" initialMigration" : true ,
" tables" : [
[
" User" ,
{
" migrated" : false ,
" DEFINE_TABLE" : true ,
" DEFINE_COLUMN" : {
" userName" : {
" name" : " userName" ,
" type" : { " id" : " VARCHAR" , " size" : 10 },
" default" : null ,
" nullable" : false ,
" autoincrement" : false ,
" primaryKey" : false ,
" unique" : false ,
" foreignKey" : null
},
" password" : {
" name" : " password" ,
" type" : { " id" : " VARCHAR" , " size" : 10 },
" default" : null ,
" nullable" : false ,
" autoincrement" : false ,
" primaryKey" : false ,
" unique" : false ,
" foreignKey" : null
},
" id" : {
" name" : " id" ,
" type" : { " id" : " INT" },
" default" : null ,
" nullable" : false ,
" autoincrement" : true ,
" primaryKey" : true ,
" unique" : false ,
" foreignKey" : null
}
}
}
]
],
" name" : " 0_Auth_migration.json"
}
{
" migrated" : true ,
" initialMigration" : false ,
" tables" : [
[
" User" ,
{
" migrated" : false ,
" CHANGE_COLUMN" : {
" password" : {
" name" : " password" ,
" type" : {
" id" : " VARCHAR" ,
" size" : 50
},
" default" : null ,
" nullable" : false ,
" autoincrement" : false ,
" primaryKey" : false ,
" unique" : false ,
" foreignKey" : null
}
}
}
]
],
" name" : " 1_Auth_migration.json"
}
Server
, http-. , , . HTTP, : Request Response, .
Request , , multipart / form-data.
Response , . , cookie.
Router
«» — , . Express — , , , .
Route — , . , , . .
Rule — , . , authorizationRule, , . , . , . Rule , , Rule Route.
. , ( ), .
connectRule ( "all" , "/" , authRule , { sensitive : false } ) ;
connectRule ( [ "put" , "delete" ] , "/profiles/{verbose}" , requireAuthorizationRule ) ;
connectRule ( [ "post" , "delete" ] , "/profiles/{user}/followers" , requireAuthorizationRule ) ;
connectRoute ( "get" , "/profiles" , getProfilesController ) ;
connectRoute ( "put" , "/profiles/{verbose}" , updateProfileController ) ;
, . , API. , , , , .
, API, , . , , , .
. — - , . Modules Manager, , , , . , .
, SOLID, . , , . , , . - , .
.
API
, API . , , . , , .
, , : , , , ...
API , . , , .
, JWT (JSON Web Token) cookie. . .
, :
authRule — , cookie . , , .
requireAuthorizationRule — , .
, . , .
.
.
NotificationService .
API:
{
" email" : " email" ,
" pass" : " password"
}
{
" session" : {
" verbose" : " id that is used to get profile info" ,
" userName" : " userName" ,
" role" : " user role: 'ADMIN', 'USER'" ,
" email" : " email"
},
" errors" : " error's descriptions list" ,
" success" : " success's descriptions list" ,
" info" : " info's descriptions list" ,
" notifications" : [
" collection of notifications"
]
}
CMS, , , . React .
- , . « » . React Router . , -. , , , -, .
Redux "" Redux-Saga ( Redux-Saga ). , Redux (Action), . (Reducer) , - , , .
, Redux-Saga , , . , .
Redux-Saga, Headless CMS. , :
function * fetchData ( endpoint , requestData ) {
const controller = new AbortController ( ) ;
const { signal } = controller ;
let res , wasTimeout , reason , failure ;
failure = false ;
try {
// use Fetch API to make request, wait no longer than `TIMEOUT`
const raceRes = yield race ( [
call ( fetch , endpoint , {
...requestData ,
signal,
mode : "cors" ,
redirect : "follow" ,
credentials : "include" ,
} ) ,
delay ( TIMEOUT , true ) ,
] ) ;
res = raceRes [ 0 ] ;
wasTimeout = raceRes [ 1 ] || false ;
if ( wasTimeout ) {
failure = true ;
reason = "Connection timeout" ;
// Abort fetching
controller . abort ( ) ;
}
} catch ( e ) {
console . log ( e ) ;
reason = "Error occurred" ;
}
return { reason, res, failure, wasTimeout } ;
}
export function * makeRequest ( endpoint , requestData ) {
// Signal that we start making request (we can use it to show loading wheel)
yield put ( { type : SES_ASYNC . START_MAKING_REQUEST_ASYNC } ) ;
// call enother saga that will make request
let { res, reason, failure, wasTimeout } = yield call ( fetchData , endpoint , requestData ) ;
if ( res ) {
// Process response
const results = yield call ( handleResponse , res , wasTimeout , reason , failure ) ;
// Signal about finishing
yield put ( { type : SES_ASYNC . END_MAKING_REQUEST_ASYNC } ) ;
return results ;
} else {
// Return error
failure = true ;
reason = "Server error" ;
yield put ( { type : SES_ASYNC . END_MAKING_REQUEST_ASYNC } ) ;
return { res : null , wasTimeout, reason, data : null , failure } ;
}
}
fetchData — , Fetch API . , TIMEOUT, . makeRequest , . - . , , :
function * openArticle ( { verbose } ) {
// Get cached articles from the state
const article = yield select ( getFetchedArticle , verbose ) ;
// If we don't have this article in cache, fetch it
if ( !article ) {
const { failure, data } = yield call (
makeRequest ,
`${ configs . endpoints . articles } /${ verbose } ` ,
{
method : "GET" ,
} ,
) ;
if ( !failure ) {
article = yield call ( convertArticleData , data . entry ) ;
}
}
// If article was successfuly fetched, we signaling to open it
if ( article ) {
yield fork ( fetchArticleContent , { fileName : article . textSourceName } ) ;
yield put ( {
type : ART_ASYNC . OPEN_ARTICLE_ASYNC ,
article,
} ) ;
}
}
, . ( ).
. — .
- NGINX:
server {
listen 80;
client_max_body_size 100M;
location / {
proxy_pass http://front_blog:3000;
}
location /admin {
proxy_pass http://front_admin_panel:3000;
}
location /api {
rewrite ^/api/?(.*)$ /$1 break;
proxy_pass http://bluro_api:8000;
}
}
Docker Compose, . , ( — ).
- , headlesscms.org , Headless CMS , .
, , , -.